Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(9)

Side by Side Diff: git_cl/git_cl.py

Issue 6674014: Make git-cl work with OWNERS files in a non .git/hooks/pre-cl-* world (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: rebase to HEAD, minor linting, cleanup, testing, fix post-commit hook Created 9 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | git_cl/test/owners.sh » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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
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
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
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
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
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
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
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:]))
OLDNEW
« no previous file with comments | « no previous file | git_cl/test/owners.sh » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698