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 |
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' | 36 POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s' |
34 PREUPLOAD_HOOK = '.git/hooks/pre-cl-upload' | |
35 DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup' | 37 DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup' |
36 | 38 |
37 def DieWithError(message): | 39 def DieWithError(message): |
38 print >>sys.stderr, message | 40 print >>sys.stderr, message |
39 sys.exit(1) | 41 sys.exit(1) |
40 | 42 |
41 | 43 |
42 def Popen(cmd, **kwargs): | 44 def Popen(cmd, **kwargs): |
43 """Wrapper for subprocess.Popen() that logs and watch for cygwin issues""" | 45 """Wrapper for subprocess.Popen() that logs and watch for cygwin issues""" |
44 logging.info('Popen: ' + ' '.join(cmd)) | 46 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) | 536 SetProperty('tree-status-url', 'STATUS', unset_error_ok=True) |
535 SetProperty('viewvc-url', 'VIEW_VC', unset_error_ok=True) | 537 SetProperty('viewvc-url', 'VIEW_VC', unset_error_ok=True) |
536 | 538 |
537 if 'PUSH_URL_CONFIG' in keyvals and 'ORIGIN_URL_CONFIG' in keyvals: | 539 if 'PUSH_URL_CONFIG' in keyvals and 'ORIGIN_URL_CONFIG' in keyvals: |
538 #should be of the form | 540 #should be of the form |
539 #PUSH_URL_CONFIG: url.ssh://gitrw.chromium.org.pushinsteadof | 541 #PUSH_URL_CONFIG: url.ssh://gitrw.chromium.org.pushinsteadof |
540 #ORIGIN_URL_CONFIG: http://src.chromium.org/git | 542 #ORIGIN_URL_CONFIG: http://src.chromium.org/git |
541 RunGit(['config', keyvals['PUSH_URL_CONFIG'], | 543 RunGit(['config', keyvals['PUSH_URL_CONFIG'], |
542 keyvals['ORIGIN_URL_CONFIG']]) | 544 keyvals['ORIGIN_URL_CONFIG']]) |
543 | 545 |
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 | 546 |
552 @usage('[repo root containing codereview.settings]') | 547 @usage('[repo root containing codereview.settings]') |
553 def CMDconfig(parser, args): | 548 def CMDconfig(parser, args): |
554 """edit configuration for this tree""" | 549 """edit configuration for this tree""" |
555 | 550 |
556 (options, args) = parser.parse_args(args) | 551 (options, args) = parser.parse_args(args) |
557 if len(args) == 0: | 552 if len(args) == 0: |
558 GetCodereviewSettingsInteractively() | 553 GetCodereviewSettingsInteractively() |
559 return 0 | 554 return 0 |
560 | 555 |
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
662 return | 657 return |
663 | 658 |
664 fileobj = open(filename) | 659 fileobj = open(filename) |
665 text = fileobj.read() | 660 text = fileobj.read() |
666 fileobj.close() | 661 fileobj.close() |
667 os.remove(filename) | 662 os.remove(filename) |
668 stripcomment_re = re.compile(r'^#.*$', re.MULTILINE) | 663 stripcomment_re = re.compile(r'^#.*$', re.MULTILINE) |
669 return stripcomment_re.sub('', text).strip() | 664 return stripcomment_re.sub('', text).strip() |
670 | 665 |
671 | 666 |
672 def RunHook(hook, upstream_branch, error_ok=False): | 667 def ConvertToInteger(inputval): |
673 """Run a given hook if it exists. By default, we fail on errors.""" | 668 """Convert a string to integer, but returns either an int or None.""" |
674 hook = '%s/%s' % (settings.GetRoot(), hook) | 669 try: |
675 if not os.path.exists(hook): | 670 return int(inputval) |
676 return | 671 except (TypeError, ValueError): |
677 return RunCommand([hook, upstream_branch], error_ok=error_ok, | 672 return None |
678 redirect_stdout=False) | 673 |
674 | |
675 def RunHook(committing, upstream_branch): | |
676 import presubmit_support | |
677 import scm | |
678 import watchlists | |
679 | |
680 root = RunCommand(['git', 'rev-parse', '--show-cdup']).strip() | |
681 if not root: | |
682 root = "." | |
chase
2011/03/14 21:05:47
my original lines in git_cl_hooks.py should have u
| |
683 absroot = os.path.abspath(root) | |
684 if not root: | |
685 raise Exception("Could not get root directory.") | |
chase
2011/03/14 21:05:47
my original lines in git_cl_hooks.py should have u
| |
686 | |
687 # We use the sha1 of HEAD as a name of this change. | |
688 name = RunCommand(['git', 'rev-parse', 'HEAD']).strip() | |
689 files = scm.GIT.CaptureStatus([root], upstream_branch) | |
690 | |
691 cl = Changelist() | |
692 issue = ConvertToInteger(cl.GetIssue()) | |
693 patchset = ConvertToInteger(cl.GetPatchset()) | |
694 if issue: | |
695 description = cl.GetDescription() | |
696 else: | |
697 # If the change was never uploaded, use the log messages of all commits | |
698 # up to the branch point, as git cl upload will prefill the description | |
699 # with these log messages. | |
700 description = RunCommand(['git', 'log', '--pretty=format:%s%n%n%b', | |
701 '%s...' % (upstream_branch)]).strip() | |
chase
2011/03/14 21:05:47
indent one char
| |
702 change = presubmit_support.GitChange(name, description, absroot, files, | |
703 issue, patchset) | |
704 | |
705 # Apply watchlists on upload. | |
706 if not committing: | |
707 watchlist = watchlists.Watchlists(change.RepositoryRoot()) | |
708 files = [f.LocalPath() for f in change.AffectedFiles()] | |
709 watchers = watchlist.GetWatchersForPaths(files) | |
710 RunCommand(['git', 'config', '--replace-all', | |
711 'rietveld.extracc', ','.join(watchers)]) | |
chase
2011/03/14 21:05:47
indent one char
| |
712 | |
713 return presubmit_support.DoPresubmitChecks(change, committing, | |
714 verbose=None, output_stream=sys.stdout, input_stream=sys.stdin, | |
715 default_presubmit=None, may_prompt=None) | |
679 | 716 |
680 | 717 |
681 def CMDpresubmit(parser, args): | 718 def CMDpresubmit(parser, args): |
682 """run presubmit tests on the current changelist""" | 719 """run presubmit tests on the current changelist""" |
683 parser.add_option('--upload', action='store_true', | 720 parser.add_option('--upload', action='store_true', |
684 help='Run upload hook instead of the push/dcommit hook') | 721 help='Run upload hook instead of the push/dcommit hook') |
685 (options, args) = parser.parse_args(args) | 722 (options, args) = parser.parse_args(args) |
686 | 723 |
687 # Make sure index is up-to-date before running diff-index. | 724 # Make sure index is up-to-date before running diff-index. |
688 RunGit(['update-index', '--refresh', '-q'], error_ok=True) | 725 RunGit(['update-index', '--refresh', '-q'], error_ok=True) |
689 if RunGit(['diff-index', 'HEAD']): | 726 if RunGit(['diff-index', 'HEAD']): |
690 # TODO(maruel): Is this really necessary? | 727 # TODO(maruel): Is this really necessary? |
691 print 'Cannot presubmit with a dirty tree. You must commit locally first.' | 728 print 'Cannot presubmit with a dirty tree. You must commit locally first.' |
692 return 1 | 729 return 1 |
693 | 730 |
694 if args: | 731 if args: |
695 base_branch = args[0] | 732 base_branch = args[0] |
696 else: | 733 else: |
697 # Default to diffing against the "upstream" branch. | 734 # Default to diffing against the "upstream" branch. |
698 base_branch = Changelist().GetUpstreamBranch() | 735 base_branch = Changelist().GetUpstreamBranch() |
699 | 736 |
700 if options.upload: | 737 if options.upload: |
701 print '*** Presubmit checks for UPLOAD would report: ***' | 738 print '*** Presubmit checks for UPLOAD would report: ***' |
702 return not RunHook(PREUPLOAD_HOOK, upstream_branch=base_branch, | 739 return RunHook(committing=False, upstream_branch=base_branch) |
703 error_ok=True) | |
704 else: | 740 else: |
705 print '*** Presubmit checks for DCOMMIT would report: ***' | 741 print '*** Presubmit checks for DCOMMIT would report: ***' |
706 return not RunHook(PREDCOMMIT_HOOK, upstream_branch=base_branch, | 742 return RunHook(committing=True, upstream_branch=base_branch) |
707 error_ok=True) | |
708 | 743 |
709 | 744 |
710 @usage('[args to "git diff"]') | 745 @usage('[args to "git diff"]') |
711 def CMDupload(parser, args): | 746 def CMDupload(parser, args): |
712 """upload the current changelist to codereview""" | 747 """upload the current changelist to codereview""" |
713 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks', | 748 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks', |
714 help='bypass upload presubmit hook') | 749 help='bypass upload presubmit hook') |
715 parser.add_option('-m', dest='message', help='message for patch') | 750 parser.add_option('-m', dest='message', help='message for patch') |
716 parser.add_option('-r', '--reviewers', | 751 parser.add_option('-r', '--reviewers', |
717 help='reviewer email addresses') | 752 help='reviewer email addresses') |
(...skipping 18 matching lines...) Expand all Loading... | |
736 | 771 |
737 cl = Changelist() | 772 cl = Changelist() |
738 if args: | 773 if args: |
739 base_branch = args[0] | 774 base_branch = args[0] |
740 else: | 775 else: |
741 # Default to diffing against the "upstream" branch. | 776 # Default to diffing against the "upstream" branch. |
742 base_branch = cl.GetUpstreamBranch() | 777 base_branch = cl.GetUpstreamBranch() |
743 args = [base_branch + "..."] | 778 args = [base_branch + "..."] |
744 | 779 |
745 if not options.bypass_hooks: | 780 if not options.bypass_hooks: |
746 RunHook(PREUPLOAD_HOOK, upstream_branch=base_branch, error_ok=False) | 781 RunHook(committing=False, upstream_branch=base_branch) |
747 | 782 |
748 # --no-ext-diff is broken in some versions of Git, so try to work around | 783 # --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 | 784 # this by overriding the environment (but there is still a problem if the |
750 # git config key "diff.external" is used). | 785 # git config key "diff.external" is used). |
751 env = os.environ.copy() | 786 env = os.environ.copy() |
752 if 'GIT_EXTERNAL_DIFF' in env: | 787 if 'GIT_EXTERNAL_DIFF' in env: |
753 del env['GIT_EXTERNAL_DIFF'] | 788 del env['GIT_EXTERNAL_DIFF'] |
754 subprocess.call(['git', 'diff', '--no-ext-diff', '--stat', '-M'] + args, | 789 subprocess.call(['git', 'diff', '--no-ext-diff', '--stat', '-M'] + args, |
755 env=env) | 790 env=env) |
756 | 791 |
(...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
893 '--pretty=format:%H']) | 928 '--pretty=format:%H']) |
894 extra_commits = RunGit(['rev-list', '^' + svn_head, base_branch]) | 929 extra_commits = RunGit(['rev-list', '^' + svn_head, base_branch]) |
895 if extra_commits: | 930 if extra_commits: |
896 print ('This branch has %d additional commits not upstreamed yet.' | 931 print ('This branch has %d additional commits not upstreamed yet.' |
897 % len(extra_commits.splitlines())) | 932 % len(extra_commits.splitlines())) |
898 print ('Upstream "%s" or rebase this branch on top of the upstream trunk ' | 933 print ('Upstream "%s" or rebase this branch on top of the upstream trunk ' |
899 'before attempting to %s.' % (base_branch, cmd)) | 934 'before attempting to %s.' % (base_branch, cmd)) |
900 return 1 | 935 return 1 |
901 | 936 |
902 if not options.force and not options.bypass_hooks: | 937 if not options.force and not options.bypass_hooks: |
903 RunHook(PREDCOMMIT_HOOK, upstream_branch=base_branch, error_ok=False) | 938 RunHook(committing=False, upstream_branch=base_branch) |
chase
2011/03/14 21:05:47
<strike>committing=True to match PREDCOMMIT_HOOK</
| |
904 | 939 |
905 if cmd == 'dcommit': | 940 if cmd == 'dcommit': |
906 # Check the tree status if the tree status URL is set. | 941 # Check the tree status if the tree status URL is set. |
907 status = GetTreeStatus() | 942 status = GetTreeStatus() |
908 if 'closed' == status: | 943 if 'closed' == status: |
909 print ('The tree is closed. Please wait for it to reopen. Use ' | 944 print ('The tree is closed. Please wait for it to reopen. Use ' |
910 '"git cl dcommit -f" to commit on a closed tree.') | 945 '"git cl dcommit -f" to commit on a closed tree.') |
911 return 1 | 946 return 1 |
912 elif 'unknown' == status: | 947 elif 'unknown' == status: |
913 print ('Unable to determine tree status. Please verify manually and ' | 948 print ('Unable to determine tree status. Please verify manually and ' |
(...skipping 365 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1279 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' | 1314 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' |
1280 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) | 1315 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) |
1281 | 1316 |
1282 # Not a known command. Default to help. | 1317 # Not a known command. Default to help. |
1283 GenUsage(parser, 'help') | 1318 GenUsage(parser, 'help') |
1284 return CMDhelp(parser, argv) | 1319 return CMDhelp(parser, argv) |
1285 | 1320 |
1286 | 1321 |
1287 if __name__ == '__main__': | 1322 if __name__ == '__main__': |
1288 sys.exit(main(sys.argv[1:])) | 1323 sys.exit(main(sys.argv[1:])) |
OLD | NEW |