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 subprocess | 10 import subprocess |
11 import sys | 11 import sys |
12 import tempfile | 12 import tempfile |
13 import textwrap | 13 import textwrap |
14 import upload | 14 import upload |
15 import urlparse | 15 import urlparse |
16 import urllib2 | 16 import urllib2 |
17 | 17 |
18 try: | 18 try: |
19 import readline | 19 import readline |
20 except ImportError: | 20 except ImportError: |
21 pass | 21 pass |
22 | 22 |
23 try: | 23 try: |
24 # Add the parent directory in case it's a depot_tools checkout. | 24 # TODO(dpranke): We wrap this in a try block for a limited form of |
M-A Ruel
2011/03/11 14:42:52
My take is to move git_cl up a directory first and
| |
25 # backwards-compatibility with older versions of git-cl that weren't | |
26 # dependent on depot_tools. This version should still work outside of | |
27 # depot_tools as long as --bypass-hooks is used. We should remove this | |
28 # once this has baked for a while and things seem safe. | |
25 depot_tools_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | 29 depot_tools_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
26 sys.path.append(depot_tools_path) | 30 sys.path.append(depot_tools_path) |
27 import breakpad | 31 import breakpad |
28 except ImportError: | 32 except ImportError: |
29 pass | 33 pass |
30 | 34 |
31 DEFAULT_SERVER = 'http://codereview.appspot.com' | 35 DEFAULT_SERVER = 'http://codereview.appspot.com' |
32 PREDCOMMIT_HOOK = '.git/hooks/pre-cl-dcommit' | |
33 POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s' | |
34 PREUPLOAD_HOOK = '.git/hooks/pre-cl-upload' | |
35 DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup' | 36 DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup' |
36 | 37 |
37 def DieWithError(message): | 38 def DieWithError(message): |
38 print >>sys.stderr, message | 39 print >>sys.stderr, message |
39 sys.exit(1) | 40 sys.exit(1) |
40 | 41 |
41 | 42 |
42 def Popen(cmd, **kwargs): | 43 def Popen(cmd, **kwargs): |
43 """Wrapper for subprocess.Popen() that logs and watch for cygwin issues""" | 44 """Wrapper for subprocess.Popen() that logs and watch for cygwin issues""" |
44 logging.info('Popen: ' + ' '.join(cmd)) | 45 logging.info('Popen: ' + ' '.join(cmd)) |
(...skipping 489 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
534 SetProperty('tree-status-url', 'STATUS', unset_error_ok=True) | 535 SetProperty('tree-status-url', 'STATUS', unset_error_ok=True) |
535 SetProperty('viewvc-url', 'VIEW_VC', unset_error_ok=True) | 536 SetProperty('viewvc-url', 'VIEW_VC', unset_error_ok=True) |
536 | 537 |
537 if 'PUSH_URL_CONFIG' in keyvals and 'ORIGIN_URL_CONFIG' in keyvals: | 538 if 'PUSH_URL_CONFIG' in keyvals and 'ORIGIN_URL_CONFIG' in keyvals: |
538 #should be of the form | 539 #should be of the form |
539 #PUSH_URL_CONFIG: url.ssh://gitrw.chromium.org.pushinsteadof | 540 #PUSH_URL_CONFIG: url.ssh://gitrw.chromium.org.pushinsteadof |
540 #ORIGIN_URL_CONFIG: http://src.chromium.org/git | 541 #ORIGIN_URL_CONFIG: http://src.chromium.org/git |
541 RunGit(['config', keyvals['PUSH_URL_CONFIG'], | 542 RunGit(['config', keyvals['PUSH_URL_CONFIG'], |
542 keyvals['ORIGIN_URL_CONFIG']]) | 543 keyvals['ORIGIN_URL_CONFIG']]) |
543 | 544 |
544 # Update the hooks if the local hook files aren't present already. | |
545 if GetProperty('GITCL_PREUPLOAD') and not os.path.isfile(PREUPLOAD_HOOK): | |
546 DownloadToFile(GetProperty('GITCL_PREUPLOAD'), PREUPLOAD_HOOK) | |
547 if GetProperty('GITCL_PREDCOMMIT') and not os.path.isfile(PREDCOMMIT_HOOK): | |
548 DownloadToFile(GetProperty('GITCL_PREDCOMMIT'), PREDCOMMIT_HOOK) | |
549 return 0 | |
550 | |
551 | 545 |
552 @usage('[repo root containing codereview.settings]') | 546 @usage('[repo root containing codereview.settings]') |
553 def CMDconfig(parser, args): | 547 def CMDconfig(parser, args): |
554 """edit configuration for this tree""" | 548 """edit configuration for this tree""" |
555 | 549 |
556 (options, args) = parser.parse_args(args) | 550 (options, args) = parser.parse_args(args) |
557 if len(args) == 0: | 551 if len(args) == 0: |
558 GetCodereviewSettingsInteractively() | 552 GetCodereviewSettingsInteractively() |
559 return 0 | 553 return 0 |
560 | 554 |
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
662 return | 656 return |
663 | 657 |
664 fileobj = open(filename) | 658 fileobj = open(filename) |
665 text = fileobj.read() | 659 text = fileobj.read() |
666 fileobj.close() | 660 fileobj.close() |
667 os.remove(filename) | 661 os.remove(filename) |
668 stripcomment_re = re.compile(r'^#.*$', re.MULTILINE) | 662 stripcomment_re = re.compile(r'^#.*$', re.MULTILINE) |
669 return stripcomment_re.sub('', text).strip() | 663 return stripcomment_re.sub('', text).strip() |
670 | 664 |
671 | 665 |
672 def RunHook(hook, upstream_branch, error_ok=False): | 666 def ConvertToInteger(inputval): |
M-A Ruel
2011/03/11 14:42:52
(random idea)
def safe_convert(inputval, outputtyp
| |
673 """Run a given hook if it exists. By default, we fail on errors.""" | 667 """Convert a string to integer, but returns either an int or None.""" |
674 hook = '%s/%s' % (settings.GetRoot(), hook) | 668 try: |
675 if not os.path.exists(hook): | 669 return int(inputval) |
676 return | 670 except (TypeError, ValueError): |
677 return RunCommand([hook, upstream_branch], error_ok=error_ok, | 671 return None |
678 redirect_stdout=False) | 672 |
673 | |
674 def RunHook(committing, upstream_branch): | |
675 import presubmit_support | |
676 import scm | |
677 import watchlists | |
678 | |
679 root = RunCommand(['git', 'rev-parse', '--show-cdup']).strip() | |
680 if not root: | |
681 root = "." | |
682 absroot = os.path.abspath(root) | |
683 if not root: | |
684 raise Exception("Could not get root directory.") | |
685 | |
686 # We use the sha1 of HEAD as a name of this change. | |
687 name = RunCommand(['git', 'rev-parse', 'HEAD']).strip() | |
688 files = scm.GIT.CaptureStatus([root], upstream_branch) | |
689 | |
690 cl = Changelist() | |
691 issue = ConvertToInteger(cl.GetIssue()) | |
692 patchset = ConvertToInteger(cl.GetPatchset()) | |
693 if issue: | |
694 description = cl.GetDescription() | |
695 else: | |
696 # If the change was never uploaded, use the log messages of all commits | |
697 # up to the branch point, as git cl upload will prefill the description | |
698 # with these log messages. | |
699 description = RunCommand(['git', 'log', '--pretty=format:%s%n%n%b', | |
700 '%s...' % (upstream_branch)]).strip() | |
701 change = presubmit_support.GitChange(name, description, absroot, files, | |
702 issue, patchset) | |
703 | |
704 # Apply watchlists on upload. | |
705 if not committing: | |
706 watchlist = watchlists.Watchlists(change.RepositoryRoot()) | |
707 files = [f.LocalPath() for f in change.AffectedFiles()] | |
708 watchers = watchlist.GetWatchersForPaths(files) | |
709 RunCommand(['git', 'config', '--replace-all', | |
710 'rietveld.extracc', ','.join(watchers)]) | |
711 | |
712 return presubmit_support.DoPresubmitChecks(change, committing, | |
M-A Ruel
2011/03/11 14:42:52
It changes from running out of process to in-proce
| |
713 verbose=None, output_stream=sys.stdout, input_stream=sys.stdin, | |
714 default_presubmit=None, may_prompt=None) | |
679 | 715 |
680 | 716 |
681 def CMDpresubmit(parser, args): | 717 def CMDpresubmit(parser, args): |
682 """run presubmit tests on the current changelist""" | 718 """run presubmit tests on the current changelist""" |
683 parser.add_option('--upload', action='store_true', | 719 parser.add_option('--upload', action='store_true', |
684 help='Run upload hook instead of the push/dcommit hook') | 720 help='Run upload hook instead of the push/dcommit hook') |
685 (options, args) = parser.parse_args(args) | 721 (options, args) = parser.parse_args(args) |
686 | 722 |
687 # Make sure index is up-to-date before running diff-index. | 723 # Make sure index is up-to-date before running diff-index. |
688 RunGit(['update-index', '--refresh', '-q'], error_ok=True) | 724 RunGit(['update-index', '--refresh', '-q'], error_ok=True) |
689 if RunGit(['diff-index', 'HEAD']): | 725 if RunGit(['diff-index', 'HEAD']): |
690 # TODO(maruel): Is this really necessary? | 726 # TODO(maruel): Is this really necessary? |
691 print 'Cannot presubmit with a dirty tree. You must commit locally first.' | 727 print 'Cannot presubmit with a dirty tree. You must commit locally first.' |
692 return 1 | 728 return 1 |
693 | 729 |
694 if args: | 730 if args: |
695 base_branch = args[0] | 731 base_branch = args[0] |
696 else: | 732 else: |
697 # Default to diffing against the "upstream" branch. | 733 # Default to diffing against the "upstream" branch. |
698 base_branch = Changelist().GetUpstreamBranch() | 734 base_branch = Changelist().GetUpstreamBranch() |
699 | 735 |
700 if options.upload: | 736 if options.upload: |
701 print '*** Presubmit checks for UPLOAD would report: ***' | 737 print '*** Presubmit checks for UPLOAD would report: ***' |
702 return not RunHook(PREUPLOAD_HOOK, upstream_branch=base_branch, | 738 return RunHook(committing=False, upstream_branch=base_branch) |
703 error_ok=True) | |
704 else: | 739 else: |
705 print '*** Presubmit checks for DCOMMIT would report: ***' | 740 print '*** Presubmit checks for DCOMMIT would report: ***' |
706 return not RunHook(PREDCOMMIT_HOOK, upstream_branch=base_branch, | 741 return RunHook(committing=True, upstream_branch=base_branch) |
707 error_ok=True) | |
708 | 742 |
709 | 743 |
710 @usage('[args to "git diff"]') | 744 @usage('[args to "git diff"]') |
711 def CMDupload(parser, args): | 745 def CMDupload(parser, args): |
712 """upload the current changelist to codereview""" | 746 """upload the current changelist to codereview""" |
713 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks', | 747 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks', |
714 help='bypass upload presubmit hook') | 748 help='bypass upload presubmit hook') |
715 parser.add_option('-m', dest='message', help='message for patch') | 749 parser.add_option('-m', dest='message', help='message for patch') |
716 parser.add_option('-r', '--reviewers', | 750 parser.add_option('-r', '--reviewers', |
717 help='reviewer email addresses') | 751 help='reviewer email addresses') |
(...skipping 18 matching lines...) Expand all Loading... | |
736 | 770 |
737 cl = Changelist() | 771 cl = Changelist() |
738 if args: | 772 if args: |
739 base_branch = args[0] | 773 base_branch = args[0] |
740 else: | 774 else: |
741 # Default to diffing against the "upstream" branch. | 775 # Default to diffing against the "upstream" branch. |
742 base_branch = cl.GetUpstreamBranch() | 776 base_branch = cl.GetUpstreamBranch() |
743 args = [base_branch + "..."] | 777 args = [base_branch + "..."] |
744 | 778 |
745 if not options.bypass_hooks: | 779 if not options.bypass_hooks: |
746 RunHook(PREUPLOAD_HOOK, upstream_branch=base_branch, error_ok=False) | 780 RunHook(committing=False, upstream_branch=base_branch) |
747 | 781 |
748 # --no-ext-diff is broken in some versions of Git, so try to work around | 782 # --no-ext-diff is broken in some versions of Git, so try to work around |
749 # this by overriding the environment (but there is still a problem if the | 783 # this by overriding the environment (but there is still a problem if the |
750 # git config key "diff.external" is used). | 784 # git config key "diff.external" is used). |
751 env = os.environ.copy() | 785 env = os.environ.copy() |
752 if 'GIT_EXTERNAL_DIFF' in env: | 786 if 'GIT_EXTERNAL_DIFF' in env: |
753 del env['GIT_EXTERNAL_DIFF'] | 787 del env['GIT_EXTERNAL_DIFF'] |
754 subprocess.call(['git', 'diff', '--no-ext-diff', '--stat', '-M'] + args, | 788 subprocess.call(['git', 'diff', '--no-ext-diff', '--stat', '-M'] + args, |
755 env=env) | 789 env=env) |
756 | 790 |
(...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
893 '--pretty=format:%H']) | 927 '--pretty=format:%H']) |
894 extra_commits = RunGit(['rev-list', '^' + svn_head, base_branch]) | 928 extra_commits = RunGit(['rev-list', '^' + svn_head, base_branch]) |
895 if extra_commits: | 929 if extra_commits: |
896 print ('This branch has %d additional commits not upstreamed yet.' | 930 print ('This branch has %d additional commits not upstreamed yet.' |
897 % len(extra_commits.splitlines())) | 931 % len(extra_commits.splitlines())) |
898 print ('Upstream "%s" or rebase this branch on top of the upstream trunk ' | 932 print ('Upstream "%s" or rebase this branch on top of the upstream trunk ' |
899 'before attempting to %s.' % (base_branch, cmd)) | 933 'before attempting to %s.' % (base_branch, cmd)) |
900 return 1 | 934 return 1 |
901 | 935 |
902 if not options.force and not options.bypass_hooks: | 936 if not options.force and not options.bypass_hooks: |
903 RunHook(PREDCOMMIT_HOOK, upstream_branch=base_branch, error_ok=False) | 937 RunHook(committing=False, upstream_branch=base_branch) |
904 | 938 |
905 if cmd == 'dcommit': | 939 if cmd == 'dcommit': |
906 # Check the tree status if the tree status URL is set. | 940 # Check the tree status if the tree status URL is set. |
907 status = GetTreeStatus() | 941 status = GetTreeStatus() |
908 if 'closed' == status: | 942 if 'closed' == status: |
909 print ('The tree is closed. Please wait for it to reopen. Use ' | 943 print ('The tree is closed. Please wait for it to reopen. Use ' |
910 '"git cl dcommit -f" to commit on a closed tree.') | 944 '"git cl dcommit -f" to commit on a closed tree.') |
911 return 1 | 945 return 1 |
912 elif 'unknown' == status: | 946 elif 'unknown' == status: |
913 print ('Unable to determine tree status. Please verify manually and ' | 947 print ('Unable to determine tree status. Please verify manually and ' |
(...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1021 else: | 1055 else: |
1022 return 1 | 1056 return 1 |
1023 viewvc_url = settings.GetViewVCUrl() | 1057 viewvc_url = settings.GetViewVCUrl() |
1024 if viewvc_url and revision: | 1058 if viewvc_url and revision: |
1025 cl.description += ('\n\nCommitted: ' + viewvc_url + revision) | 1059 cl.description += ('\n\nCommitted: ' + viewvc_url + revision) |
1026 print ('Closing issue ' | 1060 print ('Closing issue ' |
1027 '(you may be prompted for your codereview password)...') | 1061 '(you may be prompted for your codereview password)...') |
1028 cl.CloseIssue() | 1062 cl.CloseIssue() |
1029 cl.SetIssue(0) | 1063 cl.SetIssue(0) |
1030 | 1064 |
1031 if retcode == 0: | |
1032 hook = POSTUPSTREAM_HOOK_PATTERN % cmd | |
1033 if os.path.isfile(hook): | |
1034 RunHook(hook, upstream_branch=base_branch, error_ok=True) | |
1035 | |
1036 return 0 | 1065 return 0 |
1037 | 1066 |
1038 | 1067 |
1039 @usage('[upstream branch to apply against]') | 1068 @usage('[upstream branch to apply against]') |
1040 def CMDdcommit(parser, args): | 1069 def CMDdcommit(parser, args): |
1041 """commit the current changelist via git-svn""" | 1070 """commit the current changelist via git-svn""" |
1042 if not settings.GetIsGitSvn(): | 1071 if not settings.GetIsGitSvn(): |
1043 message = """This doesn't appear to be an SVN repository. | 1072 message = """This doesn't appear to be an SVN repository. |
1044 If your project has a git mirror with an upstream SVN master, you probably need | 1073 If your project has a git mirror with an upstream SVN master, you probably need |
1045 to run 'git svn init', see your project's git mirror documentation. | 1074 to run 'git svn init', see your project's git mirror documentation. |
(...skipping 233 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1279 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' | 1308 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' |
1280 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) | 1309 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) |
1281 | 1310 |
1282 # Not a known command. Default to help. | 1311 # Not a known command. Default to help. |
1283 GenUsage(parser, 'help') | 1312 GenUsage(parser, 'help') |
1284 return CMDhelp(parser, argv) | 1313 return CMDhelp(parser, argv) |
1285 | 1314 |
1286 | 1315 |
1287 if __name__ == '__main__': | 1316 if __name__ == '__main__': |
1288 sys.exit(main(sys.argv[1:])) | 1317 sys.exit(main(sys.argv[1:])) |
OLD | NEW |