| OLD | NEW |
| (Empty) |
| 1 #!/usr/bin/env python | |
| 2 # Copyright 2014 the V8 project authors. All rights reserved. | |
| 3 # Redistribution and use in source and binary forms, with or without | |
| 4 # modification, are permitted provided that the following conditions are | |
| 5 # met: | |
| 6 # | |
| 7 # * Redistributions of source code must retain the above copyright | |
| 8 # notice, this list of conditions and the following disclaimer. | |
| 9 # * Redistributions in binary form must reproduce the above | |
| 10 # copyright notice, this list of conditions and the following | |
| 11 # disclaimer in the documentation and/or other materials provided | |
| 12 # with the distribution. | |
| 13 # * Neither the name of Google Inc. nor the names of its | |
| 14 # contributors may be used to endorse or promote products derived | |
| 15 # from this software without specific prior written permission. | |
| 16 # | |
| 17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 28 | |
| 29 import re | |
| 30 | |
| 31 SHA1_RE = re.compile('^[a-fA-F0-9]{40}$') | |
| 32 ROLL_DEPS_GIT_SVN_ID_RE = re.compile('^git-svn-id: .*@([0-9]+) .*$') | |
| 33 | |
| 34 # Regular expression that matches a single commit footer line. | |
| 35 COMMIT_FOOTER_ENTRY_RE = re.compile(r'([^:]+):\s+(.+)') | |
| 36 | |
| 37 # Footer metadata key for commit position. | |
| 38 COMMIT_POSITION_FOOTER_KEY = 'Cr-Commit-Position' | |
| 39 | |
| 40 # Regular expression to parse a commit position | |
| 41 COMMIT_POSITION_RE = re.compile(r'(.+)@\{#(\d+)\}') | |
| 42 | |
| 43 # Key for the 'git-svn' ID metadata commit footer entry. | |
| 44 GIT_SVN_ID_FOOTER_KEY = 'git-svn-id' | |
| 45 | |
| 46 # e.g., git-svn-id: https://v8.googlecode.com/svn/trunk@23117 | |
| 47 # ce2b1a6d-e550-0410-aec6-3dcde31c8c00 | |
| 48 GIT_SVN_ID_RE = re.compile(r'[^@]+@(\d+)\s+(?:[a-zA-Z0-9\-]+)') | |
| 49 | |
| 50 | |
| 51 # Copied from bot_update.py. | |
| 52 def GetCommitMessageFooterMap(message): | |
| 53 """Returns: (dict) A dictionary of commit message footer entries. | |
| 54 """ | |
| 55 footers = {} | |
| 56 | |
| 57 # Extract the lines in the footer block. | |
| 58 lines = [] | |
| 59 for line in message.strip().splitlines(): | |
| 60 line = line.strip() | |
| 61 if len(line) == 0: | |
| 62 del(lines[:]) | |
| 63 continue | |
| 64 lines.append(line) | |
| 65 | |
| 66 # Parse the footer | |
| 67 for line in lines: | |
| 68 m = COMMIT_FOOTER_ENTRY_RE.match(line) | |
| 69 if not m: | |
| 70 # If any single line isn't valid, the entire footer is invalid. | |
| 71 footers.clear() | |
| 72 return footers | |
| 73 footers[m.group(1)] = m.group(2).strip() | |
| 74 return footers | |
| 75 | |
| 76 | |
| 77 class GitFailedException(Exception): | |
| 78 pass | |
| 79 | |
| 80 | |
| 81 def Strip(f): | |
| 82 def new_f(*args, **kwargs): | |
| 83 result = f(*args, **kwargs) | |
| 84 if result is None: | |
| 85 return result | |
| 86 else: | |
| 87 return result.strip() | |
| 88 return new_f | |
| 89 | |
| 90 | |
| 91 def MakeArgs(l): | |
| 92 """['-a', '', 'abc', ''] -> '-a abc'""" | |
| 93 return " ".join(filter(None, l)) | |
| 94 | |
| 95 | |
| 96 def Quoted(s): | |
| 97 return "\"%s\"" % s | |
| 98 | |
| 99 | |
| 100 class GitRecipesMixin(object): | |
| 101 def GitIsWorkdirClean(self, **kwargs): | |
| 102 return self.Git("status -s -uno", **kwargs).strip() == "" | |
| 103 | |
| 104 @Strip | |
| 105 def GitBranch(self, **kwargs): | |
| 106 return self.Git("branch", **kwargs) | |
| 107 | |
| 108 def GitCreateBranch(self, name, remote="", **kwargs): | |
| 109 assert name | |
| 110 remote_args = ["--upstream", remote] if remote else [] | |
| 111 self.Git(MakeArgs(["new-branch", name] + remote_args), **kwargs) | |
| 112 | |
| 113 def GitDeleteBranch(self, name, **kwargs): | |
| 114 assert name | |
| 115 self.Git(MakeArgs(["branch -D", name]), **kwargs) | |
| 116 | |
| 117 def GitReset(self, name, **kwargs): | |
| 118 assert name | |
| 119 self.Git(MakeArgs(["reset --hard", name]), **kwargs) | |
| 120 | |
| 121 def GitStash(self, **kwargs): | |
| 122 self.Git(MakeArgs(["stash"]), **kwargs) | |
| 123 | |
| 124 def GitRemotes(self, **kwargs): | |
| 125 return map(str.strip, | |
| 126 self.Git(MakeArgs(["branch -r"]), **kwargs).splitlines()) | |
| 127 | |
| 128 def GitCheckout(self, name, **kwargs): | |
| 129 assert name | |
| 130 self.Git(MakeArgs(["checkout -f", name]), **kwargs) | |
| 131 | |
| 132 def GitCheckoutFile(self, name, branch_or_hash, **kwargs): | |
| 133 assert name | |
| 134 assert branch_or_hash | |
| 135 self.Git(MakeArgs(["checkout -f", branch_or_hash, "--", name]), **kwargs) | |
| 136 | |
| 137 def GitCheckoutFileSafe(self, name, branch_or_hash, **kwargs): | |
| 138 try: | |
| 139 self.GitCheckoutFile(name, branch_or_hash, **kwargs) | |
| 140 except GitFailedException: # pragma: no cover | |
| 141 # The file doesn't exist in that revision. | |
| 142 return False | |
| 143 return True | |
| 144 | |
| 145 def GitChangedFiles(self, git_hash, **kwargs): | |
| 146 assert git_hash | |
| 147 try: | |
| 148 files = self.Git(MakeArgs(["diff --name-only", | |
| 149 git_hash, | |
| 150 "%s^" % git_hash]), **kwargs) | |
| 151 return map(str.strip, files.splitlines()) | |
| 152 except GitFailedException: # pragma: no cover | |
| 153 # Git fails using "^" at branch roots. | |
| 154 return [] | |
| 155 | |
| 156 | |
| 157 @Strip | |
| 158 def GitCurrentBranch(self, **kwargs): | |
| 159 for line in self.Git("status -s -b -uno", **kwargs).strip().splitlines(): | |
| 160 match = re.match(r"^## (.+)", line) | |
| 161 if match: return match.group(1) | |
| 162 raise Exception("Couldn't find curent branch.") # pragma: no cover | |
| 163 | |
| 164 @Strip | |
| 165 def GitLog(self, n=0, format="", grep="", git_hash="", parent_hash="", | |
| 166 branch="", reverse=False, **kwargs): | |
| 167 assert not (git_hash and parent_hash) | |
| 168 args = ["log"] | |
| 169 if n > 0: | |
| 170 args.append("-%d" % n) | |
| 171 if format: | |
| 172 args.append("--format=%s" % format) | |
| 173 if grep: | |
| 174 args.append("--grep=\"%s\"" % grep.replace("\"", "\\\"")) | |
| 175 if reverse: | |
| 176 args.append("--reverse") | |
| 177 if git_hash: | |
| 178 args.append(git_hash) | |
| 179 if parent_hash: | |
| 180 args.append("%s^" % parent_hash) | |
| 181 args.append(branch) | |
| 182 return self.Git(MakeArgs(args), **kwargs) | |
| 183 | |
| 184 def GitGetPatch(self, git_hash, **kwargs): | |
| 185 assert git_hash | |
| 186 return self.Git(MakeArgs(["log", "-1", "-p", git_hash]), **kwargs) | |
| 187 | |
| 188 # TODO(machenbach): Unused? Remove. | |
| 189 def GitAdd(self, name, **kwargs): | |
| 190 assert name | |
| 191 self.Git(MakeArgs(["add", Quoted(name)]), **kwargs) | |
| 192 | |
| 193 def GitApplyPatch(self, patch_file, reverse=False, **kwargs): | |
| 194 assert patch_file | |
| 195 args = ["apply --index --reject"] | |
| 196 if reverse: | |
| 197 args.append("--reverse") | |
| 198 args.append(Quoted(patch_file)) | |
| 199 self.Git(MakeArgs(args), **kwargs) | |
| 200 | |
| 201 def GitUpload(self, reviewer="", author="", force=False, cq=False, | |
| 202 bypass_hooks=False, cc="", **kwargs): | |
| 203 args = ["cl upload --send-mail"] | |
| 204 if author: | |
| 205 args += ["--email", Quoted(author)] | |
| 206 if reviewer: | |
| 207 args += ["-r", Quoted(reviewer)] | |
| 208 if force: | |
| 209 args.append("-f") | |
| 210 if cq: | |
| 211 args.append("--use-commit-queue") | |
| 212 if bypass_hooks: | |
| 213 args.append("--bypass-hooks") | |
| 214 if cc: | |
| 215 args += ["--cc", Quoted(cc)] | |
| 216 # TODO(machenbach): Check output in forced mode. Verify that all required | |
| 217 # base files were uploaded, if not retry. | |
| 218 self.Git(MakeArgs(args), pipe=False, **kwargs) | |
| 219 | |
| 220 def GitCommit(self, message="", file_name="", author=None, **kwargs): | |
| 221 assert message or file_name | |
| 222 args = ["commit"] | |
| 223 if file_name: | |
| 224 args += ["-aF", Quoted(file_name)] | |
| 225 if message: | |
| 226 args += ["-am", Quoted(message)] | |
| 227 if author: | |
| 228 args += ["--author", "\"%s <%s>\"" % (author, author)] | |
| 229 self.Git(MakeArgs(args), **kwargs) | |
| 230 | |
| 231 def GitPresubmit(self, **kwargs): | |
| 232 self.Git("cl presubmit", "PRESUBMIT_TREE_CHECK=\"skip\"", **kwargs) | |
| 233 | |
| 234 def GitCLLand(self, **kwargs): | |
| 235 self.Git( | |
| 236 "cl land -f --bypass-hooks", retry_on=lambda x: x is None, **kwargs) | |
| 237 | |
| 238 def GitDiff(self, loc1, loc2, **kwargs): | |
| 239 return self.Git(MakeArgs(["diff", loc1, loc2]), **kwargs) | |
| 240 | |
| 241 def GitPull(self, **kwargs): | |
| 242 self.Git("pull", **kwargs) | |
| 243 | |
| 244 def GitFetchOrigin(self, **kwargs): | |
| 245 self.Git("fetch origin", **kwargs) | |
| 246 | |
| 247 @Strip | |
| 248 # Copied from bot_update.py and modified for svn-like numbers only. | |
| 249 def GetCommitPositionNumber(self, git_hash, **kwargs): | |
| 250 """Dumps the 'git' log for a specific revision and parses out the commit | |
| 251 position number. | |
| 252 | |
| 253 If a commit position metadata key is found, its number will be returned. | |
| 254 | |
| 255 Otherwise, we will search for a 'git-svn' metadata entry. If one is found, | |
| 256 its SVN revision value is returned. | |
| 257 """ | |
| 258 git_log = self.GitLog(format='%B', n=1, git_hash=git_hash, **kwargs) | |
| 259 footer_map = GetCommitMessageFooterMap(git_log) | |
| 260 | |
| 261 # Search for commit position metadata | |
| 262 value = footer_map.get(COMMIT_POSITION_FOOTER_KEY) | |
| 263 if value: | |
| 264 match = COMMIT_POSITION_RE.match(value) | |
| 265 if match: | |
| 266 return match.group(2) | |
| 267 | |
| 268 # Extract the svn revision from 'git-svn' metadata | |
| 269 value = footer_map.get(GIT_SVN_ID_FOOTER_KEY) | |
| 270 if value: | |
| 271 match = GIT_SVN_ID_RE.match(value) | |
| 272 if match: | |
| 273 return match.group(1) | |
| 274 raise GitFailedException("Couldn't determine commit position for %s" % | |
| 275 git_hash) | |
| OLD | NEW |