OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright 2013 the V8 project authors. All rights reserved. | 2 # Copyright 2013 the V8 project authors. All rights reserved. |
3 # Redistribution and use in source and binary forms, with or without | 3 # Redistribution and use in source and binary forms, with or without |
4 # modification, are permitted provided that the following conditions are | 4 # modification, are permitted provided that the following conditions are |
5 # met: | 5 # met: |
6 # | 6 # |
7 # * Redistributions of source code must retain the above copyright | 7 # * Redistributions of source code must retain the above copyright |
8 # notice, this list of conditions and the following disclaimer. | 8 # notice, this list of conditions and the following disclaimer. |
9 # * Redistributions in binary form must reproduce the above | 9 # * Redistributions in binary form must reproduce the above |
10 # copyright notice, this list of conditions and the following | 10 # copyright notice, this list of conditions and the following |
(...skipping 254 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
265 | 265 |
266 def Fetch(self): | 266 def Fetch(self): |
267 raise NotImplementedError() | 267 raise NotImplementedError() |
268 | 268 |
269 def GetTags(self): | 269 def GetTags(self): |
270 raise NotImplementedError() | 270 raise NotImplementedError() |
271 | 271 |
272 def GetBranches(self): | 272 def GetBranches(self): |
273 raise NotImplementedError() | 273 raise NotImplementedError() |
274 | 274 |
275 def GitSvn(self, hsh, branch=""): | |
276 raise NotImplementedError() | |
277 | |
278 def SvnGit(self, rev, branch=""): | |
279 raise NotImplementedError() | |
280 | |
281 def MasterBranch(self): | 275 def MasterBranch(self): |
282 raise NotImplementedError() | 276 raise NotImplementedError() |
283 | 277 |
284 def CandidateBranch(self): | 278 def CandidateBranch(self): |
285 raise NotImplementedError() | 279 raise NotImplementedError() |
286 | 280 |
287 def RemoteMasterBranch(self): | 281 def RemoteMasterBranch(self): |
288 raise NotImplementedError() | 282 raise NotImplementedError() |
289 | 283 |
290 def RemoteCandidateBranch(self): | 284 def RemoteCandidateBranch(self): |
291 raise NotImplementedError() | 285 raise NotImplementedError() |
292 | 286 |
293 def RemoteBranch(self, name): | 287 def RemoteBranch(self, name): |
294 raise NotImplementedError() | 288 raise NotImplementedError() |
295 | 289 |
296 def Land(self): | 290 def Land(self): |
297 raise NotImplementedError() | 291 raise NotImplementedError() |
298 | 292 |
299 def CLLand(self): | 293 def CLLand(self): |
300 raise NotImplementedError() | 294 raise NotImplementedError() |
301 | 295 |
302 # TODO(machenbach): There is some svn knowledge in this interface. In svn, | |
303 # tag and commit are different remote commands, while in git we would commit | |
304 # and tag locally and then push/land in one unique step. | |
305 def Tag(self, tag, remote, message): | 296 def Tag(self, tag, remote, message): |
306 """Sets a tag for the current commit. | 297 """Sets a tag for the current commit. |
307 | 298 |
308 Assumptions: The commit already landed and the commit message is unique. | 299 Assumptions: The commit already landed and the commit message is unique. |
309 """ | 300 """ |
310 raise NotImplementedError() | 301 raise NotImplementedError() |
311 | 302 |
312 | 303 |
313 class GitSvnInterface(VCInterface): | 304 class GitInterface(VCInterface): |
314 def Pull(self): | |
315 self.step.GitSVNRebase() | |
316 | |
317 def Fetch(self): | |
318 self.step.GitSVNFetch() | |
319 | |
320 def GetTags(self): | |
321 # Get remote tags. | |
322 tags = filter(lambda s: re.match(r"^svn/tags/[\d+\.]+$", s), | |
323 self.step.GitRemotes()) | |
324 | |
325 # Remove 'svn/tags/' prefix. | |
326 return map(lambda s: s[9:], tags) | |
327 | |
328 def GetBranches(self): | |
329 # Get relevant remote branches, e.g. "svn/3.25". | |
330 branches = filter(lambda s: re.match(r"^svn/\d+\.\d+$", s), | |
331 self.step.GitRemotes()) | |
332 # Remove 'svn/' prefix. | |
333 return map(lambda s: s[4:], branches) | |
334 | |
335 def GitSvn(self, hsh, branch=""): | |
336 return self.step.GitSVNFindSVNRev(hsh, branch) | |
337 | |
338 def SvnGit(self, rev, branch=""): | |
339 return self.step.GitSVNFindGitHash(rev, branch) | |
340 | |
341 def MasterBranch(self): | |
342 return "bleeding_edge" | |
343 | |
344 def CandidateBranch(self): | |
345 return "trunk" | |
346 | |
347 def RemoteMasterBranch(self): | |
348 return "svn/bleeding_edge" | |
349 | |
350 def RemoteCandidateBranch(self): | |
351 return "svn/trunk" | |
352 | |
353 def RemoteBranch(self, name): | |
354 return "svn/%s" % name | |
355 | |
356 def Land(self): | |
357 self.step.GitSVNDCommit() | |
358 | |
359 def CLLand(self): | |
360 self.step.GitDCommit() | |
361 | |
362 def Tag(self, tag, remote, _): | |
363 self.step.GitSVNFetch() | |
364 self.step.Git("rebase %s" % remote) | |
365 self.step.GitSVNTag(tag) | |
366 | |
367 | |
368 class GitTagsOnlyMixin(VCInterface): | |
369 def Pull(self): | 305 def Pull(self): |
370 self.step.GitPull() | 306 self.step.GitPull() |
371 | 307 |
372 def Fetch(self): | 308 def Fetch(self): |
373 self.step.Git("fetch") | 309 self.step.Git("fetch") |
374 self.step.GitSVNFetch() | |
375 | 310 |
376 def GetTags(self): | 311 def GetTags(self): |
377 return self.step.Git("tag").strip().splitlines() | 312 return self.step.Git("tag").strip().splitlines() |
378 | 313 |
379 def GetBranches(self): | 314 def GetBranches(self): |
380 # Get relevant remote branches, e.g. "branch-heads/3.25". | 315 # Get relevant remote branches, e.g. "branch-heads/3.25". |
381 branches = filter( | 316 branches = filter( |
382 lambda s: re.match(r"^branch\-heads/\d+\.\d+$", s), | 317 lambda s: re.match(r"^branch\-heads/\d+\.\d+$", s), |
383 self.step.GitRemotes()) | 318 self.step.GitRemotes()) |
384 # Remove 'branch-heads/' prefix. | 319 # Remove 'branch-heads/' prefix. |
385 return map(lambda s: s[13:], branches) | 320 return map(lambda s: s[13:], branches) |
386 | 321 |
387 def MasterBranch(self): | 322 def MasterBranch(self): |
388 return "master" | 323 return "master" |
389 | 324 |
390 def CandidateBranch(self): | 325 def CandidateBranch(self): |
391 return "candidates" | 326 return "candidates" |
392 | 327 |
393 def RemoteMasterBranch(self): | 328 def RemoteMasterBranch(self): |
394 return "origin/master" | 329 return "origin/master" |
395 | 330 |
396 def RemoteCandidateBranch(self): | 331 def RemoteCandidateBranch(self): |
397 return "origin/candidates" | 332 return "origin/candidates" |
398 | 333 |
399 def RemoteBranch(self, name): | 334 def RemoteBranch(self, name): |
400 if name in ["candidates", "master"]: | 335 if name in ["candidates", "master"]: |
401 return "origin/%s" % name | 336 return "origin/%s" % name |
402 return "branch-heads/%s" % name | 337 return "branch-heads/%s" % name |
403 | 338 |
404 def PushRef(self, ref): | |
405 self.step.Git("push origin %s" % ref) | |
406 | |
407 def Tag(self, tag, remote, message): | 339 def Tag(self, tag, remote, message): |
408 # Wait for the commit to appear. Assumes unique commit message titles (this | 340 # Wait for the commit to appear. Assumes unique commit message titles (this |
409 # is the case for all automated merge and push commits - also no title is | 341 # is the case for all automated merge and push commits - also no title is |
410 # the prefix of another title). | 342 # the prefix of another title). |
411 commit = None | 343 commit = None |
412 for wait_interval in [3, 7, 15, 35, 45, 60]: | 344 for wait_interval in [3, 7, 15, 35, 45, 60]: |
413 self.step.Git("fetch") | 345 self.step.Git("fetch") |
414 commit = self.step.GitLog(n=1, format="%H", grep=message, branch=remote) | 346 commit = self.step.GitLog(n=1, format="%H", grep=message, branch=remote) |
415 if commit: | 347 if commit: |
416 break | 348 break |
417 print("The commit has not replicated to git. Waiting for %s seconds." % | 349 print("The commit has not replicated to git. Waiting for %s seconds." % |
418 wait_interval) | 350 wait_interval) |
419 self.step._side_effect_handler.Sleep(wait_interval) | 351 self.step._side_effect_handler.Sleep(wait_interval) |
420 else: | 352 else: |
421 self.step.Die("Couldn't determine commit for setting the tag. Maybe the " | 353 self.step.Die("Couldn't determine commit for setting the tag. Maybe the " |
422 "git updater is lagging behind?") | 354 "git updater is lagging behind?") |
423 | 355 |
424 self.step.Git("tag %s %s" % (tag, commit)) | 356 self.step.Git("tag %s %s" % (tag, commit)) |
425 self.PushRef(tag) | 357 self.step.Git("push origin %s" % tag) |
426 | |
427 | |
428 class GitReadSvnWriteInterface(GitTagsOnlyMixin, GitSvnInterface): | |
429 pass | |
430 | |
431 | |
432 class GitInterface(GitTagsOnlyMixin): | |
433 def Fetch(self): | |
434 self.step.Git("fetch") | |
435 | |
436 def GitSvn(self, hsh, branch=""): | |
437 return "" | |
438 | |
439 def SvnGit(self, rev, branch=""): | |
440 raise NotImplementedError() | |
441 | 358 |
442 def Land(self): | 359 def Land(self): |
443 # FIXME(machenbach): This will not work with checkouts from bot_update | |
444 # after flag day because it will push to the cache. Investigate if it | |
445 # will work with "cl land". | |
446 self.step.Git("push origin") | 360 self.step.Git("push origin") |
447 | 361 |
448 def CLLand(self): | 362 def CLLand(self): |
449 self.step.GitCLLand() | 363 self.step.GitCLLand() |
450 | 364 |
451 def PushRef(self, ref): | |
452 self.step.Git("push https://chromium.googlesource.com/v8/v8 %s" % ref) | |
453 | |
454 | |
455 VC_INTERFACES = { | |
456 "git_svn": GitSvnInterface, | |
457 "git_read_svn_write": GitReadSvnWriteInterface, | |
458 "git": GitInterface, | |
459 } | |
460 | |
461 | 365 |
462 class Step(GitRecipesMixin): | 366 class Step(GitRecipesMixin): |
463 def __init__(self, text, number, config, state, options, handler): | 367 def __init__(self, text, number, config, state, options, handler): |
464 self._text = text | 368 self._text = text |
465 self._number = number | 369 self._number = number |
466 self._config = config | 370 self._config = config |
467 self._state = state | 371 self._state = state |
468 self._options = options | 372 self._options = options |
469 self._side_effect_handler = handler | 373 self._side_effect_handler = handler |
470 self.vc = VC_INTERFACES[options.vc_interface]() | 374 self.vc = GitInterface() |
471 self.vc.InjectStep(self) | 375 self.vc.InjectStep(self) |
472 | 376 |
473 # The testing configuration might set a different default cwd. | 377 # The testing configuration might set a different default cwd. |
474 self.default_cwd = (self._config.get("DEFAULT_CWD") or | 378 self.default_cwd = (self._config.get("DEFAULT_CWD") or |
475 os.path.join(self._options.work_dir, "v8")) | 379 os.path.join(self._options.work_dir, "v8")) |
476 | 380 |
477 assert self._number >= 0 | 381 assert self._number >= 0 |
478 assert self._config is not None | 382 assert self._config is not None |
479 assert self._state is not None | 383 assert self._state is not None |
480 assert self._side_effect_handler is not None | 384 assert self._side_effect_handler is not None |
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
554 return self.Retry(cmd, None, [5]) | 458 return self.Retry(cmd, None, [5]) |
555 | 459 |
556 def Git(self, args="", prefix="", pipe=True, retry_on=None, cwd=None): | 460 def Git(self, args="", prefix="", pipe=True, retry_on=None, cwd=None): |
557 cmd = lambda: self._side_effect_handler.Command( | 461 cmd = lambda: self._side_effect_handler.Command( |
558 "git", args, prefix, pipe, cwd=cwd or self.default_cwd) | 462 "git", args, prefix, pipe, cwd=cwd or self.default_cwd) |
559 result = self.Retry(cmd, retry_on, [5, 30]) | 463 result = self.Retry(cmd, retry_on, [5, 30]) |
560 if result is None: | 464 if result is None: |
561 raise GitFailedException("'git %s' failed." % args) | 465 raise GitFailedException("'git %s' failed." % args) |
562 return result | 466 return result |
563 | 467 |
564 def SVN(self, args="", prefix="", pipe=True, retry_on=None, cwd=None): | |
565 cmd = lambda: self._side_effect_handler.Command( | |
566 "svn", args, prefix, pipe, cwd=cwd or self.default_cwd) | |
567 return self.Retry(cmd, retry_on, [5, 30]) | |
568 | |
569 def Editor(self, args): | 468 def Editor(self, args): |
570 if self._options.requires_editor: | 469 if self._options.requires_editor: |
571 return self._side_effect_handler.Command( | 470 return self._side_effect_handler.Command( |
572 os.environ["EDITOR"], | 471 os.environ["EDITOR"], |
573 args, | 472 args, |
574 pipe=False, | 473 pipe=False, |
575 cwd=self.default_cwd) | 474 cwd=self.default_cwd) |
576 | 475 |
577 def ReadURL(self, url, params=None, retry_on=None, wait_plan=None): | 476 def ReadURL(self, url, params=None, retry_on=None, wait_plan=None): |
578 wait_plan = wait_plan or [3, 60, 600] | 477 wait_plan = wait_plan or [3, 60, 600] |
(...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
720 line = re.sub("\d+$", self[prefix + "major"], line) | 619 line = re.sub("\d+$", self[prefix + "major"], line) |
721 elif line.startswith("#define MINOR_VERSION"): | 620 elif line.startswith("#define MINOR_VERSION"): |
722 line = re.sub("\d+$", self[prefix + "minor"], line) | 621 line = re.sub("\d+$", self[prefix + "minor"], line) |
723 elif line.startswith("#define BUILD_NUMBER"): | 622 elif line.startswith("#define BUILD_NUMBER"): |
724 line = re.sub("\d+$", self[prefix + "build"], line) | 623 line = re.sub("\d+$", self[prefix + "build"], line) |
725 elif line.startswith("#define PATCH_LEVEL"): | 624 elif line.startswith("#define PATCH_LEVEL"): |
726 line = re.sub("\d+$", self[prefix + "patch"], line) | 625 line = re.sub("\d+$", self[prefix + "patch"], line) |
727 output += "%s\n" % line | 626 output += "%s\n" % line |
728 TextToFile(output, version_file) | 627 TextToFile(output, version_file) |
729 | 628 |
730 def SVNCommit(self, root, commit_message): | |
731 patch = self.GitDiff("HEAD^", "HEAD") | |
732 TextToFile(patch, self._config["PATCH_FILE"]) | |
733 self.Command("svn", "update", cwd=self._options.svn) | |
734 if self.Command("svn", "status", cwd=self._options.svn) != "": | |
735 self.Die("SVN checkout not clean.") | |
736 if not self.Command("patch", "-d %s -p1 -i %s" % | |
737 (root, self._config["PATCH_FILE"]), | |
738 cwd=self._options.svn): | |
739 self.Die("Could not apply patch.") | |
740 for line in self.Command( | |
741 "svn", "status", cwd=self._options.svn).splitlines(): | |
742 # Check for added and removed items. Svn status has seven status columns. | |
743 # The first contains ? for unknown and ! for missing. | |
744 match = re.match(r"^(.)...... (.*)$", line) | |
745 if match and match.group(1) == "?": | |
746 self.Command("svn", "add --force %s" % match.group(2), | |
747 cwd=self._options.svn) | |
748 if match and match.group(1) == "!": | |
749 self.Command("svn", "delete --force %s" % match.group(2), | |
750 cwd=self._options.svn) | |
751 | |
752 self.Command( | |
753 "svn", | |
754 "commit --non-interactive --username=%s --config-dir=%s -m \"%s\"" % | |
755 (self._options.author, self._options.svn_config, commit_message), | |
756 cwd=self._options.svn) | |
757 | |
758 | 629 |
759 class BootstrapStep(Step): | 630 class BootstrapStep(Step): |
760 MESSAGE = "Bootstapping v8 checkout." | 631 MESSAGE = "Bootstapping v8 checkout." |
761 | 632 |
762 def RunStep(self): | 633 def RunStep(self): |
763 if os.path.realpath(self.default_cwd) == os.path.realpath(V8_BASE): | 634 if os.path.realpath(self.default_cwd) == os.path.realpath(V8_BASE): |
764 self.Die("Can't use v8 checkout with calling script as work checkout.") | 635 self.Die("Can't use v8 checkout with calling script as work checkout.") |
765 # Directory containing the working v8 checkout. | 636 # Directory containing the working v8 checkout. |
766 if not os.path.exists(self._options.work_dir): | 637 if not os.path.exists(self._options.work_dir): |
767 os.makedirs(self._options.work_dir) | 638 os.makedirs(self._options.work_dir) |
(...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
866 parser.add_argument("--dry-run", default=False, action="store_true", | 737 parser.add_argument("--dry-run", default=False, action="store_true", |
867 help="Perform only read-only actions.") | 738 help="Perform only read-only actions.") |
868 parser.add_argument("-g", "--googlers-mapping", | 739 parser.add_argument("-g", "--googlers-mapping", |
869 help="Path to the script mapping google accounts.") | 740 help="Path to the script mapping google accounts.") |
870 parser.add_argument("-r", "--reviewer", default="", | 741 parser.add_argument("-r", "--reviewer", default="", |
871 help="The account name to be used for reviews.") | 742 help="The account name to be used for reviews.") |
872 parser.add_argument("--sheriff", default=False, action="store_true", | 743 parser.add_argument("--sheriff", default=False, action="store_true", |
873 help=("Determine current sheriff to review CLs. On " | 744 help=("Determine current sheriff to review CLs. On " |
874 "success, this will overwrite the reviewer " | 745 "success, this will overwrite the reviewer " |
875 "option.")) | 746 "option.")) |
876 parser.add_argument("--svn", | |
877 help=("Optional full svn checkout for the commit." | |
878 "The folder needs to be the svn root.")) | |
879 parser.add_argument("--svn-config", | |
880 help=("Optional folder used as svn --config-dir.")) | |
881 parser.add_argument("-s", "--step", | 747 parser.add_argument("-s", "--step", |
882 help="Specify the step where to start work. Default: 0.", | 748 help="Specify the step where to start work. Default: 0.", |
883 default=0, type=int) | 749 default=0, type=int) |
884 parser.add_argument("--vc-interface", | |
885 help=("Choose VC interface out of git_svn|" | |
886 "git_read_svn_write.")) | |
887 parser.add_argument("--work-dir", | 750 parser.add_argument("--work-dir", |
888 help=("Location where to bootstrap a working v8 " | 751 help=("Location where to bootstrap a working v8 " |
889 "checkout.")) | 752 "checkout.")) |
890 self._PrepareOptions(parser) | 753 self._PrepareOptions(parser) |
891 | 754 |
892 if args is None: # pragma: no cover | 755 if args is None: # pragma: no cover |
893 options = parser.parse_args() | 756 options = parser.parse_args() |
894 else: | 757 else: |
895 options = parser.parse_args(args) | 758 options = parser.parse_args(args) |
896 | 759 |
897 # Process common options. | 760 # Process common options. |
898 if options.step < 0: # pragma: no cover | 761 if options.step < 0: # pragma: no cover |
899 print "Bad step number %d" % options.step | 762 print "Bad step number %d" % options.step |
900 parser.print_help() | 763 parser.print_help() |
901 return None | 764 return None |
902 if options.sheriff and not options.googlers_mapping: # pragma: no cover | 765 if options.sheriff and not options.googlers_mapping: # pragma: no cover |
903 print "To determine the current sheriff, requires the googler mapping" | 766 print "To determine the current sheriff, requires the googler mapping" |
904 parser.print_help() | 767 parser.print_help() |
905 return None | 768 return None |
906 if options.svn and not options.svn_config: | |
907 print "Using pure svn for committing requires also --svn-config" | |
908 parser.print_help() | |
909 return None | |
910 | 769 |
911 # Defaults for options, common to all scripts. | 770 # Defaults for options, common to all scripts. |
912 options.manual = getattr(options, "manual", True) | 771 options.manual = getattr(options, "manual", True) |
913 options.force = getattr(options, "force", False) | 772 options.force = getattr(options, "force", False) |
914 options.bypass_upload_hooks = False | 773 options.bypass_upload_hooks = False |
915 | 774 |
916 # Derived options. | 775 # Derived options. |
917 options.requires_editor = not options.force | 776 options.requires_editor = not options.force |
918 options.wait_for_lgtm = not options.force | 777 options.wait_for_lgtm = not options.force |
919 options.force_readline_defaults = not options.manual | 778 options.force_readline_defaults = not options.manual |
920 options.force_upload = not options.manual | 779 options.force_upload = not options.manual |
921 | 780 |
922 # Process script specific options. | 781 # Process script specific options. |
923 if not self._ProcessOptions(options): | 782 if not self._ProcessOptions(options): |
924 parser.print_help() | 783 parser.print_help() |
925 return None | 784 return None |
926 | 785 |
927 if not options.vc_interface: | |
928 options.vc_interface = "git_read_svn_write" | |
929 if not options.work_dir: | 786 if not options.work_dir: |
930 options.work_dir = "/tmp/v8-release-scripts-work-dir" | 787 options.work_dir = "/tmp/v8-release-scripts-work-dir" |
931 return options | 788 return options |
932 | 789 |
933 def RunSteps(self, step_classes, args=None): | 790 def RunSteps(self, step_classes, args=None): |
934 options = self.MakeOptions(args) | 791 options = self.MakeOptions(args) |
935 if not options: | 792 if not options: |
936 return 1 | 793 return 1 |
937 | 794 |
938 state_file = "%s-state.json" % self._config["PERSISTFILE_BASENAME"] | 795 state_file = "%s-state.json" % self._config["PERSISTFILE_BASENAME"] |
939 if options.step == 0 and os.path.exists(state_file): | 796 if options.step == 0 and os.path.exists(state_file): |
940 os.remove(state_file) | 797 os.remove(state_file) |
941 | 798 |
942 steps = [] | 799 steps = [] |
943 for (number, step_class) in enumerate([BootstrapStep] + step_classes): | 800 for (number, step_class) in enumerate([BootstrapStep] + step_classes): |
944 steps.append(MakeStep(step_class, number, self._state, self._config, | 801 steps.append(MakeStep(step_class, number, self._state, self._config, |
945 options, self._side_effect_handler)) | 802 options, self._side_effect_handler)) |
946 for step in steps[options.step:]: | 803 for step in steps[options.step:]: |
947 if step.Run(): | 804 if step.Run(): |
948 return 0 | 805 return 0 |
949 return 0 | 806 return 0 |
950 | 807 |
951 def Run(self, args=None): | 808 def Run(self, args=None): |
952 return self.RunSteps(self._Steps(), args) | 809 return self.RunSteps(self._Steps(), args) |
OLD | NEW |