Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(106)

Side by Side Diff: tools/push-to-trunk/push_to_trunk.py

Issue 49653002: Add push-to-trunk python port. (Closed) Base URL: https://v8.googlecode.com/svn/branches/bleeding_edge
Patch Set: Fixed read line. Created 7 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright 2013 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 datetime
30 import optparse
31 import sys
32 import tempfile
33
34 from common_includes import *
35
36 TRUNKBRANCH = "TRUNKBRANCH"
37 CHROMIUM = "CHROMIUM"
38 DEPS_FILE = "DEPS_FILE"
39
40 CONFIG = {
41 BRANCHNAME: "prepare-push",
42 TRUNKBRANCH: "trunk-push",
43 PERSISTFILE_BASENAME: "/tmp/v8-push-to-trunk-tempfile",
44 TEMP_BRANCH: "prepare-push-temporary-branch-created-by-script",
45 DOT_GIT_LOCATION: ".git",
46 VERSION_FILE: "src/version.cc",
47 CHANGELOG_FILE: "ChangeLog",
48 CHANGELOG_ENTRY_FILE: "/tmp/v8-push-to-trunk-tempfile-changelog-entry",
49 PATCH_FILE: "/tmp/v8-push-to-trunk-tempfile-patch",
50 COMMITMSG_FILE: "/tmp/v8-push-to-trunk-tempfile-commitmsg",
51 DEPS_FILE: "DEPS",
52 }
53
54
55 class Preparation(Step):
56 def __init__(self):
57 Step.__init__(self, "Preparation.")
58
59 def RunStep(self):
60 self.InitialEnvironmentChecks()
61 self.CommonPrepare()
62 self.DeleteBranch(self.Config(TRUNKBRANCH))
63
64
65 class FreshBranch(Step):
66 def __init__(self):
67 Step.__init__(self, "Create a fresh branch.")
68
69 def RunStep(self):
70 args = "checkout -b %s svn/bleeding_edge" % self.Config(BRANCHNAME)
71 if self.Git(args) is None:
72 self.Die("Creating branch %s failed." % self.Config(BRANCHNAME))
73
74
75 class DetectLastPush(Step):
76 def __init__(self):
77 Step.__init__(self, "Detect commit ID of last push to trunk.")
78
79 def RunStep(self):
80 last_push = (self._options.l or
81 self.Git("log -1 --format=%H ChangeLog").strip())
82 while True:
83 # Print assumed commit, circumventing git's pager.
84 print self.Git("log -1 %s" % last_push)
85 if self.Confirm("Is the commit printed above the last push to trunk?"):
86 break
87 args = "log -1 --format=%H %s^ ChangeLog" % last_push
88 last_push = self.Git(args).strip()
89 self.Persist("last_push", last_push)
90 self._state["last_push"] = last_push
91
92
93 class PrepareChangeLog(Step):
94 def __init__(self):
95 Step.__init__(self, "Prepare raw ChangeLog entry.")
96
97 def RunStep(self):
98 self.RestoreIfUnset("last_push")
99
100 # These version numbers are used again later for the trunk commit.
101 self.ReadAndPersistVersion()
102
103 date = datetime.date.today().strftime("%Y-%m-%d")
104 self.Persist("date", date)
105 output = "%s: Version %s.%s.%s\n\n" % (date,
106 self._state["major"],
107 self._state["minor"],
108 self._state["build"])
109 TextToFile(output, self.Config(CHANGELOG_ENTRY_FILE))
110
111 args = "log %s..HEAD --format=%%H" % self._state["last_push"]
112 commits = self.Git(args).strip()
113 for commit in commits.splitlines():
114 # Get the commit's title line.
115 args = "log -1 %s --format=\"%%w(80,8,8)%%s\"" % commit
116 title = "%s\n" % self.Git(args).rstrip()
117 AppendToFile(title, self.Config(CHANGELOG_ENTRY_FILE))
118
119 # Grep for "BUG=xxxx" lines in the commit message and convert them to
120 # "(issue xxxx)".
121 out = self.Git("log -1 %s --format=\"%%B\"" % commit).splitlines()
122 out = filter(lambda x: re.search(r"^BUG=", x), out)
123 out = filter(lambda x: not re.search(r"BUG=$", x), out)
124 out = filter(lambda x: not re.search(r"BUG=none$", x), out)
125
126 def FormatIssue(text):
127 text = re.sub(r"BUG=v8:(.*)$", r"issue \1", text)
128 text = re.sub(r"BUG=chromium:(.*)$", r"Chromium issue \1", text)
129 text = re.sub(r"BUG=(.*)$", r"Chromium issue \1", text)
130 return " %s\n" % text
131
132 for line in map(FormatIssue, out):
133 AppendToFile(line, self.Config(CHANGELOG_ENTRY_FILE))
134
135 # Append the commit's author for reference.
136 args = "log -1 %s --format=\"%%w(80,8,8)(%%an)\"" % commit
137 author = self.Git(args).rstrip()
138 AppendToFile("%s\n\n" % author, self.Config(CHANGELOG_ENTRY_FILE))
139
140 msg = " Performance and stability improvements on all platforms.\n"
141 AppendToFile(msg, self.Config(CHANGELOG_ENTRY_FILE))
142
143 class EditChangeLog(Step):
144 def __init__(self):
145 Step.__init__(self, "Edit ChangeLog entry.")
146
147 def RunStep(self):
148 print ("Please press <Return> to have your EDITOR open the ChangeLog "
149 "entry, then edit its contents to your liking. When you're done, "
150 "save the file and exit your EDITOR. ")
151 self.ReadLine()
152
153 self.Editor(self.Config(CHANGELOG_ENTRY_FILE))
154 handle, new_changelog = tempfile.mkstemp()
155 os.close(handle)
156
157 # (1) Eliminate tabs, (2) fix too little and (3) too much indentation, and
158 # (4) eliminate trailing whitespace.
159 changelog_entry = FileToText(self.Config(CHANGELOG_ENTRY_FILE)).rstrip()
160 changelog_entry = MSub(r"\t", r" ", changelog_entry)
161 changelog_entry = MSub(r"^ {1,7}([^ ])", r" \1", changelog_entry)
162 changelog_entry = MSub(r"^ {9,80}([^ ])", r" \1", changelog_entry)
163 changelog_entry = MSub(r" +$", r"", changelog_entry)
164
165 if changelog_entry == "":
166 self.Die("Empty ChangeLog entry.")
167
168 with open(new_changelog, "w") as f:
169 f.write(changelog_entry)
170 f.write("\n\n\n") # Explicitly insert two empty lines.
171
172 AppendToFile(FileToText(self.Config(CHANGELOG_FILE)), new_changelog)
173 TextToFile(FileToText(new_changelog), self.Config(CHANGELOG_FILE))
174 os.remove(new_changelog)
175
176
177 class IncrementVersion(Step):
178 def __init__(self):
179 Step.__init__(self, "Increment version number.")
180
181 def RunStep(self):
182 self.RestoreIfUnset("build")
183 new_build = str(int(self._state["build"]) + 1)
184
185 if self.Confirm(("Automatically increment BUILD_NUMBER? (Saying 'n' will "
186 "fire up your EDITOR on %s so you can make arbitrary "
187 "changes. When you're done, save the file and exit your "
188 "EDITOR.)" % self.Config(VERSION_FILE))):
189 text = FileToText(self.Config(VERSION_FILE))
190 text = MSub(r"(?<=#define BUILD_NUMBER)(?P<space>\s+)\d*$",
Jakob Kummerow 2013/11/08 10:43:19 This is another regexp construct I'd like to repla
Michael Achenbach 2013/11/08 13:08:52 Let's do that later, especially because it is a on
191 r"\g<space>%s" % new_build,
192 text)
193 TextToFile(text, self.Config(VERSION_FILE))
194 else:
195 self.Editor(self.Config(VERSION_FILE))
196
197 self.ReadAndPersistVersion("new_")
198
199
200 class CommitLocal(Step):
201 def __init__(self):
202 Step.__init__(self, "Commit to local branch.")
203
204 def RunStep(self):
205 self.RestoreVersionIfUnset("new_")
206 prep_commit_msg = ("Prepare push to trunk. "
207 "Now working on version %s.%s.%s." % (self._state["new_major"],
208 self._state["new_minor"],
209 self._state["new_build"]))
210 self.Persist("prep_commit_msg", prep_commit_msg)
211 if self.Git("commit -a -m \"%s\"" % prep_commit_msg) is None:
212 self.Die("'git commit -a' failed.")
213
214
215 class CommitRepository(Step):
216 def __init__(self):
217 Step.__init__(self, "Commit to the repository.")
218
219 def RunStep(self):
220 self.WaitForLGTM()
221 # Re-read the ChangeLog entry (to pick up possible changes).
222 TextToFile(Command("cat %s | awk --posix '{\
223 if ($0 ~ /^[0-9]{4}-[0-9]{2}-[0-9]{2}:/) {\
224 if (in_firstblock == 1) {\
225 exit 0;\
226 } else {\
227 in_firstblock = 1;\
228 }\
229 };\
230 print $0;\
231 }'" % self.Config(CHANGELOG_FILE)), self.Config(CHANGELOG_ENTRY_FILE))
232
233 if self.Git("cl dcommit", "PRESUBMIT_TREE_CHECK=\"skip\"") is None:
234 self.Die("'git cl dcommit' failed, please try again.")
235
236
237 class StragglerCommits(Step):
238 def __init__(self):
239 Step.__init__(self, "Fetch straggler commits that sneaked in since this "
240 "script was started.")
241
242 def RunStep(self):
243 if self.Git("svn fetch") is None:
244 self.Die("'git svn fetch' failed.")
245 self.Git("checkout svn/bleeding_edge")
246 self.RestoreIfUnset("prep_commit_msg")
247 args = "log -1 --format=%%H --grep=\"%s\"" % self._state["prep_commit_msg"]
248 prepare_commit_hash = self.Git(args).strip()
249 self.Persist("prepare_commit_hash", prepare_commit_hash)
250
251
252 class SquashCommits(Step):
253 def __init__(self):
254 Step.__init__(self, "Squash commits into one.")
255
256 def RunStep(self):
257 # Instead of relying on "git rebase -i", we'll just create a diff, because
258 # that's easier to automate.
259 self.RestoreIfUnset("prepare_commit_hash")
260 args = "diff svn/trunk %s" % self._state["prepare_commit_hash"]
261 TextToFile(self.Git(args), self.Config(PATCH_FILE))
262
263 # Convert the ChangeLog entry to commit message format:
264 # - remove date
265 # - remove indentation
266 # - merge paragraphs into single long lines, keeping empty lines between
267 # them.
268 self.RestoreIfUnset("date")
269 changelog_entry = FileToText(self.Config(CHANGELOG_ENTRY_FILE))
270
271 # TODO(machenbach): This could create a problem if the changelog contained
272 # any quotation marks.
273 text = Command("echo \"%s\" \
274 | sed -e \"s/^%s: //\" \
275 | sed -e 's/^ *//' \
276 | awk '{ \
277 if (need_space == 1) {\
278 printf(\" \");\
279 };\
280 printf(\"%%s\", $0);\
281 if ($0 ~ /^$/) {\
282 printf(\"\\n\\n\");\
283 need_space = 0;\
284 } else {\
285 need_space = 1;\
286 }\
287 }'" % (changelog_entry, self._state["date"]))
288
289 if not text:
290 self.Die("Commit message editing failed.")
291 TextToFile(text, self.Config(COMMITMSG_FILE))
292 os.remove(self.Config(CHANGELOG_ENTRY_FILE))
293
294
295 class NewBranch(Step):
296 def __init__(self):
297 Step.__init__(self, "Create a new branch from trunk.")
298
299 def RunStep(self):
300 if self.Git("checkout -b %s svn/trunk" % self.Config(TRUNKBRANCH)) is None:
301 self.Die("Checking out a new branch '%s' failed." %
302 self.Config(TRUNKBRANCH))
303
304
305 class ApplyChanges(Step):
306 def __init__(self):
307 Step.__init__(self, "Apply squashed changes.")
308
309 def RunStep(self):
310 self.ApplyPatch(self.Config(PATCH_FILE))
311 Command("rm", "-f %s*" % self.Config(PATCH_FILE))
312
313
314 class SetVersion(Step):
315 def __init__(self):
316 Step.__init__(self, "Set correct version for trunk.")
317
318 def RunStep(self):
319 self.RestoreVersionIfUnset()
320 output = ""
321 for line in FileToText(self.Config(VERSION_FILE)):
322 if line.startswith("#define MAJOR_VERSION"):
323 line = line.replace("\d*$", self._state["major"])
Jakob Kummerow 2013/11/08 10:43:19 string.replace doesn't handle regexp patterns. You
Michael Achenbach 2013/11/08 13:08:52 Done.
324 elif line.startswith("#define MINOR_VERSION"):
325 line = line.replace("\d*$", self._state["minor"])
326 elif line.startswith("#define BUILD_NUMBER"):
327 line = line.replace("\d*$", self._state["build"])
328 elif line.startswith("#define PATCH_LEVEL"):
329 line = line.replace("\d*$", "0")
330 elif line.startswith("#define IS_CANDIDATE_VERSION"):
331 line = line.replace("\d*$", "0")
332 output += line
333 TextToFile(output, self.Config(VERSION_FILE))
334
335
336 class CommitTrunk(Step):
337 def __init__(self):
338 Step.__init__(self, "Commit to local trunk branch.")
339
340 def RunStep(self):
341 self.Git("add \"%s\"" % self.Config(VERSION_FILE))
342 if self.Git("commit -F \"%s\"" % self.Config(COMMITMSG_FILE)) is None:
343 self.Die("'git commit' failed.")
344 Command("rm", "-f %s*" % self.Config(COMMITMSG_FILE))
345
346
347 class SanityCheck(Step):
348 def __init__(self):
349 Step.__init__(self, "Sanity check.")
350
351 def RunStep(self):
352 if not self.Confirm("Please check if your local checkout is sane: Inspect "
353 "%s, compile, run tests. Do you want to commit this new trunk "
354 "revision to the repository?" % self.Config(VERSION_FILE)):
355 self.Die("Execution canceled.")
356
357
358 class CommitSVN(Step):
359 def __init__(self):
360 Step.__init__(self, "Commit to SVN.")
361
362 def RunStep(self):
363 result = self.Git("svn dcommit 2>&1")
364 if not result:
365 self.Die("'git svn dcommit' failed.")
366 result = filter(lambda x: re.search(r"^Committed r[0-9]+", x),
367 result.splitlines())
368 if len(result) > 0:
369 trunk_revision = re.sub(r"^Committed r([0-9]+)", r"\1", result[0])
370
371 # Sometimes grepping for the revision fails. No idea why. If you figure
372 # out why it is flaky, please do fix it properly.
373 if not trunk_revision:
374 print("Sorry, grepping for the SVN revision failed. Please look for it "
375 "in the last command's output above and provide it manually (just "
376 "the number, without the leading \"r\").")
377 while not trunk_revision:
378 print "> ",
379 trunk_revision = self.ReadLine()
380 self.Persist("trunk_revision", trunk_revision)
381
382
383 class TagRevision(Step):
384 def __init__(self):
385 Step.__init__(self, "Tag the new revision.")
386
387 def RunStep(self):
388 self.RestoreVersionIfUnset()
389 ver = "%s.%s.%s" % (self._state["major"],
390 self._state["minor"],
391 self._state["build"])
392 if self.Git("svn tag %s -m \"Tagging version %s\"" % (ver, ver)) is None:
393 self.Die("'git svn tag' failed.")
394
395
396 class CheckChromium(Step):
397 def __init__(self):
398 Step.__init__(self, "Ask for chromium checkout.")
399
400 def Run(self):
401 chrome_path = self._options.c
402 if not chrome_path:
403 print ">>> (asking for Chromium checkout)"
Jakob Kummerow 2013/11/08 10:43:19 My point was that this line is pointless. To be fu
Michael Achenbach 2013/11/08 13:08:52 Done.
404 print ("Do you have a \"NewGit\" Chromium checkout and want "
405 "this script to automate creation of the roll CL? If yes, enter the "
406 "path to (and including) the \"src\" directory here, otherwise just "
407 "press <Return>: "),
408 chrome_path = self.ReadLine()
409 self.Persist("chrome_path", chrome_path)
410
411
412 class SwitchChromium(Step):
413 def __init__(self):
414 Step.__init__(self, "Switch to Chromium checkout.", requires="chrome_path")
415
416 def RunStep(self):
417 v8_path = os.getcwd()
418 self.Persist("v8_path", v8_path)
419 os.chdir(self._state["chrome_path"])
420 self.InitialEnvironmentChecks()
421 # Check for a clean workdir.
422 if self.Git("status -s -uno").strip() != "":
423 self.Die("Workspace is not clean. Please commit or undo your changes.")
424 # Assert that the DEPS file is there.
425 if not os.path.exists(self.Config(DEPS_FILE)):
426 self.Die("DEPS file not present.")
427
428
429 class UpdateChromiumCheckout(Step):
430 def __init__(self):
431 Step.__init__(self, "Update the checkout and create a new branch.",
432 requires="chrome_path")
433
434 def RunStep(self):
435 if self.Git("checkout master") is None:
436 self.Die("'git checkout master' failed.")
437 if self.Git("pull") is None:
438 self.Die("'git pull' failed, please try again.")
439
440 self.RestoreIfUnset("trunk_revision")
441 args = "checkout -b v8-roll-%s" % self._state["trunk_revision"]
442 if self.Git(args) is None:
443 self.Die("Failed to checkout a new branch.")
444
445
446 class UploadCL(Step):
447 def __init__(self):
448 Step.__init__(self, "Create and upload CL.", requires="chrome_path")
449
450 def RunStep(self):
451 # Patch DEPS file.
452 self.RestoreIfUnset("trunk_revision")
453 deps = FileToText(self.Config(DEPS_FILE))
454 deps = re.sub("(?<=\"v8_revision\": )([0-9]+)",
455 self._state["trunk_revision"],
456 deps)
457 TextToFile(deps, self.Config(DEPS_FILE))
458
459 self.RestoreVersionIfUnset()
460 ver = "%s.%s.%s" % (self._state["major"],
461 self._state["minor"],
462 self._state["build"])
463 print "Please enter the email address of a reviewer for the roll CL: ",
464 rev = self.ReadLine()
465 args = "commit -am \"Update V8 to version %s.\n\nTBR=%s\"" % (ver, rev)
466 if self.Git(args) is None:
467 self.Die("'git commit' failed.")
468 if self.Git("cl upload --send-mail", pipe=False) is None:
469 self.Die("'git cl upload' failed, please try again.")
470 print "CL uploaded."
471
472
473 class SwitchV8(Step):
474 def __init__(self):
475 Step.__init__(self, "Returning to V8 checkout.", requires="chrome_path")
476
477 def RunStep(self):
478 self.RestoreIfUnset("v8_path")
479 os.chdir(self._state["v8_path"])
480
481
482 class CleanUp(Step):
483 def __init__(self):
484 Step.__init__(self, "Done!")
485
486 def RunStep(self):
487 self.RestoreVersionIfUnset()
488 ver = "%s.%s.%s" % (self._state["major"],
489 self._state["minor"],
490 self._state["build"])
491 self.RestoreIfUnset("trunk_revision")
492 self.RestoreIfUnset("chrome_path")
493
494 if self._state["chrome_path"]:
495 print("Congratulations, you have successfully created the trunk "
496 "revision %s and rolled it into Chromium. Please don't forget to "
497 "update the v8rel spreadsheet:" % ver)
498 else:
499 print("Congratulations, you have successfully created the trunk "
500 "revision %s. Please don't forget to roll this new version into "
501 "Chromium, and to update the v8rel spreadsheet:" % ver)
502 print "%s\ttrunk\t%s" % (ver, self._state["trunk_revision"])
503
504 self.CommonCleanup()
505 if self.Config(TRUNKBRANCH) != self._state["current_branch"]:
506 self.Git("branch -D %s" % self.Config(TRUNKBRANCH))
507
508
509 def RunScript(config,
510 options,
511 side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER):
512 step_classes = [
513 Preparation,
514 FreshBranch,
515 DetectLastPush,
516 PrepareChangeLog,
517 EditChangeLog,
518 IncrementVersion,
519 CommitLocal,
520 UploadStep,
521 CommitRepository,
522 StragglerCommits,
523 SquashCommits,
524 NewBranch,
525 ApplyChanges,
526 SetVersion,
527 CommitTrunk,
528 SanityCheck,
529 CommitSVN,
530 TagRevision,
531 CheckChromium,
532 SwitchChromium,
533 UpdateChromiumCheckout,
534 UploadCL,
535 SwitchV8,
536 CleanUp,
537 ]
538
539 state = {}
540 steps = []
541 number = 0
542
543 for step_class in step_classes:
544 step = step_class()
545 step.SetNumber(number)
546 step.SetConfig(config)
547 step.SetOptions(options)
548 step.SetState(state)
549 step.SetSideEffectHandler(side_effect_handler)
550 steps.append(step)
551 number += 1
552
553 for step in steps[options.s:]:
554 step.Run()
555
556
557 def BuildOptions():
558 result = optparse.OptionParser()
559 result.add_option("-s", "--step", dest="s",
560 help="Specify the step where to start work. Default: 0.",
561 default=0, type="int")
562 result.add_option("-l", "--last-push", dest="l",
563 help=("Manually specify the git commit ID "
564 "of the last push to trunk."))
565 result.add_option("-c", "--chromium", dest="c",
566 help=("Specify the path to your Chromium src/ "
567 "directory to automate the V8 roll."))
568 return result
569
570
571 def ProcessOptions(options):
572 if options.s < 0:
573 print "Bad step number %d" % options.s
574 return False
575 return True
576
577
578 def Main():
579 parser = BuildOptions()
580 (options, args) = parser.parse_args()
581 if not ProcessOptions(options):
582 parser.print_help()
583 return 1
584 RunScript(CONFIG, options)
585
586 if __name__ == "__main__":
587 sys.exit(Main())
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698