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 18 matching lines...) Expand all Loading... |
29 import datetime | 29 import datetime |
30 import json | 30 import json |
31 import os | 31 import os |
32 import re | 32 import re |
33 import subprocess | 33 import subprocess |
34 import sys | 34 import sys |
35 import textwrap | 35 import textwrap |
36 import time | 36 import time |
37 import urllib2 | 37 import urllib2 |
38 | 38 |
| 39 from git_recipes import GitRecipesMixin |
| 40 |
39 PERSISTFILE_BASENAME = "PERSISTFILE_BASENAME" | 41 PERSISTFILE_BASENAME = "PERSISTFILE_BASENAME" |
40 TEMP_BRANCH = "TEMP_BRANCH" | 42 TEMP_BRANCH = "TEMP_BRANCH" |
41 BRANCHNAME = "BRANCHNAME" | 43 BRANCHNAME = "BRANCHNAME" |
42 DOT_GIT_LOCATION = "DOT_GIT_LOCATION" | 44 DOT_GIT_LOCATION = "DOT_GIT_LOCATION" |
43 VERSION_FILE = "VERSION_FILE" | 45 VERSION_FILE = "VERSION_FILE" |
44 CHANGELOG_FILE = "CHANGELOG_FILE" | 46 CHANGELOG_FILE = "CHANGELOG_FILE" |
45 CHANGELOG_ENTRY_FILE = "CHANGELOG_ENTRY_FILE" | 47 CHANGELOG_ENTRY_FILE = "CHANGELOG_ENTRY_FILE" |
46 COMMITMSG_FILE = "COMMITMSG_FILE" | 48 COMMITMSG_FILE = "COMMITMSG_FILE" |
47 PATCH_FILE = "PATCH_FILE" | 49 PATCH_FILE = "PATCH_FILE" |
48 | 50 |
(...skipping 176 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
225 | 227 |
226 | 228 |
227 class CommonOptions(object): | 229 class CommonOptions(object): |
228 def __init__(self, options, manual=True): | 230 def __init__(self, options, manual=True): |
229 self.requires_editor = True | 231 self.requires_editor = True |
230 self.wait_for_lgtm = True | 232 self.wait_for_lgtm = True |
231 self.s = options.s | 233 self.s = options.s |
232 self.force_readline_defaults = not manual | 234 self.force_readline_defaults = not manual |
233 self.force_upload = not manual | 235 self.force_upload = not manual |
234 self.manual = manual | 236 self.manual = manual |
235 self.reviewer = getattr(options, 'reviewer', None) | 237 self.reviewer = getattr(options, 'reviewer', "") |
236 self.author = getattr(options, 'a', None) | 238 self.author = getattr(options, 'a', "") |
237 | 239 |
238 | 240 |
239 class Step(object): | 241 class Step(GitRecipesMixin): |
240 def __init__(self, text, requires, number, config, state, options, handler): | 242 def __init__(self, text, requires, number, config, state, options, handler): |
241 self._text = text | 243 self._text = text |
242 self._requires = requires | 244 self._requires = requires |
243 self._number = number | 245 self._number = number |
244 self._config = config | 246 self._config = config |
245 self._state = state | 247 self._state = state |
246 self._options = options | 248 self._options = options |
247 self._side_effect_handler = handler | 249 self._side_effect_handler = handler |
248 assert self._number >= 0 | 250 assert self._number >= 0 |
249 assert self._config is not None | 251 assert self._config is not None |
(...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
356 if not self._options.manual: | 358 if not self._options.manual: |
357 msg = msg or "Only available in manual mode." | 359 msg = msg or "Only available in manual mode." |
358 self.Die(msg) | 360 self.Die(msg) |
359 | 361 |
360 def Confirm(self, msg): | 362 def Confirm(self, msg): |
361 print "%s [Y/n] " % msg, | 363 print "%s [Y/n] " % msg, |
362 answer = self.ReadLine(default="Y") | 364 answer = self.ReadLine(default="Y") |
363 return answer == "" or answer == "Y" or answer == "y" | 365 return answer == "" or answer == "Y" or answer == "y" |
364 | 366 |
365 def DeleteBranch(self, name): | 367 def DeleteBranch(self, name): |
366 git_result = self.Git("branch").strip() | 368 for line in self.GitBranch().splitlines(): |
367 for line in git_result.splitlines(): | |
368 if re.match(r".*\s+%s$" % name, line): | 369 if re.match(r".*\s+%s$" % name, line): |
369 msg = "Branch %s exists, do you want to delete it?" % name | 370 msg = "Branch %s exists, do you want to delete it?" % name |
370 if self.Confirm(msg): | 371 if self.Confirm(msg): |
371 self.Git("branch -D %s" % name) | 372 self.GitDeleteBranch(name) |
372 print "Branch %s deleted." % name | 373 print "Branch %s deleted." % name |
373 else: | 374 else: |
374 msg = "Can't continue. Please delete branch %s and try again." % name | 375 msg = "Can't continue. Please delete branch %s and try again." % name |
375 self.Die(msg) | 376 self.Die(msg) |
376 | 377 |
377 def InitialEnvironmentChecks(self): | 378 def InitialEnvironmentChecks(self): |
378 # Cancel if this is not a git checkout. | 379 # Cancel if this is not a git checkout. |
379 if not os.path.exists(self._config[DOT_GIT_LOCATION]): | 380 if not os.path.exists(self._config[DOT_GIT_LOCATION]): |
380 self.Die("This is not a git checkout, this script won't work for you.") | 381 self.Die("This is not a git checkout, this script won't work for you.") |
381 | 382 |
382 # Cancel if EDITOR is unset or not executable. | 383 # Cancel if EDITOR is unset or not executable. |
383 if (self._options.requires_editor and (not os.environ.get("EDITOR") or | 384 if (self._options.requires_editor and (not os.environ.get("EDITOR") or |
384 Command("which", os.environ["EDITOR"]) is None)): | 385 Command("which", os.environ["EDITOR"]) is None)): |
385 self.Die("Please set your EDITOR environment variable, you'll need it.") | 386 self.Die("Please set your EDITOR environment variable, you'll need it.") |
386 | 387 |
387 def CommonPrepare(self): | 388 def CommonPrepare(self): |
388 # Check for a clean workdir. | 389 # Check for a clean workdir. |
389 if self.Git("status -s -uno").strip() != "": | 390 if not self.GitIsWorkdirClean(): |
390 self.Die("Workspace is not clean. Please commit or undo your changes.") | 391 self.Die("Workspace is not clean. Please commit or undo your changes.") |
391 | 392 |
392 # Persist current branch. | 393 # Persist current branch. |
393 self["current_branch"] = "" | 394 self["current_branch"] = self.GitCurrentBranch() |
394 git_result = self.Git("status -s -b -uno").strip() | |
395 for line in git_result.splitlines(): | |
396 match = re.match(r"^## (.+)", line) | |
397 if match: | |
398 self["current_branch"] = match.group(1) | |
399 break | |
400 | 395 |
401 # Fetch unfetched revisions. | 396 # Fetch unfetched revisions. |
402 self.Git("svn fetch") | 397 self.GitSVNFetch() |
403 | 398 |
404 def PrepareBranch(self): | 399 def PrepareBranch(self): |
405 # Get ahold of a safe temporary branch and check it out. | 400 # Get ahold of a safe temporary branch and check it out. |
406 if self["current_branch"] != self._config[TEMP_BRANCH]: | 401 if self["current_branch"] != self._config[TEMP_BRANCH]: |
407 self.DeleteBranch(self._config[TEMP_BRANCH]) | 402 self.DeleteBranch(self._config[TEMP_BRANCH]) |
408 self.Git("checkout -b %s" % self._config[TEMP_BRANCH]) | 403 self.GitCreateBranch(self._config[TEMP_BRANCH]) |
409 | 404 |
410 # Delete the branch that will be created later if it exists already. | 405 # Delete the branch that will be created later if it exists already. |
411 self.DeleteBranch(self._config[BRANCHNAME]) | 406 self.DeleteBranch(self._config[BRANCHNAME]) |
412 | 407 |
413 def CommonCleanup(self): | 408 def CommonCleanup(self): |
414 self.Git("checkout -f %s" % self["current_branch"]) | 409 self.GitCheckout(self["current_branch"]) |
415 if self._config[TEMP_BRANCH] != self["current_branch"]: | 410 if self._config[TEMP_BRANCH] != self["current_branch"]: |
416 self.Git("branch -D %s" % self._config[TEMP_BRANCH]) | 411 self.GitDeleteBranch(self._config[TEMP_BRANCH]) |
417 if self._config[BRANCHNAME] != self["current_branch"]: | 412 if self._config[BRANCHNAME] != self["current_branch"]: |
418 self.Git("branch -D %s" % self._config[BRANCHNAME]) | 413 self.GitDeleteBranch(self._config[BRANCHNAME]) |
419 | 414 |
420 # Clean up all temporary files. | 415 # Clean up all temporary files. |
421 Command("rm", "-f %s*" % self._config[PERSISTFILE_BASENAME]) | 416 Command("rm", "-f %s*" % self._config[PERSISTFILE_BASENAME]) |
422 | 417 |
423 def ReadAndPersistVersion(self, prefix=""): | 418 def ReadAndPersistVersion(self, prefix=""): |
424 def ReadAndPersist(var_name, def_name): | 419 def ReadAndPersist(var_name, def_name): |
425 match = re.match(r"^#define %s\s+(\d*)" % def_name, line) | 420 match = re.match(r"^#define %s\s+(\d*)" % def_name, line) |
426 if match: | 421 if match: |
427 value = match.group(1) | 422 value = match.group(1) |
428 self["%s%s" % (prefix, var_name)] = value | 423 self["%s%s" % (prefix, var_name)] = value |
(...skipping 24 matching lines...) Expand all Loading... |
453 answer = "" | 448 answer = "" |
454 while answer != "RESOLVED": | 449 while answer != "RESOLVED": |
455 if answer == "ABORT": | 450 if answer == "ABORT": |
456 self.Die("Applying the patch failed.") | 451 self.Die("Applying the patch failed.") |
457 if answer != "": | 452 if answer != "": |
458 print "That was not 'RESOLVED' or 'ABORT'." | 453 print "That was not 'RESOLVED' or 'ABORT'." |
459 print "> ", | 454 print "> ", |
460 answer = self.ReadLine() | 455 answer = self.ReadLine() |
461 | 456 |
462 # Takes a file containing the patch to apply as first argument. | 457 # Takes a file containing the patch to apply as first argument. |
463 def ApplyPatch(self, patch_file, reverse_patch=""): | 458 def ApplyPatch(self, patch_file, revert=False): |
464 args = "apply --index --reject %s \"%s\"" % (reverse_patch, patch_file) | |
465 try: | 459 try: |
466 self.Git(args) | 460 self.GitApplyPatch(patch_file, revert) |
467 except GitFailedException: | 461 except GitFailedException: |
468 self.WaitForResolvingConflicts(patch_file) | 462 self.WaitForResolvingConflicts(patch_file) |
469 | 463 |
470 def FindLastTrunkPush(self): | 464 def FindLastTrunkPush(self, parent_hash=""): |
471 push_pattern = "^Version [[:digit:]]*\.[[:digit:]]*\.[[:digit:]]* (based" | 465 push_pattern = "^Version [[:digit:]]*\.[[:digit:]]*\.[[:digit:]]* (based" |
472 args = "log -1 --format=%%H --grep=\"%s\" svn/trunk" % push_pattern | 466 branch = "" if parent_hash else "svn/trunk" |
473 return self.Git(args).strip() | 467 return self.GitLog(n=1, format="%H", grep=push_pattern, |
| 468 parent_hash=parent_hash, branch=branch) |
474 | 469 |
475 | 470 |
476 class UploadStep(Step): | 471 class UploadStep(Step): |
477 MESSAGE = "Upload for code review." | 472 MESSAGE = "Upload for code review." |
478 | 473 |
479 def RunStep(self): | 474 def RunStep(self): |
480 if self._options.reviewer: | 475 if self._options.reviewer: |
481 print "Using account %s for review." % self._options.reviewer | 476 print "Using account %s for review." % self._options.reviewer |
482 reviewer = self._options.reviewer | 477 reviewer = self._options.reviewer |
483 else: | 478 else: |
484 print "Please enter the email address of a V8 reviewer for your patch: ", | 479 print "Please enter the email address of a V8 reviewer for your patch: ", |
485 self.DieNoManualMode("A reviewer must be specified in forced mode.") | 480 self.DieNoManualMode("A reviewer must be specified in forced mode.") |
486 reviewer = self.ReadLine() | 481 reviewer = self.ReadLine() |
487 author_option = self._options.author | 482 self.GitUpload(reviewer, self._options.author, self._options.force_upload) |
488 author = " --email \"%s\"" % author_option if author_option else "" | |
489 force_flag = " -f" if self._options.force_upload else "" | |
490 args = ("cl upload%s -r \"%s\" --send-mail%s" | |
491 % (author, reviewer, force_flag)) | |
492 # TODO(machenbach): Check output in forced mode. Verify that all required | |
493 # base files were uploaded, if not retry. | |
494 self.Git(args, pipe=False) | |
495 | 483 |
496 | 484 |
497 def MakeStep(step_class=Step, number=0, state=None, config=None, | 485 def MakeStep(step_class=Step, number=0, state=None, config=None, |
498 options=None, side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER): | 486 options=None, side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER): |
499 # Allow to pass in empty dictionaries. | 487 # Allow to pass in empty dictionaries. |
500 state = state if state is not None else {} | 488 state = state if state is not None else {} |
501 config = config if config is not None else {} | 489 config = config if config is not None else {} |
502 | 490 |
503 try: | 491 try: |
504 message = step_class.MESSAGE | 492 message = step_class.MESSAGE |
(...skipping 17 matching lines...) Expand all Loading... |
522 if options.s == 0 and os.path.exists(state_file): | 510 if options.s == 0 and os.path.exists(state_file): |
523 os.remove(state_file) | 511 os.remove(state_file) |
524 state = {} | 512 state = {} |
525 steps = [] | 513 steps = [] |
526 for (number, step_class) in enumerate(step_classes): | 514 for (number, step_class) in enumerate(step_classes): |
527 steps.append(MakeStep(step_class, number, state, config, | 515 steps.append(MakeStep(step_class, number, state, config, |
528 options, side_effect_handler)) | 516 options, side_effect_handler)) |
529 | 517 |
530 for step in steps[options.s:]: | 518 for step in steps[options.s:]: |
531 step.Run() | 519 step.Run() |
OLD | NEW |