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 |