Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 # git-cl -- a git-command for integrating reviews on Rietveld | 2 # git-cl -- a git-command for integrating reviews on Rietveld |
| 3 # Copyright (C) 2008 Evan Martin <martine@danga.com> | 3 # Copyright (C) 2008 Evan Martin <martine@danga.com> |
| 4 | 4 |
| 5 import errno | 5 import errno |
| 6 import logging | 6 import logging |
| 7 import optparse | 7 import optparse |
| 8 import os | 8 import os |
| 9 import re | 9 import re |
| 10 import StringIO | |
| 10 import subprocess | 11 import subprocess |
| 11 import sys | 12 import sys |
| 12 import tempfile | 13 import tempfile |
| 13 import textwrap | 14 import textwrap |
| 14 import upload | 15 import upload |
| 15 import urlparse | 16 import urlparse |
| 16 import urllib2 | 17 import urllib2 |
| 17 | 18 |
| 18 try: | 19 try: |
| 19 import readline | 20 import readline |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 30 sys.path.append(depot_tools_path) | 31 sys.path.append(depot_tools_path) |
| 31 import breakpad | 32 import breakpad |
| 32 except ImportError: | 33 except ImportError: |
| 33 pass | 34 pass |
| 34 | 35 |
| 35 DEFAULT_SERVER = 'http://codereview.appspot.com' | 36 DEFAULT_SERVER = 'http://codereview.appspot.com' |
| 36 POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s' | 37 POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s' |
| 37 DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup' | 38 DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup' |
| 38 | 39 |
| 39 def DieWithError(message): | 40 def DieWithError(message): |
| 40 print >>sys.stderr, message | 41 print >> sys.stderr, message |
| 41 sys.exit(1) | 42 sys.exit(1) |
| 42 | 43 |
| 43 | 44 |
| 44 def Popen(cmd, **kwargs): | 45 def Popen(cmd, **kwargs): |
| 45 """Wrapper for subprocess.Popen() that logs and watch for cygwin issues""" | 46 """Wrapper for subprocess.Popen() that logs and watch for cygwin issues""" |
| 46 logging.info('Popen: ' + ' '.join(cmd)) | 47 logging.info('Popen: ' + ' '.join(cmd)) |
| 47 try: | 48 try: |
| 48 return subprocess.Popen(cmd, **kwargs) | 49 return subprocess.Popen(cmd, **kwargs) |
| 49 except OSError, e: | 50 except OSError, e: |
| 50 if e.errno == errno.EAGAIN and sys.platform == 'cygwin': | 51 if e.errno == errno.EAGAIN and sys.platform == 'cygwin': |
| (...skipping 423 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 474 | 475 |
| 475 SetProperty(settings.GetCCList(), 'CC list', 'cc') | 476 SetProperty(settings.GetCCList(), 'CC list', 'cc') |
| 476 SetProperty(settings.GetTreeStatusUrl(error_ok=True), 'Tree status URL', | 477 SetProperty(settings.GetTreeStatusUrl(error_ok=True), 'Tree status URL', |
| 477 'tree-status-url') | 478 'tree-status-url') |
| 478 SetProperty(settings.GetViewVCUrl(), 'ViewVC URL', 'viewvc-url') | 479 SetProperty(settings.GetViewVCUrl(), 'ViewVC URL', 'viewvc-url') |
| 479 | 480 |
| 480 # TODO: configure a default branch to diff against, rather than this | 481 # TODO: configure a default branch to diff against, rather than this |
| 481 # svn-based hackery. | 482 # svn-based hackery. |
| 482 | 483 |
| 483 | 484 |
| 485 class HookResults(object): | |
| 486 """Contains the parsed output of the presubmit hooks.""" | |
| 487 def __init__(self, output_from_hooks=None): | |
| 488 self.reviewers = [] | |
| 489 self.output = None | |
| 490 self._ParseOutputFromHooks(output_from_hooks) | |
| 491 | |
| 492 def _ParseOutputFromHooks(self, output_from_hooks): | |
| 493 if not output_from_hooks: | |
| 494 return | |
| 495 lines = [] | |
| 496 reviewers = [] | |
| 497 reviewer_regexp = re.compile('ADD: R=(.+)') | |
| 498 for l in output_from_hooks.splitlines(): | |
| 499 m = reviewer_regexp.match(l) | |
| 500 if m: | |
| 501 reviewers.extend(m.group(1).split(',')) | |
| 502 else: | |
| 503 lines.append(l) | |
| 504 self.output = '\n'.join(lines) | |
| 505 self.reviewers = ','.join(reviewers) | |
| 506 | |
| 507 | |
| 508 class ChangeDescription(object): | |
| 509 """Contains a parsed form of the change description.""" | |
| 510 def __init__(self, subject, log_desc, reviewers): | |
| 511 self.subject = subject | |
| 512 self.log_desc = log_desc | |
| 513 self.reviewers = reviewers | |
| 514 self.description = self.log_desc | |
| 515 | |
| 516 def Update(self): | |
| 517 initial_text = """# Enter a description of the change. | |
| 518 # This will displayed on the codereview site. | |
| 519 # The first line will also be used as the subject of the review. | |
| 520 """ | |
| 521 initial_text += self.description | |
| 522 if 'R=' not in self.description and self.reviewers: | |
| 523 initial_text += '\nR=' + self.reviewers | |
| 524 if 'BUG=' not in self.description: | |
| 525 initial_text += '\nBUG=' | |
| 526 if 'TEST=' not in self.description: | |
| 527 initial_text += '\nTEST=' | |
| 528 self._ParseDescription(UserEditedLog(initial_text)) | |
| 529 | |
| 530 def _ParseDescription(self, description): | |
| 531 if not description: | |
| 532 self.description = description | |
| 533 return | |
| 534 | |
| 535 parsed_lines = [] | |
| 536 reviewers_regexp = re.compile('\s*R=(.+)') | |
| 537 reviewers = '' | |
| 538 subject = '' | |
| 539 for l in description.splitlines(): | |
| 540 if not subject: | |
| 541 subject = l | |
| 542 matched_reviewers = reviewers_regexp.match(l) | |
| 543 if matched_reviewers: | |
| 544 reviewers = matched_reviewers.group(1) | |
| 545 parsed_lines.append(l) | |
| 546 | |
| 547 self.description = '\n'.join(parsed_lines) + '\n' | |
| 548 self.subject = subject | |
| 549 self.reviewers = reviewers | |
| 550 | |
| 551 def IsEmpty(self): | |
| 552 return not self.description | |
| 553 | |
| 554 | |
| 484 def FindCodereviewSettingsFile(filename='codereview.settings'): | 555 def FindCodereviewSettingsFile(filename='codereview.settings'): |
| 485 """Finds the given file starting in the cwd and going up. | 556 """Finds the given file starting in the cwd and going up. |
| 486 | 557 |
| 487 Only looks up to the top of the repository unless an | 558 Only looks up to the top of the repository unless an |
| 488 'inherit-review-settings-ok' file exists in the root of the repository. | 559 'inherit-review-settings-ok' file exists in the root of the repository. |
| 489 """ | 560 """ |
| 490 inherit_ok_file = 'inherit-review-settings-ok' | 561 inherit_ok_file = 'inherit-review-settings-ok' |
| 491 cwd = os.getcwd() | 562 cwd = os.getcwd() |
| 492 root = os.path.abspath(RunGit(['rev-parse', '--show-cdup']).strip()) | 563 root = os.path.abspath(RunGit(['rev-parse', '--show-cdup']).strip()) |
| 493 if os.path.isfile(os.path.join(root, inherit_ok_file)): | 564 if os.path.isfile(os.path.join(root, inherit_ok_file)): |
| 494 root = '/' | 565 root = '/' |
| 495 while True: | 566 while True: |
| 496 if filename in os.listdir(cwd): | 567 if filename in os.listdir(cwd): |
| 497 if os.path.isfile(os.path.join(cwd, filename)): | 568 if os.path.isfile(os.path.join(cwd, filename)): |
| 498 return open(os.path.join(cwd, filename)) | 569 return open(os.path.join(cwd, filename)) |
| 499 if cwd == root: | 570 if cwd == root: |
| 500 break | 571 break |
| 501 cwd = os.path.dirname(cwd) | 572 cwd = os.path.dirname(cwd) |
| 502 | 573 |
| 503 | 574 |
| 504 def LoadCodereviewSettingsFromFile(fileobj): | 575 def LoadCodereviewSettingsFromFile(fileobj): |
| 505 """Parse a codereview.settings file and updates hooks.""" | 576 """Parse a codereview.settings file and updates hooks.""" |
| 506 def DownloadToFile(url, filename): | |
| 507 filename = os.path.join(settings.GetRoot(), filename) | |
| 508 contents = urllib2.urlopen(url).read() | |
| 509 fileobj = open(filename, 'w') | |
| 510 fileobj.write(contents) | |
| 511 fileobj.close() | |
| 512 os.chmod(filename, 0755) | |
| 513 return 0 | |
| 514 | |
| 515 keyvals = {} | 577 keyvals = {} |
| 516 for line in fileobj.read().splitlines(): | 578 for line in fileobj.read().splitlines(): |
| 517 if not line or line.startswith("#"): | 579 if not line or line.startswith("#"): |
| 518 continue | 580 continue |
| 519 k, v = line.split(": ", 1) | 581 k, v = line.split(": ", 1) |
| 520 keyvals[k] = v | 582 keyvals[k] = v |
| 521 | 583 |
| 522 def GetProperty(name): | |
| 523 return keyvals.get(name) | |
| 524 | |
| 525 def SetProperty(name, setting, unset_error_ok=False): | 584 def SetProperty(name, setting, unset_error_ok=False): |
| 526 fullname = 'rietveld.' + name | 585 fullname = 'rietveld.' + name |
| 527 if setting in keyvals: | 586 if setting in keyvals: |
| 528 RunGit(['config', fullname, keyvals[setting]]) | 587 RunGit(['config', fullname, keyvals[setting]]) |
| 529 else: | 588 else: |
| 530 RunGit(['config', '--unset-all', fullname], error_ok=unset_error_ok) | 589 RunGit(['config', '--unset-all', fullname], error_ok=unset_error_ok) |
| 531 | 590 |
| 532 SetProperty('server', 'CODE_REVIEW_SERVER') | 591 SetProperty('server', 'CODE_REVIEW_SERVER') |
| 533 # Only server setting is required. Other settings can be absent. | 592 # Only server setting is required. Other settings can be absent. |
| 534 # In that case, we ignore errors raised during option deletion attempt. | 593 # In that case, we ignore errors raised during option deletion attempt. |
| (...skipping 130 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 665 | 724 |
| 666 | 725 |
| 667 def ConvertToInteger(inputval): | 726 def ConvertToInteger(inputval): |
| 668 """Convert a string to integer, but returns either an int or None.""" | 727 """Convert a string to integer, but returns either an int or None.""" |
| 669 try: | 728 try: |
| 670 return int(inputval) | 729 return int(inputval) |
| 671 except (TypeError, ValueError): | 730 except (TypeError, ValueError): |
| 672 return None | 731 return None |
| 673 | 732 |
| 674 | 733 |
| 675 def RunHook(committing, upstream_branch): | 734 def RunHook(committing, upstream_branch, rietveld_server, tbr, may_prompt): |
| 735 """Calls sys.exit() if the hook fails; returns a HookResults otherwise.""" | |
| 676 import presubmit_support | 736 import presubmit_support |
| 677 import scm | 737 import scm |
| 678 import watchlists | 738 import watchlists |
| 679 | 739 |
| 680 root = RunCommand(['git', 'rev-parse', '--show-cdup']).strip() | 740 root = RunCommand(['git', 'rev-parse', '--show-cdup']).strip() |
| 681 if not root: | 741 if not root: |
| 682 root = "." | 742 root = "." |
| 683 absroot = os.path.abspath(root) | 743 absroot = os.path.abspath(root) |
| 684 if not root: | 744 if not root: |
| 685 raise Exception("Could not get root directory.") | 745 raise Exception("Could not get root directory.") |
| (...skipping 17 matching lines...) Expand all Loading... | |
| 703 issue, patchset) | 763 issue, patchset) |
| 704 | 764 |
| 705 # Apply watchlists on upload. | 765 # Apply watchlists on upload. |
| 706 if not committing: | 766 if not committing: |
| 707 watchlist = watchlists.Watchlists(change.RepositoryRoot()) | 767 watchlist = watchlists.Watchlists(change.RepositoryRoot()) |
| 708 files = [f.LocalPath() for f in change.AffectedFiles()] | 768 files = [f.LocalPath() for f in change.AffectedFiles()] |
| 709 watchers = watchlist.GetWatchersForPaths(files) | 769 watchers = watchlist.GetWatchersForPaths(files) |
| 710 RunCommand(['git', 'config', '--replace-all', | 770 RunCommand(['git', 'config', '--replace-all', |
| 711 'rietveld.extracc', ','.join(watchers)]) | 771 'rietveld.extracc', ','.join(watchers)]) |
| 712 | 772 |
| 713 return presubmit_support.DoPresubmitChecks(change, committing, | 773 output = StringIO.StringIO() |
| 714 verbose=None, output_stream=sys.stdout, input_stream=sys.stdin, | 774 res = presubmit_support.DoPresubmitChecks(change, committing, |
| 715 default_presubmit=None, may_prompt=None) | 775 verbose=None, output_stream=output, input_stream=sys.stdin, |
| 776 default_presubmit=None, may_prompt=may_prompt, tbr=tbr, | |
| 777 host_url=cl.GetRietveldServer()) | |
| 778 hook_results = HookResults(output.getvalue()) | |
| 779 if hook_results.output: | |
| 780 print hook_results.output | |
| 781 | |
| 782 # TODO(dpranke): We should propagate the error out instead of calling exit(). | |
| 783 if not res: | |
| 784 sys.exit(1) | |
| 785 return hook_results | |
| 716 | 786 |
| 717 | 787 |
| 718 def CMDpresubmit(parser, args): | 788 def CMDpresubmit(parser, args): |
| 719 """run presubmit tests on the current changelist""" | 789 """run presubmit tests on the current changelist""" |
| 720 parser.add_option('--upload', action='store_true', | 790 parser.add_option('--upload', action='store_true', |
| 721 help='Run upload hook instead of the push/dcommit hook') | 791 help='Run upload hook instead of the push/dcommit hook') |
| 722 (options, args) = parser.parse_args(args) | 792 (options, args) = parser.parse_args(args) |
| 723 | 793 |
| 724 # Make sure index is up-to-date before running diff-index. | 794 # Make sure index is up-to-date before running diff-index. |
| 725 RunGit(['update-index', '--refresh', '-q'], error_ok=True) | 795 RunGit(['update-index', '--refresh', '-q'], error_ok=True) |
| 726 if RunGit(['diff-index', 'HEAD']): | 796 if RunGit(['diff-index', 'HEAD']): |
| 727 # TODO(maruel): Is this really necessary? | 797 # TODO(maruel): Is this really necessary? |
| 728 print 'Cannot presubmit with a dirty tree. You must commit locally first.' | 798 print 'Cannot presubmit with a dirty tree. You must commit locally first.' |
| 729 return 1 | 799 return 1 |
| 730 | 800 |
| 801 cl = Changelist() | |
| 731 if args: | 802 if args: |
| 732 base_branch = args[0] | 803 base_branch = args[0] |
| 733 else: | 804 else: |
| 734 # Default to diffing against the "upstream" branch. | 805 # Default to diffing against the "upstream" branch. |
| 735 base_branch = Changelist().GetUpstreamBranch() | 806 base_branch = cl.GetUpstreamBranch() |
| 736 | 807 |
| 737 if options.upload: | 808 if options.upload: |
| 738 print '*** Presubmit checks for UPLOAD would report: ***' | 809 print '*** Presubmit checks for UPLOAD would report: ***' |
| 739 return RunHook(committing=False, upstream_branch=base_branch) | 810 RunHook(committing=False, upstream_branch=base_branch, |
| 811 rietveld_server=cl.GetRietveldServer(), tbr=False, | |
| 812 may_prompt=False) | |
| 813 return 0 | |
| 740 else: | 814 else: |
| 741 print '*** Presubmit checks for DCOMMIT would report: ***' | 815 print '*** Presubmit checks for DCOMMIT would report: ***' |
| 742 return RunHook(committing=True, upstream_branch=base_branch) | 816 RunHook(committing=True, upstream_branch=base_branch, |
| 817 rietveld_server=cl.GetRietveldServer, tbr=False, | |
| 818 may_prompt=False) | |
| 819 return 0 | |
| 743 | 820 |
| 744 | 821 |
| 745 @usage('[args to "git diff"]') | 822 @usage('[args to "git diff"]') |
| 746 def CMDupload(parser, args): | 823 def CMDupload(parser, args): |
| 747 """upload the current changelist to codereview""" | 824 """upload the current changelist to codereview""" |
| 748 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks', | 825 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks', |
| 749 help='bypass upload presubmit hook') | 826 help='bypass upload presubmit hook') |
| 827 parser.add_option('-f', action='store_true', dest='force', | |
| 828 help="force yes to questions (don't prompt)") | |
| 750 parser.add_option('-m', dest='message', help='message for patch') | 829 parser.add_option('-m', dest='message', help='message for patch') |
| 751 parser.add_option('-r', '--reviewers', | 830 parser.add_option('-r', '--reviewers', |
| 752 help='reviewer email addresses') | 831 help='reviewer email addresses') |
| 753 parser.add_option('--cc', | 832 parser.add_option('--cc', |
| 754 help='cc email addresses') | 833 help='cc email addresses') |
| 755 parser.add_option('--send-mail', action='store_true', | 834 parser.add_option('--send-mail', action='store_true', |
| 756 help='send email to reviewer immediately') | 835 help='send email to reviewer immediately') |
| 757 parser.add_option("--emulate_svn_auto_props", action="store_true", | 836 parser.add_option("--emulate_svn_auto_props", action="store_true", |
| 758 dest="emulate_svn_auto_props", | 837 dest="emulate_svn_auto_props", |
| 759 help="Emulate Subversion's auto properties feature.") | 838 help="Emulate Subversion's auto properties feature.") |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 771 | 850 |
| 772 cl = Changelist() | 851 cl = Changelist() |
| 773 if args: | 852 if args: |
| 774 base_branch = args[0] | 853 base_branch = args[0] |
| 775 else: | 854 else: |
| 776 # Default to diffing against the "upstream" branch. | 855 # Default to diffing against the "upstream" branch. |
| 777 base_branch = cl.GetUpstreamBranch() | 856 base_branch = cl.GetUpstreamBranch() |
| 778 args = [base_branch + "..."] | 857 args = [base_branch + "..."] |
| 779 | 858 |
| 780 if not options.bypass_hooks: | 859 if not options.bypass_hooks: |
| 781 RunHook(committing=False, upstream_branch=base_branch) | 860 hook_results = RunHook(committing=False, upstream_branch=base_branch, |
| 861 rietveld_server=cl.GetRietveldServer(), tbr=False, | |
| 862 may_prompt=(not options.force)) | |
|
M-A Ruel
2011/03/12 03:02:21
Can you test it to see what happens when may_promp
| |
| 863 else: | |
| 864 hook_results = HookResults() | |
| 865 | |
| 866 if not options.reviewers and hook_results.reviewers: | |
| 867 options.reviewers = hook_results.reviewers | |
| 782 | 868 |
| 783 # --no-ext-diff is broken in some versions of Git, so try to work around | 869 # --no-ext-diff is broken in some versions of Git, so try to work around |
| 784 # this by overriding the environment (but there is still a problem if the | 870 # this by overriding the environment (but there is still a problem if the |
| 785 # git config key "diff.external" is used). | 871 # git config key "diff.external" is used). |
| 786 env = os.environ.copy() | 872 env = os.environ.copy() |
| 787 if 'GIT_EXTERNAL_DIFF' in env: | 873 if 'GIT_EXTERNAL_DIFF' in env: |
| 788 del env['GIT_EXTERNAL_DIFF'] | 874 del env['GIT_EXTERNAL_DIFF'] |
| 789 subprocess.call(['git', 'diff', '--no-ext-diff', '--stat', '-M'] + args, | 875 subprocess.call(['git', 'diff', '--no-ext-diff', '--stat', '-M'] + args, |
| 790 env=env) | 876 env=env) |
| 791 | 877 |
| 792 upload_args = ['--assume_yes'] # Don't ask about untracked files. | 878 upload_args = ['--assume_yes'] # Don't ask about untracked files. |
| 793 upload_args.extend(['--server', cl.GetRietveldServer()]) | 879 upload_args.extend(['--server', cl.GetRietveldServer()]) |
| 794 if options.reviewers: | |
| 795 upload_args.extend(['--reviewers', options.reviewers]) | |
| 796 if options.emulate_svn_auto_props: | 880 if options.emulate_svn_auto_props: |
| 797 upload_args.append('--emulate_svn_auto_props') | 881 upload_args.append('--emulate_svn_auto_props') |
| 798 if options.send_mail: | 882 if options.send_mail: |
| 799 if not options.reviewers: | 883 if not options.reviewers: |
| 800 DieWithError("Must specify reviewers to send email.") | 884 DieWithError("Must specify reviewers to send email.") |
| 801 upload_args.append('--send_mail') | 885 upload_args.append('--send_mail') |
| 802 if options.from_logs and not options.message: | 886 if options.from_logs and not options.message: |
| 803 print 'Must set message for subject line if using desc_from_logs' | 887 print 'Must set message for subject line if using desc_from_logs' |
| 804 return 1 | 888 return 1 |
| 805 | 889 |
| 806 change_desc = None | 890 change_desc = None |
| 807 | 891 |
| 808 if cl.GetIssue(): | 892 if cl.GetIssue(): |
| 809 if options.message: | 893 if options.message: |
| 810 upload_args.extend(['--message', options.message]) | 894 upload_args.extend(['--message', options.message]) |
| 811 upload_args.extend(['--issue', cl.GetIssue()]) | 895 upload_args.extend(['--issue', cl.GetIssue()]) |
| 812 print ("This branch is associated with issue %s. " | 896 print ("This branch is associated with issue %s. " |
| 813 "Adding patch to that issue." % cl.GetIssue()) | 897 "Adding patch to that issue." % cl.GetIssue()) |
| 814 else: | 898 else: |
| 815 log_desc = CreateDescriptionFromLog(args) | 899 log_desc = CreateDescriptionFromLog(args) |
| 816 if options.from_logs: | 900 change_desc = ChangeDescription(options.message, log_desc, |
| 817 # Uses logs as description and message as subject. | 901 options.reviewers) |
| 818 subject = options.message | 902 if not options.from_logs: |
| 819 change_desc = subject + '\n\n' + log_desc | 903 change_desc.Update() |
| 820 else: | 904 |
| 821 initial_text = """# Enter a description of the change. | 905 if change_desc.IsEmpty(): |
| 822 # This will displayed on the codereview site. | |
| 823 # The first line will also be used as the subject of the review. | |
| 824 """ | |
| 825 if 'BUG=' not in log_desc: | |
| 826 log_desc += '\nBUG=' | |
| 827 if 'TEST=' not in log_desc: | |
| 828 log_desc += '\nTEST=' | |
| 829 change_desc = UserEditedLog(initial_text + log_desc) | |
| 830 subject = '' | |
| 831 if change_desc: | |
| 832 subject = change_desc.splitlines()[0] | |
| 833 if not change_desc: | |
| 834 print "Description is empty; aborting." | 906 print "Description is empty; aborting." |
| 835 return 1 | 907 return 1 |
| 836 upload_args.extend(['--message', subject]) | 908 |
| 837 upload_args.extend(['--description', change_desc]) | 909 upload_args.extend(['--message', change_desc.subject]) |
| 910 upload_args.extend(['--description', change_desc.description]) | |
| 911 if change_desc.reviewers: | |
| 912 upload_args.extend(['--reviewers', change_desc.reviewers]) | |
| 838 cc = ','.join(filter(None, (settings.GetCCList(), options.cc))) | 913 cc = ','.join(filter(None, (settings.GetCCList(), options.cc))) |
| 839 if cc: | 914 if cc: |
| 840 upload_args.extend(['--cc', cc]) | 915 upload_args.extend(['--cc', cc]) |
| 841 | 916 |
| 842 # Include the upstream repo's URL in the change -- this is useful for | 917 # Include the upstream repo's URL in the change -- this is useful for |
| 843 # projects that have their source spread across multiple repos. | 918 # projects that have their source spread across multiple repos. |
| 844 remote_url = None | 919 remote_url = None |
| 845 if settings.GetIsGitSvn(): | 920 if settings.GetIsGitSvn(): |
| 846 # URL is dependent on the current directory. | 921 # URL is dependent on the current directory. |
| 847 data = RunGit(['svn', 'info'], cwd=settings.GetRoot()) | 922 data = RunGit(['svn', 'info'], cwd=settings.GetRoot()) |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 859 try: | 934 try: |
| 860 issue, patchset = upload.RealMain(['upload'] + upload_args + args) | 935 issue, patchset = upload.RealMain(['upload'] + upload_args + args) |
| 861 except: | 936 except: |
| 862 # If we got an exception after the user typed a description for their | 937 # If we got an exception after the user typed a description for their |
| 863 # change, back up the description before re-raising. | 938 # change, back up the description before re-raising. |
| 864 if change_desc: | 939 if change_desc: |
| 865 backup_path = os.path.expanduser(DESCRIPTION_BACKUP_FILE) | 940 backup_path = os.path.expanduser(DESCRIPTION_BACKUP_FILE) |
| 866 print '\nGot exception while uploading -- saving description to %s\n' \ | 941 print '\nGot exception while uploading -- saving description to %s\n' \ |
| 867 % backup_path | 942 % backup_path |
| 868 backup_file = open(backup_path, 'w') | 943 backup_file = open(backup_path, 'w') |
| 869 backup_file.write(change_desc) | 944 backup_file.write(change_desc.description) |
| 870 backup_file.close() | 945 backup_file.close() |
| 871 raise | 946 raise |
| 872 | 947 |
| 873 if not cl.GetIssue(): | 948 if not cl.GetIssue(): |
| 874 cl.SetIssue(issue) | 949 cl.SetIssue(issue) |
| 875 cl.SetPatchset(patchset) | 950 cl.SetPatchset(patchset) |
| 876 return 0 | 951 return 0 |
| 877 | 952 |
| 878 | 953 |
| 879 def SendUpstream(parser, args, cmd): | 954 def SendUpstream(parser, args, cmd): |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 927 svn_head = RunGit(['log', '--grep=^git-svn-id:', '-1', | 1002 svn_head = RunGit(['log', '--grep=^git-svn-id:', '-1', |
| 928 '--pretty=format:%H']) | 1003 '--pretty=format:%H']) |
| 929 extra_commits = RunGit(['rev-list', '^' + svn_head, base_branch]) | 1004 extra_commits = RunGit(['rev-list', '^' + svn_head, base_branch]) |
| 930 if extra_commits: | 1005 if extra_commits: |
| 931 print ('This branch has %d additional commits not upstreamed yet.' | 1006 print ('This branch has %d additional commits not upstreamed yet.' |
| 932 % len(extra_commits.splitlines())) | 1007 % len(extra_commits.splitlines())) |
| 933 print ('Upstream "%s" or rebase this branch on top of the upstream trunk ' | 1008 print ('Upstream "%s" or rebase this branch on top of the upstream trunk ' |
| 934 'before attempting to %s.' % (base_branch, cmd)) | 1009 'before attempting to %s.' % (base_branch, cmd)) |
| 935 return 1 | 1010 return 1 |
| 936 | 1011 |
| 1012 if not options.bypass_hooks: | |
| 1013 RunHook(committing=True, upstream_branch=base_branch, | |
| 1014 rietveld_server=cl.GetRietveldServer(), tbr=options.tbr, | |
| 1015 may_prompt=(not options.force)) | |
| 1016 | |
| 937 if not options.force and not options.bypass_hooks: | 1017 if not options.force and not options.bypass_hooks: |
| 938 RunHook(committing=False, upstream_branch=base_branch) | |
| 939 | |
| 940 if cmd == 'dcommit': | 1018 if cmd == 'dcommit': |
| 941 # Check the tree status if the tree status URL is set. | 1019 # Check the tree status if the tree status URL is set. |
| 942 status = GetTreeStatus() | 1020 status = GetTreeStatus() |
| 943 if 'closed' == status: | 1021 if 'closed' == status: |
| 944 print ('The tree is closed. Please wait for it to reopen. Use ' | 1022 print ('The tree is closed. Please wait for it to reopen. Use ' |
| 945 '"git cl dcommit -f" to commit on a closed tree.') | 1023 '"git cl dcommit -f" to commit on a closed tree.') |
| 946 return 1 | 1024 return 1 |
| 947 elif 'unknown' == status: | 1025 elif 'unknown' == status: |
| 948 print ('Unable to determine tree status. Please verify manually and ' | 1026 print ('Unable to determine tree status. Please verify manually and ' |
| 949 'use "git cl dcommit -f" to commit on a closed tree.') | 1027 'use "git cl dcommit -f" to commit on a closed tree.') |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 968 | 1046 |
| 969 description += "\n\nReview URL: %s" % cl.GetIssueURL() | 1047 description += "\n\nReview URL: %s" % cl.GetIssueURL() |
| 970 else: | 1048 else: |
| 971 if not description: | 1049 if not description: |
| 972 # Submitting TBR. See if there's already a description in Rietveld, else | 1050 # Submitting TBR. See if there's already a description in Rietveld, else |
| 973 # create a template description. Eitherway, give the user a chance to edit | 1051 # create a template description. Eitherway, give the user a chance to edit |
| 974 # it to fill in the TBR= field. | 1052 # it to fill in the TBR= field. |
| 975 if cl.GetIssue(): | 1053 if cl.GetIssue(): |
| 976 description = cl.GetDescription() | 1054 description = cl.GetDescription() |
| 977 | 1055 |
| 1056 # TODO(dpranke): Update to use ChangeDescription object. | |
| 978 if not description: | 1057 if not description: |
| 979 description = """# Enter a description of the change. | 1058 description = """# Enter a description of the change. |
| 980 # This will be used as the change log for the commit. | 1059 # This will be used as the change log for the commit. |
| 981 | 1060 |
| 982 """ | 1061 """ |
| 983 description += CreateDescriptionFromLog(args) | 1062 description += CreateDescriptionFromLog(args) |
| 984 | 1063 |
| 985 description = UserEditedLog(description + '\nTBR=') | 1064 description = UserEditedLog(description + '\nTBR=') |
| 986 | 1065 |
| 987 if not description: | 1066 if not description: |
| (...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1059 if viewvc_url and revision: | 1138 if viewvc_url and revision: |
| 1060 cl.description += ('\n\nCommitted: ' + viewvc_url + revision) | 1139 cl.description += ('\n\nCommitted: ' + viewvc_url + revision) |
| 1061 print ('Closing issue ' | 1140 print ('Closing issue ' |
| 1062 '(you may be prompted for your codereview password)...') | 1141 '(you may be prompted for your codereview password)...') |
| 1063 cl.CloseIssue() | 1142 cl.CloseIssue() |
| 1064 cl.SetIssue(0) | 1143 cl.SetIssue(0) |
| 1065 | 1144 |
| 1066 if retcode == 0: | 1145 if retcode == 0: |
| 1067 hook = POSTUPSTREAM_HOOK_PATTERN % cmd | 1146 hook = POSTUPSTREAM_HOOK_PATTERN % cmd |
| 1068 if os.path.isfile(hook): | 1147 if os.path.isfile(hook): |
| 1069 RunHook(hook, upstream_branch=base_branch, error_ok=True) | 1148 RunCommand([hook, base_branch], error_ok=True) |
| 1070 | 1149 |
| 1071 return 0 | 1150 return 0 |
| 1072 | 1151 |
| 1073 | 1152 |
| 1074 @usage('[upstream branch to apply against]') | 1153 @usage('[upstream branch to apply against]') |
| 1075 def CMDdcommit(parser, args): | 1154 def CMDdcommit(parser, args): |
| 1076 """commit the current changelist via git-svn""" | 1155 """commit the current changelist via git-svn""" |
| 1077 if not settings.GetIsGitSvn(): | 1156 if not settings.GetIsGitSvn(): |
| 1078 message = """This doesn't appear to be an SVN repository. | 1157 message = """This doesn't appear to be an SVN repository. |
| 1079 If your project has a git mirror with an upstream SVN master, you probably need | 1158 If your project has a git mirror with an upstream SVN master, you probably need |
| (...skipping 115 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1195 """Fetches the tree status and returns either 'open', 'closed', | 1274 """Fetches the tree status and returns either 'open', 'closed', |
| 1196 'unknown' or 'unset'.""" | 1275 'unknown' or 'unset'.""" |
| 1197 url = settings.GetTreeStatusUrl(error_ok=True) | 1276 url = settings.GetTreeStatusUrl(error_ok=True) |
| 1198 if url: | 1277 if url: |
| 1199 status = urllib2.urlopen(url).read().lower() | 1278 status = urllib2.urlopen(url).read().lower() |
| 1200 if status.find('closed') != -1 or status == '0': | 1279 if status.find('closed') != -1 or status == '0': |
| 1201 return 'closed' | 1280 return 'closed' |
| 1202 elif status.find('open') != -1 or status == '1': | 1281 elif status.find('open') != -1 or status == '1': |
| 1203 return 'open' | 1282 return 'open' |
| 1204 return 'unknown' | 1283 return 'unknown' |
| 1284 return 'unset' | |
| 1205 | 1285 |
| 1206 return 'unset' | |
| 1207 | 1286 |
| 1208 def GetTreeStatusReason(): | 1287 def GetTreeStatusReason(): |
| 1209 """Fetches the tree status from a json url and returns the message | 1288 """Fetches the tree status from a json url and returns the message |
| 1210 with the reason for the tree to be opened or closed.""" | 1289 with the reason for the tree to be opened or closed.""" |
| 1211 # Don't import it at file level since simplejson is not installed by default | 1290 # Don't import it at file level since simplejson is not installed by default |
| 1212 # on python 2.5 and it is only used for git-cl tree which isn't often used, | 1291 # on python 2.5 and it is only used for git-cl tree which isn't often used, |
| 1213 # forcing everyone to install simplejson isn't efficient. | 1292 # forcing everyone to install simplejson isn't efficient. |
| 1214 try: | 1293 try: |
| 1215 import simplejson as json | 1294 import simplejson as json |
| 1216 except ImportError: | 1295 except ImportError: |
| 1217 try: | 1296 try: |
| 1218 import json | 1297 import json |
| 1219 # Some versions of python2.5 have an incomplete json module. Check to make | 1298 # Some versions of python2.5 have an incomplete json module. Check to make |
| 1220 # sure loads exists. | 1299 # sure loads exists. |
| 1221 json.loads | 1300 json.loads |
| 1222 except (ImportError, AttributeError): | 1301 except (ImportError, AttributeError): |
| 1223 print >> sys.stderr, 'Please install simplejson' | 1302 print >> sys.stderr, 'Please install simplejson' |
| 1224 sys.exit(1) | 1303 sys.exit(1) |
| 1225 | 1304 |
| 1226 url = settings.GetTreeStatusUrl() | 1305 url = settings.GetTreeStatusUrl() |
| 1227 json_url = urlparse.urljoin(url, '/current?format=json') | 1306 json_url = urlparse.urljoin(url, '/current?format=json') |
| 1228 connection = urllib2.urlopen(json_url) | 1307 connection = urllib2.urlopen(json_url) |
| 1229 status = json.loads(connection.read()) | 1308 status = json.loads(connection.read()) |
| 1230 connection.close() | 1309 connection.close() |
| 1231 return status['message'] | 1310 return status['message'] |
| 1232 | 1311 |
| 1312 | |
| 1233 def CMDtree(parser, args): | 1313 def CMDtree(parser, args): |
| 1234 """show the status of the tree""" | 1314 """show the status of the tree""" |
| 1235 (options, args) = parser.parse_args(args) | 1315 (options, args) = parser.parse_args(args) |
| 1236 status = GetTreeStatus() | 1316 status = GetTreeStatus() |
| 1237 if 'unset' == status: | 1317 if 'unset' == status: |
| 1238 print 'You must configure your tree status URL by running "git cl config".' | 1318 print 'You must configure your tree status URL by running "git cl config".' |
| 1239 return 2 | 1319 return 2 |
| 1240 | 1320 |
| 1241 print "The tree is %s" % status | 1321 print "The tree is %s" % status |
| 1242 print | 1322 print |
| (...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1314 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' | 1394 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' |
| 1315 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) | 1395 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) |
| 1316 | 1396 |
| 1317 # Not a known command. Default to help. | 1397 # Not a known command. Default to help. |
| 1318 GenUsage(parser, 'help') | 1398 GenUsage(parser, 'help') |
| 1319 return CMDhelp(parser, argv) | 1399 return CMDhelp(parser, argv) |
| 1320 | 1400 |
| 1321 | 1401 |
| 1322 if __name__ == '__main__': | 1402 if __name__ == '__main__': |
| 1323 sys.exit(main(sys.argv[1:])) | 1403 sys.exit(main(sys.argv[1:])) |
| OLD | NEW |