OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 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 datetime |
6 import optparse | 7 import optparse |
7 import os | 8 import os |
8 import re | 9 import re |
9 import string | 10 import string |
10 import sys | 11 import sys |
11 import urllib2 | 12 import urllib2 |
12 import urlparse | 13 import urlparse |
13 | 14 |
14 import breakpad # pylint: disable=W0611 | 15 import breakpad # pylint: disable=W0611 |
15 | 16 |
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
65 command = "%s %s" % (gcl_path, subcommand) | 66 command = "%s %s" % (gcl_path, subcommand) |
66 return os.system(command) | 67 return os.system(command) |
67 | 68 |
68 def gclUpload(revision, author): | 69 def gclUpload(revision, author): |
69 command = ("upload " + str(revision) + | 70 command = ("upload " + str(revision) + |
70 " --send_mail --no_presubmit --reviewers=" + author) | 71 " --send_mail --no_presubmit --reviewers=" + author) |
71 return runGcl(command) | 72 return runGcl(command) |
72 | 73 |
73 def getSVNInfo(url, revision): | 74 def getSVNInfo(url, revision): |
74 info = {} | 75 info = {} |
75 try: | 76 svn_info = subprocess2.capture( |
76 svn_info = subprocess2.check_output( | 77 ['svn', 'info', '--non-interactive', '%s@%s' % (url, revision)], |
77 ['svn', 'info', '%s@%s' % (url, revision)]).splitlines() | 78 stderr=subprocess2.VOID).splitlines() |
78 for line in svn_info: | 79 for line in svn_info: |
79 match = re.search(r"(.*?):(.*)", line) | 80 match = re.search(r"(.*?):(.*)", line) |
80 if match: | 81 if match: |
81 info[match.group(1).strip()] = match.group(2).strip() | 82 info[match.group(1).strip()] = match.group(2).strip() |
82 except subprocess2.CalledProcessError: | |
83 pass | |
84 return info | 83 return info |
85 | 84 |
86 def isSVNDirty(): | 85 def isSVNDirty(): |
87 svn_status = subprocess2.check_output(['svn', 'status']).splitlines() | 86 svn_status = subprocess2.check_output(['svn', 'status']).splitlines() |
88 for line in svn_status: | 87 for line in svn_status: |
89 match = re.search(r"^[^X?]", line) | 88 match = re.search(r"^[^X?]", line) |
90 if match: | 89 if match: |
91 return True | 90 return True |
92 | 91 |
93 return False | 92 return False |
(...skipping 390 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
484 return default | 483 return default |
485 return answer | 484 return answer |
486 | 485 |
487 | 486 |
488 def drover(options, args): | 487 def drover(options, args): |
489 revision = options.revert or options.merge | 488 revision = options.revert or options.merge |
490 | 489 |
491 # Initialize some variables used below. They can be overwritten by | 490 # Initialize some variables used below. They can be overwritten by |
492 # the drover.properties file. | 491 # the drover.properties file. |
493 BASE_URL = "svn://svn.chromium.org/chrome" | 492 BASE_URL = "svn://svn.chromium.org/chrome" |
| 493 REVERT_ALT_URLS = ['svn://svn.chromium.org/chrome-internal', |
| 494 'svn://svn.chromium.org/native_client'] |
494 TRUNK_URL = BASE_URL + "/trunk/src" | 495 TRUNK_URL = BASE_URL + "/trunk/src" |
495 BRANCH_URL = BASE_URL + "/branches/$branch/src" | 496 BRANCH_URL = BASE_URL + "/branches/$branch/src" |
496 SKIP_CHECK_WORKING = True | 497 SKIP_CHECK_WORKING = True |
497 PROMPT_FOR_AUTHOR = False | 498 PROMPT_FOR_AUTHOR = False |
498 | 499 |
499 # Translate a given milestone to the appropriate branch number. | 500 # Translate a given milestone to the appropriate branch number. |
500 if options.milestone: | 501 if options.milestone: |
501 options.branch = getBranchForMilestone(options.milestone) | 502 options.branch = getBranchForMilestone(options.milestone) |
502 if not options.branch: | 503 if not options.branch: |
503 return 1 | 504 return 1 |
(...skipping 10 matching lines...) Expand all Loading... |
514 global file_pattern_ | 515 global file_pattern_ |
515 if os.path.exists("drover.properties"): | 516 if os.path.exists("drover.properties"): |
516 FILE_PATTERN = file_pattern_ | 517 FILE_PATTERN = file_pattern_ |
517 f = open("drover.properties") | 518 f = open("drover.properties") |
518 exec(f) | 519 exec(f) |
519 f.close() | 520 f.close() |
520 if FILE_PATTERN: | 521 if FILE_PATTERN: |
521 file_pattern_ = FILE_PATTERN | 522 file_pattern_ = FILE_PATTERN |
522 | 523 |
523 if options.revert and options.branch: | 524 if options.revert and options.branch: |
| 525 print 'Note: --branch is usually not needed for reverts.' |
524 url = BRANCH_URL.replace("$branch", options.branch) | 526 url = BRANCH_URL.replace("$branch", options.branch) |
525 elif options.merge and options.sbranch: | 527 elif options.merge and options.sbranch: |
526 url = BRANCH_URL.replace("$branch", options.sbranch) | 528 url = BRANCH_URL.replace("$branch", options.sbranch) |
527 elif options.revert and options.url: | 529 elif options.revert: |
528 url = options.url | 530 url = options.url or BASE_URL |
529 file_pattern_ = r"[ ]+([MADUC])[ ]+((/.*)/(.*))" | 531 file_pattern_ = r"[ ]+([MADUC])[ ]+((/.*)/(.*))" |
530 else: | 532 else: |
531 url = TRUNK_URL | 533 url = TRUNK_URL |
532 | 534 |
533 working = options.workdir or DEFAULT_WORKING | 535 working = options.workdir or DEFAULT_WORKING |
534 | 536 |
535 if options.local: | 537 if options.local: |
536 working = os.getcwd() | 538 working = os.getcwd() |
537 if not inCheckoutRoot(working): | 539 if not inCheckoutRoot(working): |
538 print "'%s' appears not to be the root of a working copy" % working | 540 print "'%s' appears not to be the root of a working copy" % working |
539 return 1 | 541 return 1 |
540 if (isSVNDirty() and not | 542 if (isSVNDirty() and not |
541 prompt("Working copy contains uncommitted files. Continue?")): | 543 prompt("Working copy contains uncommitted files. Continue?")): |
542 return 1 | 544 return 1 |
543 | 545 |
| 546 if options.revert and not options.no_alt_urls: |
| 547 for cur_url in [url] + REVERT_ALT_URLS: |
| 548 try: |
| 549 commit_date_str = getSVNInfo( |
| 550 cur_url, options.revert).get('Last Changed Date', 'x').split()[0] |
| 551 commit_date = datetime.datetime.strptime(commit_date_str, '%Y-%m-%d') |
| 552 if (datetime.datetime.now() - commit_date).days < 120: |
| 553 if cur_url != url: |
| 554 print 'Guessing svn repo: %s.' % cur_url, |
| 555 print 'Use --no-alt-urls to disable heuristic.' |
| 556 url = cur_url |
| 557 break |
| 558 except ValueError: |
| 559 pass |
544 command = 'svn log ' + url + " -r "+str(revision) + " -v" | 560 command = 'svn log ' + url + " -r "+str(revision) + " -v" |
545 os.system(command) | 561 os.system(command) |
546 | 562 |
547 if not (options.revertbot or prompt("Is this the correct revision?")): | 563 if not (options.revertbot or prompt("Is this the correct revision?")): |
548 return 0 | 564 return 0 |
549 | 565 |
550 if (os.path.exists(working)) and not options.local: | 566 if (os.path.exists(working)) and not options.local: |
551 if not (options.revertbot or SKIP_CHECK_WORKING or | 567 if not (options.revertbot or SKIP_CHECK_WORKING or |
552 prompt("Working directory: '%s' already exists, clobber?" % working)): | 568 prompt("Working directory: '%s' already exists, clobber?" % working)): |
553 return 0 | 569 return 0 |
(...skipping 11 matching lines...) Expand all Loading... |
565 checkoutRevision(url, revision, branch_url) | 581 checkoutRevision(url, revision, branch_url) |
566 # Merge everything that changed | 582 # Merge everything that changed |
567 mergeRevision(url, revision) | 583 mergeRevision(url, revision) |
568 # "Export" files that were added from the source and add them to branch | 584 # "Export" files that were added from the source and add them to branch |
569 exportRevision(url, revision) | 585 exportRevision(url, revision) |
570 # Delete directories that were deleted (file deletes are handled in the | 586 # Delete directories that were deleted (file deletes are handled in the |
571 # merge). | 587 # merge). |
572 deleteRevision(url, revision) | 588 deleteRevision(url, revision) |
573 elif options.revert: | 589 elif options.revert: |
574 action = "Revert" | 590 action = "Revert" |
575 if options.branch: | |
576 url = BRANCH_URL.replace("$branch", options.branch) | |
577 pop_em = not options.url | 591 pop_em = not options.url |
578 checkoutRevision(url, revision, url, True, pop_em) | 592 checkoutRevision(url, revision, url, True, pop_em) |
579 revertRevision(url, revision) | 593 revertRevision(url, revision) |
580 revertExportRevision(url, revision) | 594 revertExportRevision(url, revision) |
581 | 595 |
582 # Check the base url so we actually find the author who made the change | 596 # Check the base url so we actually find the author who made the change |
583 if options.auditor: | 597 if options.auditor: |
584 author = options.auditor | 598 author = options.auditor |
585 else: | 599 else: |
586 author = getAuthor(url, revision) | 600 author = getAuthor(url, revision) |
(...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
665 option_parser.add_option('-s', '--sbranch', | 679 option_parser.add_option('-s', '--sbranch', |
666 help='Source branch for merge') | 680 help='Source branch for merge') |
667 option_parser.add_option('-r', '--revert', type="int", | 681 option_parser.add_option('-r', '--revert', type="int", |
668 help='Revision to revert') | 682 help='Revision to revert') |
669 option_parser.add_option('-w', '--workdir', | 683 option_parser.add_option('-w', '--workdir', |
670 help='subdir to use for the revert') | 684 help='subdir to use for the revert') |
671 option_parser.add_option('-u', '--url', | 685 option_parser.add_option('-u', '--url', |
672 help='svn url to use for the revert') | 686 help='svn url to use for the revert') |
673 option_parser.add_option('-a', '--auditor', | 687 option_parser.add_option('-a', '--auditor', |
674 help='overrides the author for reviewer') | 688 help='overrides the author for reviewer') |
675 option_parser.add_option('', '--revertbot', action='store_true', | 689 option_parser.add_option('--revertbot', action='store_true', |
676 default=False) | 690 default=False) |
677 option_parser.add_option('', '--revertbot-commit', action='store_true', | 691 option_parser.add_option('--no-alt-urls', action='store_true', |
| 692 help='Disable heuristics used to determine svn url') |
| 693 option_parser.add_option('--revertbot-commit', action='store_true', |
678 default=False) | 694 default=False) |
679 option_parser.add_option('', '--revertbot-reviewers') | 695 option_parser.add_option('--revertbot-reviewers') |
680 options, args = option_parser.parse_args() | 696 options, args = option_parser.parse_args() |
681 | 697 |
682 if not options.merge and not options.revert: | 698 if not options.merge and not options.revert: |
683 option_parser.error("You need at least --merge or --revert") | 699 option_parser.error("You need at least --merge or --revert") |
684 return 1 | 700 return 1 |
685 | 701 |
686 if options.merge and not (options.branch or options.milestone or | 702 if options.merge and not (options.branch or options.milestone or |
687 options.local): | 703 options.local): |
688 option_parser.error("--merge requires either --branch " | 704 option_parser.error("--merge requires either --branch " |
689 "or --milestone or --local") | 705 "or --milestone or --local") |
690 return 1 | 706 return 1 |
691 | 707 |
692 if options.local and (options.revert or options.branch or options.milestone): | 708 if options.local and (options.revert or options.branch or options.milestone): |
693 option_parser.error("--local cannot be used with --revert " | 709 option_parser.error("--local cannot be used with --revert " |
694 "or --branch or --milestone") | 710 "or --branch or --milestone") |
695 return 1 | 711 return 1 |
696 | 712 |
697 if options.branch and options.milestone: | 713 if options.branch and options.milestone: |
698 option_parser.error("--branch cannot be used with --milestone") | 714 option_parser.error("--branch cannot be used with --milestone") |
699 return 1 | 715 return 1 |
700 | 716 |
701 return drover(options, args) | 717 return drover(options, args) |
702 | 718 |
703 | 719 |
704 if __name__ == "__main__": | 720 if __name__ == "__main__": |
705 sys.exit(main()) | 721 sys.exit(main()) |
OLD | NEW |