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 29import re 30 31SHA1_RE = re.compile('^[a-fA-F0-9]{40}$') 32ROLL_DEPS_GIT_SVN_ID_RE = re.compile('^git-svn-id: .*@([0-9]+) .*$') 33 34# Regular expression that matches a single commit footer line. 35COMMIT_FOOTER_ENTRY_RE = re.compile(r'([^:]+):\s+(.+)') 36 37# Footer metadata key for commit position. 38COMMIT_POSITION_FOOTER_KEY = 'Cr-Commit-Position' 39 40# Regular expression to parse a commit position 41COMMIT_POSITION_RE = re.compile(r'(.+)@\{#(\d+)\}') 42 43# Key for the 'git-svn' ID metadata commit footer entry. 44GIT_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 48GIT_SVN_ID_RE = re.compile(r'((?:\w+)://[^@]+)@(\d+)\s+(?:[a-zA-Z0-9\-]+)') 49 50 51# Copied from bot_update.py. 52def 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 77class GitFailedException(Exception): 78 pass 79 80 81def Strip(f): 82 def new_f(*args, **kwargs): 83 return f(*args, **kwargs).strip() 84 return new_f 85 86 87def MakeArgs(l): 88 """['-a', '', 'abc', ''] -> '-a abc'""" 89 return " ".join(filter(None, l)) 90 91 92def Quoted(s): 93 return "\"%s\"" % s 94 95 96class GitRecipesMixin(object): 97 def GitIsWorkdirClean(self, **kwargs): 98 return self.Git("status -s -uno", **kwargs).strip() == "" 99 100 @Strip 101 def GitBranch(self, **kwargs): 102 return self.Git("branch", **kwargs) 103 104 def GitCreateBranch(self, name, branch="", **kwargs): 105 assert name 106 self.Git(MakeArgs(["checkout -b", name, branch]), **kwargs) 107 108 def GitDeleteBranch(self, name, **kwargs): 109 assert name 110 self.Git(MakeArgs(["branch -D", name]), **kwargs) 111 112 def GitReset(self, name, **kwargs): 113 assert name 114 self.Git(MakeArgs(["reset --hard", name]), **kwargs) 115 116 def GitStash(self, **kwargs): 117 self.Git(MakeArgs(["stash"]), **kwargs) 118 119 def GitRemotes(self, **kwargs): 120 return map(str.strip, 121 self.Git(MakeArgs(["branch -r"]), **kwargs).splitlines()) 122 123 def GitCheckout(self, name, **kwargs): 124 assert name 125 self.Git(MakeArgs(["checkout -f", name]), **kwargs) 126 127 def GitCheckoutFile(self, name, branch_or_hash, **kwargs): 128 assert name 129 assert branch_or_hash 130 self.Git(MakeArgs(["checkout -f", branch_or_hash, "--", name]), **kwargs) 131 132 def GitCheckoutFileSafe(self, name, branch_or_hash, **kwargs): 133 try: 134 self.GitCheckoutFile(name, branch_or_hash, **kwargs) 135 except GitFailedException: # pragma: no cover 136 # The file doesn't exist in that revision. 137 return False 138 return True 139 140 def GitChangedFiles(self, git_hash, **kwargs): 141 assert git_hash 142 try: 143 files = self.Git(MakeArgs(["diff --name-only", 144 git_hash, 145 "%s^" % git_hash]), **kwargs) 146 return map(str.strip, files.splitlines()) 147 except GitFailedException: # pragma: no cover 148 # Git fails using "^" at branch roots. 149 return [] 150 151 152 @Strip 153 def GitCurrentBranch(self, **kwargs): 154 for line in self.Git("status -s -b -uno", **kwargs).strip().splitlines(): 155 match = re.match(r"^## (.+)", line) 156 if match: return match.group(1) 157 raise Exception("Couldn't find curent branch.") # pragma: no cover 158 159 @Strip 160 def GitLog(self, n=0, format="", grep="", git_hash="", parent_hash="", 161 branch="", reverse=False, **kwargs): 162 assert not (git_hash and parent_hash) 163 args = ["log"] 164 if n > 0: 165 args.append("-%d" % n) 166 if format: 167 args.append("--format=%s" % format) 168 if grep: 169 args.append("--grep=\"%s\"" % grep.replace("\"", "\\\"")) 170 if reverse: 171 args.append("--reverse") 172 if git_hash: 173 args.append(git_hash) 174 if parent_hash: 175 args.append("%s^" % parent_hash) 176 args.append(branch) 177 return self.Git(MakeArgs(args), **kwargs) 178 179 def GitGetPatch(self, git_hash, **kwargs): 180 assert git_hash 181 return self.Git(MakeArgs(["log", "-1", "-p", git_hash]), **kwargs) 182 183 # TODO(machenbach): Unused? Remove. 184 def GitAdd(self, name, **kwargs): 185 assert name 186 self.Git(MakeArgs(["add", Quoted(name)]), **kwargs) 187 188 def GitApplyPatch(self, patch_file, reverse=False, **kwargs): 189 assert patch_file 190 args = ["apply --index --reject"] 191 if reverse: 192 args.append("--reverse") 193 args.append(Quoted(patch_file)) 194 self.Git(MakeArgs(args), **kwargs) 195 196 def GitUpload(self, reviewer="", author="", force=False, cq=False, 197 bypass_hooks=False, **kwargs): 198 args = ["cl upload --send-mail"] 199 if author: 200 args += ["--email", Quoted(author)] 201 if reviewer: 202 args += ["-r", Quoted(reviewer)] 203 if force: 204 args.append("-f") 205 if cq: 206 args.append("--use-commit-queue") 207 if bypass_hooks: 208 args.append("--bypass-hooks") 209 # TODO(machenbach): Check output in forced mode. Verify that all required 210 # base files were uploaded, if not retry. 211 self.Git(MakeArgs(args), pipe=False, **kwargs) 212 213 def GitCommit(self, message="", file_name="", author=None, **kwargs): 214 assert message or file_name 215 args = ["commit"] 216 if file_name: 217 args += ["-aF", Quoted(file_name)] 218 if message: 219 args += ["-am", Quoted(message)] 220 if author: 221 args += ["--author", "\"%s <%s>\"" % (author, author)] 222 self.Git(MakeArgs(args), **kwargs) 223 224 def GitPresubmit(self, **kwargs): 225 self.Git("cl presubmit", "PRESUBMIT_TREE_CHECK=\"skip\"", **kwargs) 226 227 def GitDCommit(self, **kwargs): 228 self.Git( 229 "cl dcommit -f --bypass-hooks", retry_on=lambda x: x is None, **kwargs) 230 231 def GitDiff(self, loc1, loc2, **kwargs): 232 return self.Git(MakeArgs(["diff", loc1, loc2]), **kwargs) 233 234 def GitPull(self, **kwargs): 235 self.Git("pull", **kwargs) 236 237 def GitFetchOrigin(self, **kwargs): 238 self.Git("fetch origin", **kwargs) 239 240 def GitConvertToSVNRevision(self, git_hash, **kwargs): 241 result = self.Git(MakeArgs(["rev-list", "-n", "1", git_hash]), **kwargs) 242 if not result or not SHA1_RE.match(result): 243 raise GitFailedException("Git hash %s is unknown." % git_hash) 244 log = self.GitLog(n=1, format="%B", git_hash=git_hash, **kwargs) 245 for line in reversed(log.splitlines()): 246 match = ROLL_DEPS_GIT_SVN_ID_RE.match(line.strip()) 247 if match: 248 return match.group(1) 249 raise GitFailedException("Couldn't convert %s to SVN." % git_hash) 250 251 @Strip 252 # Copied from bot_update.py and modified for svn-like numbers only. 253 def GetCommitPositionNumber(self, git_hash, **kwargs): 254 """Dumps the 'git' log for a specific revision and parses out the commit 255 position number. 256 257 If a commit position metadata key is found, its number will be returned. 258 259 Otherwise, we will search for a 'git-svn' metadata entry. If one is found, 260 its SVN revision value is returned. 261 """ 262 git_log = self.GitLog(format='%B', n=1, git_hash=git_hash, **kwargs) 263 footer_map = GetCommitMessageFooterMap(git_log) 264 265 # Search for commit position metadata 266 value = footer_map.get(COMMIT_POSITION_FOOTER_KEY) 267 if value: 268 match = COMMIT_POSITION_RE.match(value) 269 if match: 270 return match.group(2) 271 272 # Extract the svn revision from 'git-svn' metadata 273 value = footer_map.get(GIT_SVN_ID_FOOTER_KEY) 274 if value: 275 match = GIT_SVN_ID_RE.match(value) 276 if match: 277 return match.group(2) 278 return None 279 280 ### Git svn stuff 281 282 def GitSVNFetch(self, **kwargs): 283 self.Git("svn fetch", **kwargs) 284 285 def GitSVNRebase(self, **kwargs): 286 self.Git("svn rebase", **kwargs) 287 288 # TODO(machenbach): Unused? Remove. 289 @Strip 290 def GitSVNLog(self, **kwargs): 291 return self.Git("svn log -1 --oneline", **kwargs) 292 293 @Strip 294 def GitSVNFindGitHash(self, revision, branch="", **kwargs): 295 assert revision 296 return self.Git( 297 MakeArgs(["svn find-rev", "r%s" % revision, branch]), **kwargs) 298 299 @Strip 300 def GitSVNFindSVNRev(self, git_hash, branch="", **kwargs): 301 return self.Git(MakeArgs(["svn find-rev", git_hash, branch]), **kwargs) 302 303 def GitSVNDCommit(self, **kwargs): 304 return self.Git("svn dcommit 2>&1", retry_on=lambda x: x is None, **kwargs) 305 306 def GitSVNTag(self, version, **kwargs): 307 self.Git(("svn tag %s -m \"Tagging version %s\"" % (version, version)), 308 retry_on=lambda x: x is None, 309 **kwargs) 310