OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2011 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 string |
9 import sys | 10 import sys |
| 11 import urllib2 |
10 | 12 |
11 import breakpad # pylint: disable=W0611 | 13 import breakpad # pylint: disable=W0611 |
12 | 14 |
13 import gclient_utils | 15 import gclient_utils |
14 import subprocess2 | 16 import subprocess2 |
15 | 17 |
16 USAGE = """ | 18 USAGE = """ |
17 WARNING: Please use this tool in an empty directory | 19 WARNING: Please use this tool in an empty directory |
18 (or at least one that you don't mind clobbering.) | 20 (or at least one that you don't mind clobbering.) |
19 | 21 |
20 REQUIRES: SVN 1.5+ | 22 REQUIRES: SVN 1.5+ |
21 NOTE: NO NEED TO CHECKOUT ANYTHING IN ADVANCE OF USING THIS TOOL. | 23 NOTE: NO NEED TO CHECKOUT ANYTHING IN ADVANCE OF USING THIS TOOL. |
22 Valid parameters: | 24 Valid parameters: |
23 | 25 |
24 [Merge from trunk to branch] | 26 [Merge from trunk to branch] |
25 --merge <revision> --branch <branch_num> | 27 --merge <revision> --branch <branch_num> |
26 Example: %(app)s --merge 12345 --branch 187 | 28 Example: %(app)s --merge 12345 --branch 187 |
27 | 29 |
| 30 [Merge from trunk to milestone] |
| 31 --merge <revision> --milestone <milestone_num> |
| 32 Example: %(app)s -- merge 12345 --milestone 16 |
| 33 |
28 [Merge from trunk to local copy] | 34 [Merge from trunk to local copy] |
29 --merge <revision> --local | 35 --merge <revision> --local |
30 Example: %(app)s --merge 12345 --local | 36 Example: %(app)s --merge 12345 --local |
31 | 37 |
32 [Merge from branch to branch] | 38 [Merge from branch to branch] |
33 --merge <revision> --sbranch <branch_num> --branch <branch_num> | 39 --merge <revision> --sbranch <branch_num> --branch <branch_num> |
34 Example: %(app)s --merge 12345 --sbranch 248 --branch 249 | 40 Example: %(app)s --merge 12345 --sbranch 248 --branch 249 |
35 | 41 |
36 [Revert from trunk] | 42 [Revert from trunk] |
37 --revert <revision> | 43 --revert <revision> |
(...skipping 312 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
350 | 356 |
351 | 357 |
352 def getAllFilesInRevision(files_info): | 358 def getAllFilesInRevision(files_info): |
353 """Checks for existing files in the revision. | 359 """Checks for existing files in the revision. |
354 | 360 |
355 Anything that's A will require special treatment (either a merge or an | 361 Anything that's A will require special treatment (either a merge or an |
356 export + add) | 362 export + add) |
357 """ | 363 """ |
358 return ['%s/%s' % (f[2], f[3]) for f in files_info] | 364 return ['%s/%s' % (f[2], f[3]) for f in files_info] |
359 | 365 |
| 366 |
| 367 def getBranchForMilestone(milestone): |
| 368 """Queries omahaproxy.appspot.com for the branch number given |milestone|. |
| 369 """ |
| 370 OMAHA_PROXY_URL = "http://omahaproxy.appspot.com" |
| 371 request = urllib2.Request(OMAHA_PROXY_URL) |
| 372 try: |
| 373 response = urllib2.urlopen(request) |
| 374 except urllib2.HTTPError, e: |
| 375 print "Failed to query %s: %d" % (OMAHA_PROXY_URL, e.code) |
| 376 return None |
| 377 |
| 378 # Dictionary of [branch: major]. When searching for the appropriate branch |
| 379 # matching |milestone|, all major versions that match are added to the |
| 380 # dictionary. If all of the branches are the same, this branch value is |
| 381 # returned; otherwise, the user is prompted to accept the largest branch |
| 382 # value. |
| 383 branch_dict = {} |
| 384 |
| 385 # Slice the first line since it's column information text. |
| 386 for line in response.readlines()[1:]: |
| 387 # Version data is CSV. |
| 388 parameters = string.split(line, ',') |
| 389 |
| 390 # Version is the third parameter and consists of a quad of numbers separated |
| 391 # by periods. |
| 392 version = string.split(parameters[2], '.') |
| 393 major = int(version[0], 10) |
| 394 if major != milestone: |
| 395 continue |
| 396 |
| 397 # Branch number is the third value in the quad. |
| 398 branch_dict[version[2]] = major |
| 399 |
| 400 if not branch_dict: |
| 401 # |milestone| not found. |
| 402 print "Milestone provided is invalid" |
| 403 return None |
| 404 |
| 405 # The following returns a sorted list of the keys of |branch_dict|. |
| 406 sorted_branches = sorted(branch_dict) |
| 407 branch = sorted_branches[0] |
| 408 |
| 409 # If all keys match, the branch is the same for all platforms given |
| 410 # |milestone|. This is the safe case, so return the branch. |
| 411 if len(sorted_branches) == 1: |
| 412 return branch |
| 413 |
| 414 # Not all of the platforms have the same branch. Prompt the user and return |
| 415 # the greatest (by value) branch on success. |
| 416 if prompt("Not all platforms have the same branch number, " |
| 417 "continue with branch %s?" % branch): |
| 418 return branch |
| 419 |
| 420 # User cancelled. |
| 421 return None |
| 422 |
| 423 |
360 def prompt(question): | 424 def prompt(question): |
361 while True: | 425 while True: |
362 print question + " [y|n]:", | 426 print question + " [y|n]:", |
363 answer = sys.stdin.readline() | 427 answer = sys.stdin.readline() |
364 if answer.lower().startswith('n'): | 428 if answer.lower().startswith('n'): |
365 return False | 429 return False |
366 elif answer.lower().startswith('y'): | 430 elif answer.lower().startswith('y'): |
367 return True | 431 return True |
368 | 432 |
369 | 433 |
370 def text_prompt(question, default): | 434 def text_prompt(question, default): |
371 print question + " [" + default + "]:" | 435 print question + " [" + default + "]:" |
372 answer = sys.stdin.readline() | 436 answer = sys.stdin.readline() |
373 if answer.strip() == "": | 437 if answer.strip() == "": |
374 return default | 438 return default |
375 return answer | 439 return answer |
376 | 440 |
377 | 441 |
378 def drover(options, args): | 442 def drover(options, args): |
379 revision = options.revert or options.merge | 443 revision = options.revert or options.merge |
380 | 444 |
381 # Initialize some variables used below. They can be overwritten by | 445 # Initialize some variables used below. They can be overwritten by |
382 # the drover.properties file. | 446 # the drover.properties file. |
383 BASE_URL = "svn://svn.chromium.org/chrome" | 447 BASE_URL = "svn://svn.chromium.org/chrome" |
384 TRUNK_URL = BASE_URL + "/trunk/src" | 448 TRUNK_URL = BASE_URL + "/trunk/src" |
385 BRANCH_URL = BASE_URL + "/branches/$branch/src" | 449 BRANCH_URL = BASE_URL + "/branches/$branch/src" |
386 SKIP_CHECK_WORKING = True | 450 SKIP_CHECK_WORKING = True |
387 PROMPT_FOR_AUTHOR = False | 451 PROMPT_FOR_AUTHOR = False |
388 | 452 |
| 453 # Translate a given milestone to the appropriate branch number. |
| 454 if options.milestone: |
| 455 options.branch = getBranchForMilestone(options.milestone) |
| 456 if not options.branch: |
| 457 return 1 |
| 458 |
389 DEFAULT_WORKING = "drover_" + str(revision) | 459 DEFAULT_WORKING = "drover_" + str(revision) |
390 if options.branch: | 460 if options.branch: |
391 DEFAULT_WORKING += ("_" + options.branch) | 461 DEFAULT_WORKING += ("_" + options.branch) |
392 | 462 |
393 if not isMinimumSVNVersion(1, 5): | 463 if not isMinimumSVNVersion(1, 5): |
394 print "You need to use at least SVN version 1.5.x" | 464 print "You need to use at least SVN version 1.5.x" |
395 return 1 | 465 return 1 |
396 | 466 |
397 # Override the default properties if there is a drover.properties file. | 467 # Override the default properties if there is a drover.properties file. |
398 global file_pattern_ | 468 global file_pattern_ |
(...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
515 else: | 585 else: |
516 return 0 | 586 return 0 |
517 | 587 |
518 | 588 |
519 def main(): | 589 def main(): |
520 option_parser = optparse.OptionParser(usage=USAGE % {"app": sys.argv[0]}) | 590 option_parser = optparse.OptionParser(usage=USAGE % {"app": sys.argv[0]}) |
521 option_parser.add_option('-m', '--merge', type="int", | 591 option_parser.add_option('-m', '--merge', type="int", |
522 help='Revision to merge from trunk to branch') | 592 help='Revision to merge from trunk to branch') |
523 option_parser.add_option('-b', '--branch', | 593 option_parser.add_option('-b', '--branch', |
524 help='Branch to revert or merge from') | 594 help='Branch to revert or merge from') |
| 595 option_parser.add_option('-M', '--milestone', type="int", |
| 596 help='Milestone to revert or merge from') |
525 option_parser.add_option('-l', '--local', action='store_true', | 597 option_parser.add_option('-l', '--local', action='store_true', |
526 help='Local working copy to merge to') | 598 help='Local working copy to merge to') |
527 option_parser.add_option('-s', '--sbranch', | 599 option_parser.add_option('-s', '--sbranch', |
528 help='Source branch for merge') | 600 help='Source branch for merge') |
529 option_parser.add_option('-r', '--revert', type="int", | 601 option_parser.add_option('-r', '--revert', type="int", |
530 help='Revision to revert') | 602 help='Revision to revert') |
531 option_parser.add_option('-w', '--workdir', | 603 option_parser.add_option('-w', '--workdir', |
532 help='subdir to use for the revert') | 604 help='subdir to use for the revert') |
533 option_parser.add_option('-a', '--auditor', | 605 option_parser.add_option('-a', '--auditor', |
534 help='overrides the author for reviewer') | 606 help='overrides the author for reviewer') |
535 option_parser.add_option('', '--revertbot', action='store_true', | 607 option_parser.add_option('', '--revertbot', action='store_true', |
536 default=False) | 608 default=False) |
537 option_parser.add_option('', '--revertbot-commit', action='store_true', | 609 option_parser.add_option('', '--revertbot-commit', action='store_true', |
538 default=False) | 610 default=False) |
539 option_parser.add_option('', '--revertbot-reviewers') | 611 option_parser.add_option('', '--revertbot-reviewers') |
540 options, args = option_parser.parse_args() | 612 options, args = option_parser.parse_args() |
541 | 613 |
542 if not options.merge and not options.revert: | 614 if not options.merge and not options.revert: |
543 option_parser.error("You need at least --merge or --revert") | 615 option_parser.error("You need at least --merge or --revert") |
544 return 1 | 616 return 1 |
545 | 617 |
546 if options.merge and not options.branch and not options.local: | 618 if options.merge and not (options.branch or options.milestone or |
547 option_parser.error("--merge requires either --branch or --local") | 619 options.local): |
| 620 option_parser.error("--merge requires either --branch " |
| 621 "or --milestone or --local") |
548 return 1 | 622 return 1 |
549 | 623 |
550 if options.local and (options.revert or options.branch): | 624 if options.local and (options.revert or options.branch or options.milestone): |
551 option_parser.error("--local cannot be used with --revert or --branch") | 625 option_parser.error("--local cannot be used with --revert " |
| 626 "or --branch or --milestone") |
| 627 return 1 |
| 628 |
| 629 if options.branch and options.milestone: |
| 630 option_parser.error("--branch cannot be used with --milestone") |
552 return 1 | 631 return 1 |
553 | 632 |
554 return drover(options, args) | 633 return drover(options, args) |
555 | 634 |
556 | 635 |
557 if __name__ == "__main__": | 636 if __name__ == "__main__": |
558 sys.exit(main()) | 637 sys.exit(main()) |
OLD | NEW |