OLD | NEW |
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 # Copyright (c) 2009 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2009 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 import optparse | 6 import optparse |
7 import os | 7 import os |
8 import re | 8 import re |
9 import subprocess | 9 import subprocess |
10 import sys | 10 import sys |
11 import webbrowser | 11 import webbrowser |
12 | 12 |
13 import breakpad | 13 import breakpad |
14 | 14 |
15 USAGE = """ | 15 USAGE = """ |
16 WARNING: Please use this tool in an empty directory | 16 WARNING: Please use this tool in an empty directory |
17 (or at least one that you don't mind clobbering.) | 17 (or at least one that you don't mind clobbering.) |
18 | 18 |
19 REQUIRES: SVN 1.5+ | 19 REQUIRES: SVN 1.5+ |
20 NOTE: NO NEED TO CHECKOUT ANYTHING IN ADVANCE OF USING THIS TOOL." | 20 NOTE: NO NEED TO CHECKOUT ANYTHING IN ADVANCE OF USING THIS TOOL." |
21 Valid parameters: | 21 Valid parameters: |
22 | 22 |
23 [Merge from trunk to branch] | 23 [Merge from trunk to branch] |
24 --merge <revision> --branch <branch_num> | 24 --merge <revision> --branch <branch_num> |
25 Example: %(app)s --merge 12345 --branch 187 | 25 Example: %(app)s --merge 12345 --branch 187 |
26 | 26 |
| 27 [Merge from trunk to local copy] |
| 28 --merge <revision> --local |
| 29 Example: %(app)s --merge 12345 --local |
| 30 |
27 [Merge from branch to branch] | 31 [Merge from branch to branch] |
28 --merge <revision> --sbranch <branch_num> --branch <branch_num> | 32 --merge <revision> --sbranch <branch_num> --branch <branch_num> |
29 Example: %(app)s --merge 12345 --sbranch 248 --branch 249 | 33 Example: %(app)s --merge 12345 --sbranch 248 --branch 249 |
30 | 34 |
31 [Revert from trunk] | 35 [Revert from trunk] |
32 --revert <revision> | 36 --revert <revision> |
33 Example: %(app)s --revert 12345 | 37 Example: %(app)s --revert 12345 |
34 | 38 |
35 [Revert from branch] | 39 [Revert from branch] |
36 --revert <revision> --branch <branch_num> | 40 --revert <revision> --branch <branch_num> |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
89 stdout=subprocess.PIPE, | 93 stdout=subprocess.PIPE, |
90 stderr=subprocess.PIPE).stdout.readlines() | 94 stderr=subprocess.PIPE).stdout.readlines() |
91 info = {} | 95 info = {} |
92 for line in svn_info: | 96 for line in svn_info: |
93 match = re.search(r"(.*?):(.*)", line) | 97 match = re.search(r"(.*?):(.*)", line) |
94 if match: | 98 if match: |
95 info[match.group(1).strip()]=match.group(2).strip() | 99 info[match.group(1).strip()]=match.group(2).strip() |
96 | 100 |
97 return info | 101 return info |
98 | 102 |
| 103 def isSVNDirty(): |
| 104 command = 'svn status' |
| 105 svn_status = subprocess.Popen(command, |
| 106 shell=True, |
| 107 stdout=subprocess.PIPE, |
| 108 stderr=subprocess.PIPE).stdout.readlines() |
| 109 for line in svn_status: |
| 110 match = re.search(r"^[^X?]", line) |
| 111 if match: |
| 112 return True |
| 113 |
| 114 return False |
| 115 |
99 def getAuthor(url, revision): | 116 def getAuthor(url, revision): |
100 info = getSVNInfo(url, revision) | 117 info = getSVNInfo(url, revision) |
101 if (info.has_key("Last Changed Author")): | 118 if (info.has_key("Last Changed Author")): |
102 return info["Last Changed Author"] | 119 return info["Last Changed Author"] |
103 return None | 120 return None |
104 | 121 |
105 def isSVNFile(url, revision): | 122 def isSVNFile(url, revision): |
106 info = getSVNInfo(url, revision) | 123 info = getSVNInfo(url, revision) |
107 if (info.has_key("Node Kind")): | 124 if (info.has_key("Node Kind")): |
108 if (info["Node Kind"] == "file"): | 125 if (info["Node Kind"] == "file"): |
109 return True | 126 return True |
110 return False | 127 return False |
111 | 128 |
112 def isSVNDirectory(url, revision): | 129 def isSVNDirectory(url, revision): |
113 info = getSVNInfo(url, revision) | 130 info = getSVNInfo(url, revision) |
114 if (info.has_key("Node Kind")): | 131 if (info.has_key("Node Kind")): |
115 if (info["Node Kind"] == "directory"): | 132 if (info["Node Kind"] == "directory"): |
116 return True | 133 return True |
117 return False | 134 return False |
118 | 135 |
| 136 def inCheckoutRoot(path): |
| 137 info = getSVNInfo(path, "HEAD") |
| 138 if (not info.has_key("Repository Root")): |
| 139 return False |
| 140 repo_root = info["Repository Root"]; |
| 141 info = getSVNInfo(os.path.dirname(os.path.abspath(path)), "HEAD") |
| 142 if (info.get("Repository Root", None) != repo_root): |
| 143 return True |
| 144 return False |
| 145 |
119 def getRevisionLog(url, revision): | 146 def getRevisionLog(url, revision): |
120 """Takes an svn url and gets the associated revision.""" | 147 """Takes an svn url and gets the associated revision.""" |
121 command = 'svn log ' + url + " -r"+str(revision) | 148 command = 'svn log ' + url + " -r"+str(revision) |
122 svn_log = subprocess.Popen(command, | 149 svn_log = subprocess.Popen(command, |
123 shell=True, | 150 shell=True, |
124 stdout=subprocess.PIPE, | 151 stdout=subprocess.PIPE, |
125 stderr=subprocess.PIPE).stdout.readlines() | 152 stderr=subprocess.PIPE).stdout.readlines() |
126 log = "" | 153 log = "" |
127 pos = 0 | 154 pos = 0 |
128 for line in svn_log: | 155 for line in svn_log: |
(...skipping 306 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
435 | 462 |
436 if options.revert and options.branch: | 463 if options.revert and options.branch: |
437 url = BRANCH_URL.replace("$branch", options.branch) | 464 url = BRANCH_URL.replace("$branch", options.branch) |
438 elif options.merge and options.sbranch: | 465 elif options.merge and options.sbranch: |
439 url = BRANCH_URL.replace("$branch", options.sbranch) | 466 url = BRANCH_URL.replace("$branch", options.sbranch) |
440 else: | 467 else: |
441 url = TRUNK_URL | 468 url = TRUNK_URL |
442 | 469 |
443 working = options.workdir or DEFAULT_WORKING | 470 working = options.workdir or DEFAULT_WORKING |
444 | 471 |
| 472 if options.local: |
| 473 working = os.getcwd() |
| 474 if not inCheckoutRoot(working): |
| 475 print "'%s' appears not to be the root of a working copy" % working |
| 476 sys.exit(1) |
| 477 if isSVNDirty(): |
| 478 print "Working copy contains uncommitted files" |
| 479 sys.exit(1) |
| 480 |
445 command = 'svn log ' + url + " -r "+str(revision) + " -v" | 481 command = 'svn log ' + url + " -r "+str(revision) + " -v" |
446 os.system(command) | 482 os.system(command) |
447 | 483 |
448 if not (options.revertbot or prompt("Is this the correct revision?")): | 484 if not (options.revertbot or prompt("Is this the correct revision?")): |
449 sys.exit(0) | 485 sys.exit(0) |
450 | 486 |
451 if (os.path.exists(working)): | 487 if (os.path.exists(working)) and not options.local: |
452 if not (options.revertbot or SKIP_CHECK_WORKING or | 488 if not (options.revertbot or SKIP_CHECK_WORKING or |
453 prompt("Working directory: '%s' already exists, clobber?" % working)): | 489 prompt("Working directory: '%s' already exists, clobber?" % working)): |
454 sys.exit(0) | 490 sys.exit(0) |
455 deltree(working) | 491 deltree(working) |
456 | 492 |
457 os.makedirs(working) | 493 if not options.local: |
458 os.chdir(working) | 494 os.makedirs(working) |
| 495 os.chdir(working) |
459 | 496 |
460 if options.merge: | 497 if options.merge: |
461 action = "Merge" | 498 action = "Merge" |
462 branch_url = BRANCH_URL.replace("$branch", options.branch) | 499 if not options.local: |
463 # Checkout everything but stuff that got added into a new dir | 500 branch_url = BRANCH_URL.replace("$branch", options.branch) |
464 checkoutRevision(url, revision, branch_url) | 501 # Checkout everything but stuff that got added into a new dir |
| 502 checkoutRevision(url, revision, branch_url) |
465 # Merge everything that changed | 503 # Merge everything that changed |
466 mergeRevision(url, revision) | 504 mergeRevision(url, revision) |
467 # "Export" files that were added from the source and add them to branch | 505 # "Export" files that were added from the source and add them to branch |
468 exportRevision(url, revision) | 506 exportRevision(url, revision) |
469 # Delete directories that were deleted (file deletes are handled in the | 507 # Delete directories that were deleted (file deletes are handled in the |
470 # merge). | 508 # merge). |
471 deleteRevision(url, revision) | 509 deleteRevision(url, revision) |
472 elif options.revert: | 510 elif options.revert: |
473 action = "Revert" | 511 action = "Revert" |
474 if options.branch: | 512 if options.branch: |
(...skipping 16 matching lines...) Expand all Loading... |
491 out.write(getRevisionLog(url, revision)) | 529 out.write(getRevisionLog(url, revision)) |
492 if (author): | 530 if (author): |
493 out.write("TBR=" + author) | 531 out.write("TBR=" + author) |
494 out.close() | 532 out.close() |
495 | 533 |
496 change_cmd = 'change ' + str(revision) + " " + filename | 534 change_cmd = 'change ' + str(revision) + " " + filename |
497 if options.revertbot: | 535 if options.revertbot: |
498 change_cmd += ' --silent' | 536 change_cmd += ' --silent' |
499 runGcl(change_cmd) | 537 runGcl(change_cmd) |
500 os.unlink(filename) | 538 os.unlink(filename) |
| 539 |
| 540 if options.local: |
| 541 sys.exit(0) |
| 542 |
501 print author | 543 print author |
502 print revision | 544 print revision |
503 print ("gcl upload " + str(revision) + | 545 print ("gcl upload " + str(revision) + |
504 " --send_mail --no_try --no_presubmit --reviewers=" + author) | 546 " --send_mail --no_try --no_presubmit --reviewers=" + author) |
505 | 547 |
506 if options.revertbot or prompt("Would you like to upload?"): | 548 if options.revertbot or prompt("Would you like to upload?"): |
507 if PROMPT_FOR_AUTHOR: | 549 if PROMPT_FOR_AUTHOR: |
508 author = text_prompt("Enter new author or press enter to accept default", | 550 author = text_prompt("Enter new author or press enter to accept default", |
509 author) | 551 author) |
510 if options.revertbot and options.revertbot_reviewers: | 552 if options.revertbot and options.revertbot_reviewers: |
(...skipping 14 matching lines...) Expand all Loading... |
525 runGcl("commit " + str(revision) + " --no_presubmit --force") | 567 runGcl("commit " + str(revision) + " --no_presubmit --force") |
526 else: | 568 else: |
527 sys.exit(0) | 569 sys.exit(0) |
528 | 570 |
529 if __name__ == "__main__": | 571 if __name__ == "__main__": |
530 option_parser = optparse.OptionParser(usage=USAGE % {"app": sys.argv[0]}) | 572 option_parser = optparse.OptionParser(usage=USAGE % {"app": sys.argv[0]}) |
531 option_parser.add_option('-m', '--merge', type="int", | 573 option_parser.add_option('-m', '--merge', type="int", |
532 help='Revision to merge from trunk to branch') | 574 help='Revision to merge from trunk to branch') |
533 option_parser.add_option('-b', '--branch', | 575 option_parser.add_option('-b', '--branch', |
534 help='Branch to revert or merge from') | 576 help='Branch to revert or merge from') |
| 577 option_parser.add_option('-l', '--local', action='store_true', |
| 578 help='Local working copy to merge to') |
535 option_parser.add_option('-s', '--sbranch', | 579 option_parser.add_option('-s', '--sbranch', |
536 help='Source branch for merge') | 580 help='Source branch for merge') |
537 option_parser.add_option('-r', '--revert', type="int", | 581 option_parser.add_option('-r', '--revert', type="int", |
538 help='Revision to revert') | 582 help='Revision to revert') |
539 option_parser.add_option('-w', '--workdir', | 583 option_parser.add_option('-w', '--workdir', |
540 help='subdir to use for the revert') | 584 help='subdir to use for the revert') |
541 option_parser.add_option('-a', '--auditor', | 585 option_parser.add_option('-a', '--auditor', |
542 help='overrides the author for reviewer')
| 586 help='overrides the author for reviewer')
|
543 option_parser.add_option('', '--revertbot', action='store_true', | 587 option_parser.add_option('', '--revertbot', action='store_true', |
544 default=False) | 588 default=False) |
545 option_parser.add_option('', '--revertbot-commit', action='store_true', | 589 option_parser.add_option('', '--revertbot-commit', action='store_true', |
546 default=False) | 590 default=False) |
547 option_parser.add_option('', '--revertbot-reviewers') | 591 option_parser.add_option('', '--revertbot-reviewers') |
548 options, args = option_parser.parse_args() | 592 options, args = option_parser.parse_args() |
549 | 593 |
550 if not options.merge and not options.revert: | 594 if not options.merge and not options.revert: |
551 option_parser.error("You need at least --merge or --revert") | 595 option_parser.error("You need at least --merge or --revert") |
552 sys.exit(1) | 596 sys.exit(1) |
553 | 597 |
554 if options.merge and not options.branch: | 598 if options.merge and not options.branch and not options.local: |
555 option_parser.error("--merge requires a --branch") | 599 option_parser.error("--merge requires either --branch or --local") |
| 600 sys.exit(1) |
| 601 |
| 602 if options.local and (options.revert or options.branch): |
| 603 option_parser.error("--local cannot be used with --revert or --branch") |
556 sys.exit(1) | 604 sys.exit(1) |
557 | 605 |
558 sys.exit(main(options, args)) | 606 sys.exit(main(options, args)) |
OLD | NEW |