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 |