| 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 |