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 | |
mathiasb
2014/02/19 09:45:00
Next time a new file is added, consider using the
| |
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 from collections import OrderedDict | |
30 | |
31 from common_includes import * | |
32 | |
33 ALREADY_MERGING_SENTINEL_FILE = "ALREADY_MERGING_SENTINEL_FILE" | |
34 COMMIT_HASHES_FILE = "COMMIT_HASHES_FILE" | |
35 TEMPORARY_PATCH_FILE = "TEMPORARY_PATCH_FILE" | |
36 | |
37 CONFIG = { | |
38 BRANCHNAME: "prepare-merge", | |
39 PERSISTFILE_BASENAME: "/tmp/v8-merge-to-branch-tempfile", | |
40 ALREADY_MERGING_SENTINEL_FILE: | |
41 "/tmp/v8-merge-to-branch-tempfile-already-merging", | |
42 TEMP_BRANCH: "prepare-merge-temporary-branch-created-by-script", | |
43 DOT_GIT_LOCATION: ".git", | |
44 VERSION_FILE: "src/version.cc", | |
45 TEMPORARY_PATCH_FILE: "/tmp/v8-prepare-merge-tempfile-temporary-patch", | |
46 COMMITMSG_FILE: "/tmp/v8-prepare-merge-tempfile-commitmsg", | |
47 COMMIT_HASHES_FILE: "/tmp/v8-merge-to-branch-tempfile-PATCH_COMMIT_HASHES", | |
48 } | |
49 | |
50 | |
51 class MergeToBranchOptions(CommonOptions): | |
52 def __init__(self, options, args): | |
53 super(MergeToBranchOptions, self).__init__(options, options.m) | |
54 self.requires_editor = True | |
55 self.wait_for_lgtm = True | |
56 self.delete_sentinel = options.f | |
57 self.message = options.m | |
58 self.revert = "--reverse" if getattr(options, "r", None) else "" | |
59 self.revert_bleeding_edge = getattr(options, "revert_bleeding_edge", False) | |
60 self.patch = getattr(options, "p", "") | |
61 self.args = args | |
62 | |
63 | |
64 class Preparation(Step): | |
65 MESSAGE = "Preparation." | |
66 | |
67 def RunStep(self): | |
68 if os.path.exists(self.Config(ALREADY_MERGING_SENTINEL_FILE)): | |
69 if self._options.delete_sentinel: | |
70 os.remove(self.Config(ALREADY_MERGING_SENTINEL_FILE)) | |
71 elif self._options.s == 0: | |
72 self.Die("A merge is already in progress") | |
73 open(self.Config(ALREADY_MERGING_SENTINEL_FILE), "a").close() | |
74 | |
75 self.InitialEnvironmentChecks() | |
76 if self._options.revert_bleeding_edge: | |
77 self.Persist("merge_to_branch", "bleeding_edge") | |
78 elif self._options.args[0]: | |
79 self.Persist("merge_to_branch", self._options.args[0]) | |
80 self._options.args = self._options.args[1:] | |
81 else: | |
82 self.Die("Please specify a branch to merge to") | |
83 | |
84 self.CommonPrepare() | |
85 self.PrepareBranch() | |
86 | |
87 | |
88 class CreateBranch(Step): | |
89 MESSAGE = "Create a fresh branch for the patch." | |
90 | |
91 def RunStep(self): | |
92 self.RestoreIfUnset("merge_to_branch") | |
93 args = "checkout -b %s svn/%s" % (self.Config(BRANCHNAME), | |
94 self._state["merge_to_branch"]) | |
95 if self.Git(args) is None: | |
96 self.die("Creating branch %s failed." % self.Config(BRANCHNAME)) | |
97 | |
98 | |
99 class SearchArchitecturePorts(Step): | |
100 MESSAGE = "Search for corresponding architecture ports." | |
101 | |
102 def RunStep(self): | |
103 full_revision_list = list(OrderedDict.fromkeys(self._options.args)) | |
104 port_revision_list = [] | |
105 for revision in full_revision_list: | |
106 # Search for commits which matches the "Port rXXX" pattern. | |
107 args = ("log svn/bleeding_edge --reverse " | |
108 "--format=%%H --grep=\"Port r%d\"" % int(revision)) | |
109 git_hashes = self.Git(args) or "" | |
110 for git_hash in git_hashes.strip().splitlines(): | |
111 args = "svn find-rev %s svn/bleeding_edge" % git_hash | |
112 svn_revision = self.Git(args).strip() | |
113 if not svn_revision: | |
114 self.Die("Cannot determine svn revision for %s" % git_hash) | |
115 revision_title = self.Git("log -1 --format=%%s %s" % git_hash) | |
116 | |
117 # Is this revision included in the original revision list? | |
118 if svn_revision in full_revision_list: | |
119 print("Found port of r%s -> r%s (already included): %s" | |
120 % (revision, svn_revision, revision_title)) | |
121 else: | |
122 print("Found port of r%s -> r%s: %s" | |
123 % (revision, svn_revision, revision_title)) | |
124 port_revision_list.append(svn_revision) | |
125 | |
126 # Do we find any port? | |
127 if len(port_revision_list) > 0: | |
128 if self.Confirm("Automatically add corresponding ports (%s)?" | |
129 % ", ".join(port_revision_list)): | |
130 #: 'y': Add ports to revision list. | |
131 full_revision_list.extend(port_revision_list) | |
132 self.Persist("full_revision_list", ",".join(full_revision_list)) | |
133 | |
134 | |
135 class FindGitRevisions(Step): | |
136 MESSAGE = "Find the git revisions associated with the patches." | |
137 | |
138 def RunStep(self): | |
139 self.RestoreIfUnset("full_revision_list") | |
140 self.RestoreIfUnset("merge_to_branch") | |
141 full_revision_list = self._state["full_revision_list"].split(",") | |
142 patch_commit_hashes = [] | |
143 for revision in full_revision_list: | |
144 next_hash = self.Git("svn find-rev \"r%s\" svn/bleeding_edge" % revision) | |
145 if not next_hash: | |
146 self.Die("Cannot determine git hash for r%s" % revision) | |
147 patch_commit_hashes.append(next_hash) | |
148 | |
149 # Stringify: [123, 234] -> "r123, r234" | |
150 revision_list = ", ".join(map(lambda s: "r%s" % s, full_revision_list)) | |
151 | |
152 if not revision_list: | |
153 self.Die("Revision list is empty.") | |
154 | |
155 if self._options.revert: | |
156 if not self._options.revert_bleeding_edge: | |
157 new_commit_msg = ("Rollback of %s in %s branch." | |
158 % (revision_list, self._state["merge_to_branch"])) | |
159 else: | |
160 new_commit_msg = "Revert %s ." % revision_list | |
161 else: | |
162 new_commit_msg = ("Merged %s into %s branch." | |
163 % (revision_list, self._state["merge_to_branch"])) | |
164 new_commit_msg += "\n\n" | |
165 | |
166 for commit_hash in patch_commit_hashes: | |
167 patch_merge_desc = self.Git("log -1 --format=%%s %s" % commit_hash) | |
168 new_commit_msg += "%s\n\n" % patch_merge_desc.strip() | |
169 | |
170 bugs = [] | |
171 for commit_hash in patch_commit_hashes: | |
172 msg = self.Git("log -1 %s" % commit_hash) | |
173 for bug in re.findall(r"^[ \t]*BUG[ \t]*=[ \t]*(.*?)[ \t]*$", msg, | |
174 re.M): | |
175 bugs.extend(map(lambda s: s.strip(), bug.split(","))) | |
176 bug_aggregate = ",".join(sorted(bugs)) | |
177 if bug_aggregate: | |
178 new_commit_msg += "BUG=%s\nLOG=N\n" % bug_aggregate | |
179 TextToFile(new_commit_msg, self.Config(COMMITMSG_FILE)) | |
180 self.Persist("new_commit_msg", new_commit_msg) | |
181 self.Persist("revision_list", revision_list) | |
182 self._state["patch_commit_hashes"] = patch_commit_hashes | |
183 self.Persist("patch_commit_hashes_list", " ".join(patch_commit_hashes)) | |
184 | |
185 | |
186 class ApplyPatches(Step): | |
187 MESSAGE = "Apply patches for selected revisions." | |
188 | |
189 def RunStep(self): | |
190 self.RestoreIfUnset("merge_to_branch") | |
191 self.RestoreIfUnset("patch_commit_hashes_list") | |
192 patch_commit_hashes = self._state.get("patch_commit_hashes") | |
193 if not patch_commit_hashes: | |
194 patch_commit_hashes = ( | |
195 self._state.get("patch_commit_hashes_list").strip().split(" ")) | |
196 if not patch_commit_hashes and not options.patch: | |
197 self.Die("Variable patch_commit_hashes could not be restored.") | |
198 for commit_hash in patch_commit_hashes: | |
199 print("Applying patch for %s to %s..." | |
200 % (commit_hash, self._state["merge_to_branch"])) | |
201 patch = self.Git("log -1 -p %s" % commit_hash) | |
202 TextToFile(patch, self.Config(TEMPORARY_PATCH_FILE)) | |
203 self.ApplyPatch(self.Config(TEMPORARY_PATCH_FILE), self._options.revert) | |
204 if self._options.patch: | |
205 self.ApplyPatch(self._options.patch, self._options.revert) | |
206 | |
207 | |
208 class PrepareVersion(Step): | |
209 MESSAGE = "Prepare version file." | |
210 | |
211 def RunStep(self): | |
212 if self._options.revert_bleeding_edge: | |
213 return | |
214 # These version numbers are used again for creating the tag | |
215 self.ReadAndPersistVersion() | |
216 | |
217 | |
218 class IncrementVersion(Step): | |
219 MESSAGE = "Increment version number." | |
220 | |
221 def RunStep(self): | |
222 if self._options.revert_bleeding_edge: | |
223 return | |
224 self.RestoreIfUnset("patch") | |
225 new_patch = str(int(self._state["patch"]) + 1) | |
226 if self.Confirm("Automatically increment PATCH_LEVEL? (Saying 'n' will " | |
227 "fire up your EDITOR on %s so you can make arbitrary " | |
228 "changes. When you're done, save the file and exit your " | |
229 "EDITOR.)" % self.Config(VERSION_FILE)): | |
230 text = FileToText(self.Config(VERSION_FILE)) | |
231 text = MSub(r"(?<=#define PATCH_LEVEL)(?P<space>\s+)\d*$", | |
232 r"\g<space>%s" % new_patch, | |
233 text) | |
234 TextToFile(text, self.Config(VERSION_FILE)) | |
235 else: | |
236 self.Editor(self.Config(VERSION_FILE)) | |
237 self.ReadAndPersistVersion("new_") | |
238 | |
239 | |
240 class CommitLocal(Step): | |
241 MESSAGE = "Commit to local branch." | |
242 | |
243 def RunStep(self): | |
244 if self.Git("commit -a -F \"%s\"" % self.Config(COMMITMSG_FILE)) is None: | |
245 self.Die("'git commit -a' failed.") | |
246 | |
247 | |
248 class CommitRepository(Step): | |
249 MESSAGE = "Commit to the repository." | |
250 | |
251 def RunStep(self): | |
252 self.RestoreIfUnset("merge_to_branch") | |
253 if self.Git("checkout %s" % self.Config(BRANCHNAME)) is None: | |
254 self.Die("Cannot ensure that the current branch is %s" | |
255 % self.Config(BRANCHNAME)) | |
256 self.WaitForLGTM() | |
257 if self.Git("cl presubmit", "PRESUBMIT_TREE_CHECK=\"skip\"") is None: | |
258 self.Die("Presubmit failed.") | |
259 | |
260 if self.Git("cl dcommit -f --bypass-hooks", | |
261 retry_on=lambda x: x is None) is None: | |
262 self.Die("Failed to commit to %s" % self._status["merge_to_branch"]) | |
263 | |
264 | |
265 class PrepareSVN(Step): | |
266 MESSAGE = "Determine svn commit revision." | |
267 | |
268 def RunStep(self): | |
269 if self._options.revert_bleeding_edge: | |
270 return | |
271 self.RestoreIfUnset("new_commit_msg") | |
272 self.RestoreIfUnset("merge_to_branch") | |
273 if self.Git("svn fetch") is None: | |
274 self.Die("'git svn fetch' failed.") | |
275 args = ("log -1 --format=%%H --grep=\"%s\" svn/%s" | |
276 % (self._state["new_commit_msg"], self._state["merge_to_branch"])) | |
277 commit_hash = self.Git(args).strip() | |
278 if not commit_hash: | |
279 self.Die("Unable to map git commit to svn revision.") | |
280 svn_revision = self.Git("svn find-rev %s" % commit_hash).strip() | |
281 print "subversion revision number is r%s" % svn_revision | |
282 self.Persist("svn_revision", svn_revision) | |
283 | |
284 | |
285 class TagRevision(Step): | |
286 MESSAGE = "Create the tag." | |
287 | |
288 def RunStep(self): | |
289 if self._options.revert_bleeding_edge: | |
290 return | |
291 self.RestoreVersionIfUnset("new_") | |
292 self.RestoreIfUnset("svn_revision") | |
293 self.RestoreIfUnset("merge_to_branch") | |
294 ver = "%s.%s.%s.%s" % (self._state["new_major"], | |
295 self._state["new_minor"], | |
296 self._state["new_build"], | |
297 self._state["new_patch"]) | |
298 print "Creating tag svn/tags/%s" % ver | |
299 if self._state["merge_to_branch"] == "trunk": | |
300 to_url = "trunk" | |
301 else: | |
302 to_url = "branches/%s" % self._state["merge_to_branch"] | |
303 self.SVN("copy -r %s https://v8.googlecode.com/svn/%s " | |
304 "https://v8.googlecode.com/svn/tags/%s -m " | |
305 "\"Tagging version %s\"" | |
306 % (self._state["svn_revision"], to_url, ver, ver)) | |
307 self.Persist("to_url", to_url) | |
308 | |
309 | |
310 class CleanUp(Step): | |
311 MESSAGE = "Cleanup." | |
312 | |
313 def RunStep(self): | |
314 self.RestoreIfUnset("svn_revision") | |
315 self.RestoreIfUnset("to_url") | |
316 self.RestoreIfUnset("revision_list") | |
317 self.RestoreVersionIfUnset("new_") | |
318 ver = "%s.%s.%s.%s" % (self._state["new_major"], | |
319 self._state["new_minor"], | |
320 self._state["new_build"], | |
321 self._state["new_patch"]) | |
322 self.CommonCleanup() | |
323 if not self._options.revert_bleeding_edge: | |
324 print "*** SUMMARY ***" | |
325 print "version: %s" % ver | |
326 print "branch: %s" % self._state["to_url"] | |
327 print "svn revision: %s" % self._state["svn_revision"] | |
328 if self._state["revision_list"]: | |
329 print "patches: %s" % self._state["revision_list"] | |
330 | |
331 | |
332 def RunMergeToBranch(config, | |
333 options, | |
334 side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER): | |
335 step_classes = [ | |
336 Preparation, | |
337 CreateBranch, | |
338 SearchArchitecturePorts, | |
339 FindGitRevisions, | |
340 ApplyPatches, | |
341 PrepareVersion, | |
342 IncrementVersion, | |
343 CommitLocal, | |
344 UploadStep, | |
345 CommitRepository, | |
346 PrepareSVN, | |
347 TagRevision, | |
348 CleanUp, | |
349 ] | |
350 | |
351 RunScript(step_classes, config, options, side_effect_handler) | |
352 | |
353 | |
354 def BuildOptions(): | |
355 result = optparse.OptionParser() | |
356 result.add_option("-f", | |
357 help="Delete sentinel file.", | |
358 default=False, action="store_true") | |
359 result.add_option("-m", "--message", | |
360 help="Specify a commit message for the patch.") | |
361 result.add_option("-r", "--revert", | |
362 help="Revert specified patches.", | |
363 default=False, action="store_true") | |
364 result.add_option("-R", "--revert-bleeding-edge", | |
365 help="Revert specified patches from bleeding edge.", | |
366 default=False, action="store_true") | |
367 result.add_option("-p", "--patch", dest="p", | |
368 help="Specify a patch file to apply as part of the merge.") | |
369 result.add_option("-s", "--step", dest="s", | |
370 help="Specify the step where to start work. Default: 0.", | |
371 default=0, type="int") | |
372 return result | |
373 | |
374 | |
375 def ProcessOptions(options, args): | |
376 revert_from_bleeding_edge = 1 if options.revert_bleeding_edge else 0 | |
377 min_exp_args = 2 - revert_from_bleeding_edge | |
378 if len(args) < min_exp_args: | |
379 if not options.p: | |
380 print "Either a patch file or revision numbers must be specified" | |
381 return False | |
382 if not options.message: | |
383 print "You must specify a merge comment if no patches are specified" | |
384 return False | |
385 if options.s < 0: | |
386 print "Bad step number %d" % options.s | |
387 return False | |
388 return True | |
389 | |
390 | |
391 def Main(): | |
392 parser = BuildOptions() | |
393 (options, args) = parser.parse_args() | |
394 if not ProcessOptions(options, args): | |
395 parser.print_help() | |
396 return 1 | |
397 RunMergeToBranch(CONFIG, MergeToBranchOptions(options)) | |
398 | |
399 if __name__ == "__main__": | |
400 sys.exit(Main()) | |
OLD | NEW |