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 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 |