blob: 0f8fcef26d05fc08611974059a095a7d9c6a306c [file] [log] [blame]
Ben Murdochb8a8cc12014-11-26 15:28:44 +00001#!/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)