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

Side by Side Diff: git_cl.py

Issue 2394033003: Remove SVN (and dcommit) support from git-cl (Closed)
Patch Set: Updated patchset dependency Created 4 years, 2 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
« no previous file with comments | « no previous file | no next file » | 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/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 # Copyright (C) 2008 Evan Martin <martine@danga.com> 6 # Copyright (C) 2008 Evan Martin <martine@danga.com>
7 7
8 """A git-command for integrating reviews on Rietveld and Gerrit.""" 8 """A git-command for integrating reviews on Rietveld and Gerrit."""
9 9
10 from __future__ import print_function 10 from __future__ import print_function
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
57 import rietveld 57 import rietveld
58 import scm 58 import scm
59 import subcommand 59 import subcommand
60 import subprocess2 60 import subprocess2
61 import watchlists 61 import watchlists
62 62
63 __version__ = '2.0' 63 __version__ = '2.0'
64 64
65 COMMIT_BOT_EMAIL = 'commit-bot@chromium.org' 65 COMMIT_BOT_EMAIL = 'commit-bot@chromium.org'
66 DEFAULT_SERVER = 'https://codereview.chromium.org' 66 DEFAULT_SERVER = 'https://codereview.chromium.org'
67 POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s' 67 POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-land'
68 DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup' 68 DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup'
69 GIT_INSTRUCTIONS_URL = 'http://code.google.com/p/chromium/wiki/UsingGit'
70 REFS_THAT_ALIAS_TO_OTHER_REFS = { 69 REFS_THAT_ALIAS_TO_OTHER_REFS = {
71 'refs/remotes/origin/lkgr': 'refs/remotes/origin/master', 70 'refs/remotes/origin/lkgr': 'refs/remotes/origin/master',
72 'refs/remotes/origin/lkcr': 'refs/remotes/origin/master', 71 'refs/remotes/origin/lkcr': 'refs/remotes/origin/master',
73 } 72 }
74 73
75 # Valid extensions for files we want to lint. 74 # Valid extensions for files we want to lint.
76 DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)" 75 DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)"
77 DEFAULT_LINT_IGNORE_REGEX = r"$^" 76 DEFAULT_LINT_IGNORE_REGEX = r"$^"
78 77
79 # Shortcut since it quickly becomes redundant. 78 # Shortcut since it quickly becomes redundant.
(...skipping 471 matching lines...) Expand 10 before | Expand all | Expand 10 after
551 'failure_reason': build.get('failure_reason'), 550 'failure_reason': build.get('failure_reason'),
552 'url': build.get('url'), 551 'url': build.get('url'),
553 } 552 }
554 553
555 converted = [] 554 converted = []
556 for _, build in sorted(builds.items()): 555 for _, build in sorted(builds.items()):
557 converted.append(convert_build_dict(build)) 556 converted.append(convert_build_dict(build))
558 write_json(output_file, converted) 557 write_json(output_file, converted)
559 558
560 559
561 def MatchSvnGlob(url, base_url, glob_spec, allow_wildcards):
562 """Return the corresponding git ref if |base_url| together with |glob_spec|
563 matches the full |url|.
564
565 If |allow_wildcards| is true, |glob_spec| can contain wildcards (see below).
566 """
567 fetch_suburl, as_ref = glob_spec.split(':')
568 if allow_wildcards:
569 glob_match = re.match('(.+/)?(\*|{[^/]*})(/.+)?', fetch_suburl)
570 if glob_match:
571 # Parse specs like "branches/*/src:refs/remotes/svn/*" or
572 # "branches/{472,597,648}/src:refs/remotes/svn/*".
573 branch_re = re.escape(base_url)
574 if glob_match.group(1):
575 branch_re += '/' + re.escape(glob_match.group(1))
576 wildcard = glob_match.group(2)
577 if wildcard == '*':
578 branch_re += '([^/]*)'
579 else:
580 # Escape and replace surrounding braces with parentheses and commas
581 # with pipe symbols.
582 wildcard = re.escape(wildcard)
583 wildcard = re.sub('^\\\\{', '(', wildcard)
584 wildcard = re.sub('\\\\,', '|', wildcard)
585 wildcard = re.sub('\\\\}$', ')', wildcard)
586 branch_re += wildcard
587 if glob_match.group(3):
588 branch_re += re.escape(glob_match.group(3))
589 match = re.match(branch_re, url)
590 if match:
591 return re.sub('\*$', match.group(1), as_ref)
592
593 # Parse specs like "trunk/src:refs/remotes/origin/trunk".
594 if fetch_suburl:
595 full_url = base_url + '/' + fetch_suburl
596 else:
597 full_url = base_url
598 if full_url == url:
599 return as_ref
600 return None
601
602
603 def print_stats(similarity, find_copies, args): 560 def print_stats(similarity, find_copies, args):
604 """Prints statistics about the change to the user.""" 561 """Prints statistics about the change to the user."""
605 # --no-ext-diff is broken in some versions of Git, so try to work around 562 # --no-ext-diff is broken in some versions of Git, so try to work around
606 # this by overriding the environment (but there is still a problem if the 563 # this by overriding the environment (but there is still a problem if the
607 # git config key "diff.external" is used). 564 # git config key "diff.external" is used).
608 env = GetNoGitPagerEnv() 565 env = GetNoGitPagerEnv()
609 if 'GIT_EXTERNAL_DIFF' in env: 566 if 'GIT_EXTERNAL_DIFF' in env:
610 del env['GIT_EXTERNAL_DIFF'] 567 del env['GIT_EXTERNAL_DIFF']
611 568
612 if find_copies: 569 if find_copies:
(...skipping 14 matching lines...) Expand all
627 584
628 class BuildbucketResponseException(Exception): 585 class BuildbucketResponseException(Exception):
629 pass 586 pass
630 587
631 588
632 class Settings(object): 589 class Settings(object):
633 def __init__(self): 590 def __init__(self):
634 self.default_server = None 591 self.default_server = None
635 self.cc = None 592 self.cc = None
636 self.root = None 593 self.root = None
637 self.is_git_svn = None
638 self.svn_branch = None
639 self.tree_status_url = None 594 self.tree_status_url = None
640 self.viewvc_url = None 595 self.viewvc_url = None
641 self.updated = False 596 self.updated = False
642 self.is_gerrit = None 597 self.is_gerrit = None
643 self.squash_gerrit_uploads = None 598 self.squash_gerrit_uploads = None
644 self.gerrit_skip_ensure_authenticated = None 599 self.gerrit_skip_ensure_authenticated = None
645 self.git_editor = None 600 self.git_editor = None
646 self.project = None 601 self.project = None
647 self.force_https_commit_url = None 602 self.force_https_commit_url = None
648 self.pending_ref_prefix = None 603 self.pending_ref_prefix = None
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
691 if not os.path.isdir(local_url): 646 if not os.path.isdir(local_url):
692 return None 647 return None
693 git_cache.Mirror.SetCachePath(os.path.dirname(local_url)) 648 git_cache.Mirror.SetCachePath(os.path.dirname(local_url))
694 remote_url = git_cache.Mirror.CacheDirToUrl(local_url) 649 remote_url = git_cache.Mirror.CacheDirToUrl(local_url)
695 # Use the /dev/null print_func to avoid terminal spew in WaitForRealCommit. 650 # Use the /dev/null print_func to avoid terminal spew in WaitForRealCommit.
696 mirror = git_cache.Mirror(remote_url, print_func = lambda *args: None) 651 mirror = git_cache.Mirror(remote_url, print_func = lambda *args: None)
697 if mirror.exists(): 652 if mirror.exists():
698 return mirror 653 return mirror
699 return None 654 return None
700 655
701 def GetIsGitSvn(self):
702 """Return true if this repo looks like it's using git-svn."""
703 if self.is_git_svn is None:
704 if self.GetPendingRefPrefix():
705 # If PENDING_REF_PREFIX is set then it's a pure git repo no matter what.
706 self.is_git_svn = False
707 else:
708 # If you have any "svn-remote.*" config keys, we think you're using svn.
709 self.is_git_svn = RunGitWithCode(
710 ['config', '--local', '--get-regexp', r'^svn-remote\.'])[0] == 0
711 return self.is_git_svn
712
713 def GetSVNBranch(self):
714 if self.svn_branch is None:
715 if not self.GetIsGitSvn():
716 DieWithError('Repo doesn\'t appear to be a git-svn repo.')
717
718 # Try to figure out which remote branch we're based on.
719 # Strategy:
720 # 1) iterate through our branch history and find the svn URL.
721 # 2) find the svn-remote that fetches from the URL.
722
723 # regexp matching the git-svn line that contains the URL.
724 git_svn_re = re.compile(r'^\s*git-svn-id: (\S+)@', re.MULTILINE)
725
726 # We don't want to go through all of history, so read a line from the
727 # pipe at a time.
728 # The -100 is an arbitrary limit so we don't search forever.
729 cmd = ['git', 'log', '-100', '--pretty=medium']
730 proc = subprocess2.Popen(cmd, stdout=subprocess2.PIPE,
731 env=GetNoGitPagerEnv())
732 url = None
733 for line in proc.stdout:
734 match = git_svn_re.match(line)
735 if match:
736 url = match.group(1)
737 proc.stdout.close() # Cut pipe.
738 break
739
740 if url:
741 svn_remote_re = re.compile(r'^svn-remote\.([^.]+)\.url (.*)$')
742 remotes = RunGit(['config', '--get-regexp',
743 r'^svn-remote\..*\.url']).splitlines()
744 for remote in remotes:
745 match = svn_remote_re.match(remote)
746 if match:
747 remote = match.group(1)
748 base_url = match.group(2)
749 rewrite_root = RunGit(
750 ['config', 'svn-remote.%s.rewriteRoot' % remote],
751 error_ok=True).strip()
752 if rewrite_root:
753 base_url = rewrite_root
754 fetch_spec = RunGit(
755 ['config', 'svn-remote.%s.fetch' % remote],
756 error_ok=True).strip()
757 if fetch_spec:
758 self.svn_branch = MatchSvnGlob(url, base_url, fetch_spec, False)
759 if self.svn_branch:
760 break
761 branch_spec = RunGit(
762 ['config', 'svn-remote.%s.branches' % remote],
763 error_ok=True).strip()
764 if branch_spec:
765 self.svn_branch = MatchSvnGlob(url, base_url, branch_spec, True)
766 if self.svn_branch:
767 break
768 tag_spec = RunGit(
769 ['config', 'svn-remote.%s.tags' % remote],
770 error_ok=True).strip()
771 if tag_spec:
772 self.svn_branch = MatchSvnGlob(url, base_url, tag_spec, True)
773 if self.svn_branch:
774 break
775
776 if not self.svn_branch:
777 DieWithError('Can\'t guess svn branch -- try specifying it on the '
778 'command line')
779
780 return self.svn_branch
781
782 def GetTreeStatusUrl(self, error_ok=False): 656 def GetTreeStatusUrl(self, error_ok=False):
783 if not self.tree_status_url: 657 if not self.tree_status_url:
784 error_message = ('You must configure your tree status URL by running ' 658 error_message = ('You must configure your tree status URL by running '
785 '"git cl config".') 659 '"git cl config".')
786 self.tree_status_url = self._GetRietveldConfig( 660 self.tree_status_url = self._GetRietveldConfig(
787 'tree-status-url', error_ok=error_ok, error_message=error_message) 661 'tree-status-url', error_ok=error_ok, error_message=error_message)
788 return self.tree_status_url 662 return self.tree_status_url
789 663
790 def GetViewVCUrl(self): 664 def GetViewVCUrl(self):
791 if not self.viewvc_url: 665 if not self.viewvc_url:
(...skipping 330 matching lines...) Expand 10 before | Expand all | Expand 10 after
1122 upstream_branch = _git_get_branch_config_value('merge', branch=branch) 996 upstream_branch = _git_get_branch_config_value('merge', branch=branch)
1123 997
1124 if upstream_branch: 998 if upstream_branch:
1125 remote = _git_get_branch_config_value('remote', branch=branch) 999 remote = _git_get_branch_config_value('remote', branch=branch)
1126 else: 1000 else:
1127 upstream_branch = RunGit(['config', 'rietveld.upstream-branch'], 1001 upstream_branch = RunGit(['config', 'rietveld.upstream-branch'],
1128 error_ok=True).strip() 1002 error_ok=True).strip()
1129 if upstream_branch: 1003 if upstream_branch:
1130 remote = RunGit(['config', 'rietveld.upstream-remote']).strip() 1004 remote = RunGit(['config', 'rietveld.upstream-remote']).strip()
1131 else: 1005 else:
1132 # Fall back on trying a git-svn upstream branch. 1006 # Else, try to guess the origin remote.
1133 if settings.GetIsGitSvn(): 1007 remote_branches = RunGit(['branch', '-r']).split()
1134 upstream_branch = settings.GetSVNBranch() 1008 if 'origin/master' in remote_branches:
1009 # Fall back on origin/master if it exits.
1010 remote = 'origin'
1011 upstream_branch = 'refs/heads/master'
1135 else: 1012 else:
1136 # Else, try to guess the origin remote. 1013 DieWithError(
1137 remote_branches = RunGit(['branch', '-r']).split() 1014 'Unable to determine default branch to diff against.\n'
1138 if 'origin/master' in remote_branches: 1015 'Either pass complete "git diff"-style arguments, like\n'
1139 # Fall back on origin/master if it exits. 1016 ' git cl upload origin/master\n'
1140 remote = 'origin' 1017 'or verify this branch is set up to track another \n'
1141 upstream_branch = 'refs/heads/master' 1018 '(via the --track argument to "git checkout -b ...").')
1142 elif 'origin/trunk' in remote_branches:
1143 # Fall back on origin/trunk if it exists. Generally a shared
1144 # git-svn clone
1145 remote = 'origin'
1146 upstream_branch = 'refs/heads/trunk'
1147 else:
1148 DieWithError(
1149 'Unable to determine default branch to diff against.\n'
1150 'Either pass complete "git diff"-style arguments, like\n'
1151 ' git cl upload origin/master\n'
1152 'or verify this branch is set up to track another \n'
1153 '(via the --track argument to "git checkout -b ...").')
1154 1019
1155 return remote, upstream_branch 1020 return remote, upstream_branch
1156 1021
1157 def GetCommonAncestorWithUpstream(self): 1022 def GetCommonAncestorWithUpstream(self):
1158 upstream_branch = self.GetUpstreamBranch() 1023 upstream_branch = self.GetUpstreamBranch()
1159 if not BranchExists(upstream_branch): 1024 if not BranchExists(upstream_branch):
1160 DieWithError('The upstream for the current branch (%s) does not exist ' 1025 DieWithError('The upstream for the current branch (%s) does not exist '
1161 'anymore.\nPlease fix it and try again.' % self.GetBranch()) 1026 'anymore.\nPlease fix it and try again.' % self.GetBranch())
1162 return git_common.get_or_create_merge_base(self.GetBranch(), 1027 return git_common.get_or_create_merge_base(self.GetBranch(),
1163 upstream_branch) 1028 upstream_branch)
(...skipping 18 matching lines...) Expand all
1182 remote, branch = self.FetchUpstreamTuple(branch) 1047 remote, branch = self.FetchUpstreamTuple(branch)
1183 branch = ShortBranchName(branch) 1048 branch = ShortBranchName(branch)
1184 if remote != '.' or branch.startswith('refs/remotes'): 1049 if remote != '.' or branch.startswith('refs/remotes'):
1185 break 1050 break
1186 else: 1051 else:
1187 remotes = RunGit(['remote'], error_ok=True).split() 1052 remotes = RunGit(['remote'], error_ok=True).split()
1188 if len(remotes) == 1: 1053 if len(remotes) == 1:
1189 remote, = remotes 1054 remote, = remotes
1190 elif 'origin' in remotes: 1055 elif 'origin' in remotes:
1191 remote = 'origin' 1056 remote = 'origin'
1192 logging.warning('Could not determine which remote this change is ' 1057 logging.warn('Could not determine which remote this change is '
1193 'associated with, so defaulting to "%s". This may ' 1058 'associated with, so defaulting to "%s".' % self._remote)
1194 'not be what you want. You may prevent this message '
1195 'by running "git svn info" as documented here: %s',
1196 self._remote,
1197 GIT_INSTRUCTIONS_URL)
1198 else: 1059 else:
1199 logging.warn('Could not determine which remote this change is ' 1060 logging.warn('Could not determine which remote this change is '
1200 'associated with. You may prevent this message by ' 1061 'associated with.')
1201 'running "git svn info" as documented here: %s',
1202 GIT_INSTRUCTIONS_URL)
1203 branch = 'HEAD' 1062 branch = 'HEAD'
1204 if branch.startswith('refs/remotes'): 1063 if branch.startswith('refs/remotes'):
1205 self._remote = (remote, branch) 1064 self._remote = (remote, branch)
1206 elif branch.startswith('refs/branch-heads/'): 1065 elif branch.startswith('refs/branch-heads/'):
1207 self._remote = (remote, branch.replace('refs/', 'refs/remotes/')) 1066 self._remote = (remote, branch.replace('refs/', 'refs/remotes/'))
1208 else: 1067 else:
1209 self._remote = (remote, 'refs/remotes/%s/%s' % (remote, branch)) 1068 self._remote = (remote, 'refs/remotes/%s/%s' % (remote, branch))
1210 return self._remote 1069 return self._remote
1211 1070
1212 def GitSanityChecks(self, upstream_git_obj): 1071 def GitSanityChecks(self, upstream_git_obj):
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
1251 return False 1110 return False
1252 return True 1111 return True
1253 1112
1254 def GetGitBaseUrlFromConfig(self): 1113 def GetGitBaseUrlFromConfig(self):
1255 """Return the configured base URL from branch.<branchname>.baseurl. 1114 """Return the configured base URL from branch.<branchname>.baseurl.
1256 1115
1257 Returns None if it is not set. 1116 Returns None if it is not set.
1258 """ 1117 """
1259 return self._GitGetBranchConfigValue('base-url') 1118 return self._GitGetBranchConfigValue('base-url')
1260 1119
1261 def GetGitSvnRemoteUrl(self):
1262 """Return the configured git-svn remote URL parsed from git svn info.
1263
1264 Returns None if it is not set.
1265 """
1266 # URL is dependent on the current directory.
1267 data = RunGit(['svn', 'info'], cwd=settings.GetRoot())
1268 if data:
1269 keys = dict(line.split(': ', 1) for line in data.splitlines()
1270 if ': ' in line)
1271 return keys.get('URL', None)
1272 return None
1273
1274 def GetRemoteUrl(self): 1120 def GetRemoteUrl(self):
1275 """Return the configured remote URL, e.g. 'git://example.org/foo.git/'. 1121 """Return the configured remote URL, e.g. 'git://example.org/foo.git/'.
1276 1122
1277 Returns None if there is no remote. 1123 Returns None if there is no remote.
1278 """ 1124 """
1279 remote, _ = self.GetRemoteBranch() 1125 remote, _ = self.GetRemoteBranch()
1280 url = RunGit(['config', 'remote.%s.url' % remote], error_ok=True).strip() 1126 url = RunGit(['config', 'remote.%s.url' % remote], error_ok=True).strip()
1281 1127
1282 # If URL is pointing to a local directory, it is probably a git cache. 1128 # If URL is pointing to a local directory, it is probably a git cache.
1283 if os.path.isdir(url): 1129 if os.path.isdir(url):
(...skipping 750 matching lines...) Expand 10 before | Expand all | Expand 10 after
2034 upload_args.append('--private') 1880 upload_args.append('--private')
2035 1881
2036 upload_args.extend(['--git_similarity', str(options.similarity)]) 1882 upload_args.extend(['--git_similarity', str(options.similarity)])
2037 if not options.find_copies: 1883 if not options.find_copies:
2038 upload_args.extend(['--git_no_find_copies']) 1884 upload_args.extend(['--git_no_find_copies'])
2039 1885
2040 # Include the upstream repo's URL in the change -- this is useful for 1886 # Include the upstream repo's URL in the change -- this is useful for
2041 # projects that have their source spread across multiple repos. 1887 # projects that have their source spread across multiple repos.
2042 remote_url = self.GetGitBaseUrlFromConfig() 1888 remote_url = self.GetGitBaseUrlFromConfig()
2043 if not remote_url: 1889 if not remote_url:
2044 if settings.GetIsGitSvn(): 1890 if self.GetRemoteUrl() and '/' in self.GetUpstreamBranch():
2045 remote_url = self.GetGitSvnRemoteUrl() 1891 remote_url = '%s@%s' % (self.GetRemoteUrl(),
2046 else: 1892 self.GetUpstreamBranch().split('/')[-1])
2047 if self.GetRemoteUrl() and '/' in self.GetUpstreamBranch():
2048 remote_url = '%s@%s' % (self.GetRemoteUrl(),
2049 self.GetUpstreamBranch().split('/')[-1])
2050 if remote_url: 1893 if remote_url:
2051 remote, remote_branch = self.GetRemoteBranch() 1894 remote, remote_branch = self.GetRemoteBranch()
2052 target_ref = GetTargetRef(remote, remote_branch, options.target_branch, 1895 target_ref = GetTargetRef(remote, remote_branch, options.target_branch,
2053 settings.GetPendingRefPrefix()) 1896 settings.GetPendingRefPrefix())
2054 if target_ref: 1897 if target_ref:
2055 upload_args.extend(['--target_ref', target_ref]) 1898 upload_args.extend(['--target_ref', target_ref])
2056 1899
2057 # Look for dependent patchsets. See crbug.com/480453 for more details. 1900 # Look for dependent patchsets. See crbug.com/480453 for more details.
2058 remote, upstream_branch = self.FetchUpstreamTuple(self.GetBranch()) 1901 remote, upstream_branch = self.FetchUpstreamTuple(self.GetBranch())
2059 upstream_branch = ShortBranchName(upstream_branch) 1902 upstream_branch = ShortBranchName(upstream_branch)
(...skipping 1917 matching lines...) Expand 10 before | Expand all | Expand 10 after
3977 if options.cq_dry_run and options.use_commit_queue: 3820 if options.cq_dry_run and options.use_commit_queue:
3978 parser.error('only one of --use-commit-queue and --cq-dry-run allowed.') 3821 parser.error('only one of --use-commit-queue and --cq-dry-run allowed.')
3979 3822
3980 # For sanity of test expectations, do this otherwise lazy-loading *now*. 3823 # For sanity of test expectations, do this otherwise lazy-loading *now*.
3981 settings.GetIsGerrit() 3824 settings.GetIsGerrit()
3982 3825
3983 cl = Changelist(auth_config=auth_config, codereview=options.forced_codereview) 3826 cl = Changelist(auth_config=auth_config, codereview=options.forced_codereview)
3984 return cl.CMDUpload(options, args, orig_args) 3827 return cl.CMDUpload(options, args, orig_args)
3985 3828
3986 3829
3987 def IsSubmoduleMergeCommit(ref):
3988 # When submodules are added to the repo, we expect there to be a single
3989 # non-git-svn merge commit at remote HEAD with a signature comment.
3990 pattern = '^SVN changes up to revision [0-9]*$'
3991 cmd = ['rev-list', '--merges', '--grep=%s' % pattern, '%s^!' % ref]
3992 return RunGit(cmd) != ''
3993
3994
3995 def SendUpstream(parser, args, cmd): 3830 def SendUpstream(parser, args, cmd):
3996 """Common code for CMDland and CmdDCommit 3831 """Common code for CMDland and CmdDCommit
3997 3832
3998 In case of Gerrit, uses Gerrit REST api to "submit" the issue, which pushes 3833 In case of Gerrit, uses Gerrit REST api to "submit" the issue, which pushes
3999 upstream and closes the issue automatically and atomically. 3834 upstream and closes the issue automatically and atomically.
4000 3835
4001 Otherwise (in case of Rietveld): 3836 Otherwise (in case of Rietveld):
4002 Squashes branch into a single commit. 3837 Squashes branch into a single commit.
4003 Updates changelog with metadata (e.g. pointer to review). 3838 Updates changelog with metadata (e.g. pointer to review).
4004 Pushes/dcommits the code upstream. 3839 Pushes/dcommits the code upstream.
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
4038 '"Forge-Author" permission.') 3873 '"Forge-Author" permission.')
4039 if not cl.GetIssue(): 3874 if not cl.GetIssue():
4040 DieWithError('You must upload the issue first to Gerrit.\n' 3875 DieWithError('You must upload the issue first to Gerrit.\n'
4041 ' If you would rather have `git cl land` upload ' 3876 ' If you would rather have `git cl land` upload '
4042 'automatically for you, see http://crbug.com/642759') 3877 'automatically for you, see http://crbug.com/642759')
4043 return cl._codereview_impl.CMDLand(options.force, options.bypass_hooks, 3878 return cl._codereview_impl.CMDLand(options.force, options.bypass_hooks,
4044 options.verbose) 3879 options.verbose)
4045 3880
4046 current = cl.GetBranch() 3881 current = cl.GetBranch()
4047 remote, upstream_branch = cl.FetchUpstreamTuple(cl.GetBranch()) 3882 remote, upstream_branch = cl.FetchUpstreamTuple(cl.GetBranch())
4048 if not settings.GetIsGitSvn() and remote == '.': 3883 if remote == '.':
4049 print() 3884 print()
4050 print('Attempting to push branch %r into another local branch!' % current) 3885 print('Attempting to push branch %r into another local branch!' % current)
4051 print() 3886 print()
4052 print('Either reparent this branch on top of origin/master:') 3887 print('Either reparent this branch on top of origin/master:')
4053 print(' git reparent-branch --root') 3888 print(' git reparent-branch --root')
4054 print() 3889 print()
4055 print('OR run `git rebase-update` if you think the parent branch is ') 3890 print('OR run `git rebase-update` if you think the parent branch is ')
4056 print('already committed.') 3891 print('already committed.')
4057 print() 3892 print()
4058 print(' Current parent: %r' % upstream_branch) 3893 print(' Current parent: %r' % upstream_branch)
4059 return 1 3894 return 1
4060 3895
4061 if not args or cmd == 'land': 3896 if not args:
4062 # Default to merging against our best guess of the upstream branch. 3897 # Default to merging against our best guess of the upstream branch.
4063 args = [cl.GetUpstreamBranch()] 3898 args = [cl.GetUpstreamBranch()]
4064 3899
4065 if options.contributor: 3900 if options.contributor:
4066 if not re.match('^.*\s<\S+@\S+>$', options.contributor): 3901 if not re.match('^.*\s<\S+@\S+>$', options.contributor):
4067 print("Please provide contibutor as 'First Last <email@example.com>'") 3902 print("Please provide contibutor as 'First Last <email@example.com>'")
4068 return 1 3903 return 1
4069 3904
4070 base_branch = args[0] 3905 base_branch = args[0]
4071 base_has_submodules = IsSubmoduleMergeCommit(base_branch)
4072 3906
4073 if git_common.is_dirty_git_tree(cmd): 3907 if git_common.is_dirty_git_tree('land'):
4074 return 1 3908 return 1
4075 3909
4076 # This rev-list syntax means "show all commits not in my branch that 3910 # This rev-list syntax means "show all commits not in my branch that
4077 # are in base_branch". 3911 # are in base_branch".
4078 upstream_commits = RunGit(['rev-list', '^' + cl.GetBranchRef(), 3912 upstream_commits = RunGit(['rev-list', '^' + cl.GetBranchRef(),
4079 base_branch]).splitlines() 3913 base_branch]).splitlines()
4080 if upstream_commits: 3914 if upstream_commits:
4081 print('Base branch "%s" has %d commits ' 3915 print('Base branch "%s" has %d commits '
4082 'not in this branch.' % (base_branch, len(upstream_commits))) 3916 'not in this branch.' % (base_branch, len(upstream_commits)))
4083 print('Run "git merge %s" before attempting to %s.' % (base_branch, cmd)) 3917 print('Run "git merge %s" before attempting to land.' % base_branch)
4084 return 1 3918 return 1
4085 3919
4086 # This is the revision `svn dcommit` will commit on top of.
4087 svn_head = None
4088 if cmd == 'dcommit' or base_has_submodules:
4089 svn_head = RunGit(['log', '--grep=^git-svn-id:', '-1',
4090 '--pretty=format:%H'])
4091
4092 if cmd == 'dcommit':
4093 # If the base_head is a submodule merge commit, the first parent of the
4094 # base_head should be a git-svn commit, which is what we're interested in.
4095 base_svn_head = base_branch
4096 if base_has_submodules:
4097 base_svn_head += '^1'
4098
4099 extra_commits = RunGit(['rev-list', '^' + svn_head, base_svn_head])
4100 if extra_commits:
4101 print('This branch has %d additional commits not upstreamed yet.'
4102 % len(extra_commits.splitlines()))
4103 print('Upstream "%s" or rebase this branch on top of the upstream trunk '
4104 'before attempting to %s.' % (base_branch, cmd))
4105 return 1
4106
4107 merge_base = RunGit(['merge-base', base_branch, 'HEAD']).strip() 3920 merge_base = RunGit(['merge-base', base_branch, 'HEAD']).strip()
4108 if not options.bypass_hooks: 3921 if not options.bypass_hooks:
4109 author = None 3922 author = None
4110 if options.contributor: 3923 if options.contributor:
4111 author = re.search(r'\<(.*)\>', options.contributor).group(1) 3924 author = re.search(r'\<(.*)\>', options.contributor).group(1)
4112 hook_results = cl.RunHook( 3925 hook_results = cl.RunHook(
4113 committing=True, 3926 committing=True,
4114 may_prompt=not options.force, 3927 may_prompt=not options.force,
4115 verbose=options.verbose, 3928 verbose=options.verbose,
4116 change=cl.GetChange(merge_base, author)) 3929 change=cl.GetChange(merge_base, author))
4117 if not hook_results.should_continue(): 3930 if not hook_results.should_continue():
4118 return 1 3931 return 1
4119 3932
4120 # Check the tree status if the tree status URL is set. 3933 # Check the tree status if the tree status URL is set.
4121 status = GetTreeStatus() 3934 status = GetTreeStatus()
4122 if 'closed' == status: 3935 if 'closed' == status:
4123 print('The tree is closed. Please wait for it to reopen. Use ' 3936 print('The tree is closed. Please wait for it to reopen. Use '
4124 '"git cl %s --bypass-hooks" to commit on a closed tree.' % cmd) 3937 '"git cl land --bypass-hooks" to commit on a closed tree.')
4125 return 1 3938 return 1
4126 elif 'unknown' == status: 3939 elif 'unknown' == status:
4127 print('Unable to determine tree status. Please verify manually and ' 3940 print('Unable to determine tree status. Please verify manually and '
4128 'use "git cl %s --bypass-hooks" to commit on a closed tree.' % cmd) 3941 'use "git cl land --bypass-hooks" to commit on a closed tree.')
4129 return 1 3942 return 1
4130 3943
4131 change_desc = ChangeDescription(options.message) 3944 change_desc = ChangeDescription(options.message)
4132 if not change_desc.description and cl.GetIssue(): 3945 if not change_desc.description and cl.GetIssue():
4133 change_desc = ChangeDescription(cl.GetDescription()) 3946 change_desc = ChangeDescription(cl.GetDescription())
4134 3947
4135 if not change_desc.description: 3948 if not change_desc.description:
4136 if not cl.GetIssue() and options.bypass_hooks: 3949 if not cl.GetIssue() and options.bypass_hooks:
4137 change_desc = ChangeDescription(CreateDescriptionFromLog([merge_base])) 3950 change_desc = ChangeDescription(CreateDescriptionFromLog([merge_base]))
4138 else: 3951 else:
(...skipping 20 matching lines...) Expand all
4159 3972
4160 print('Description:') 3973 print('Description:')
4161 print(commit_desc.description) 3974 print(commit_desc.description)
4162 3975
4163 branches = [merge_base, cl.GetBranchRef()] 3976 branches = [merge_base, cl.GetBranchRef()]
4164 if not options.force: 3977 if not options.force:
4165 print_stats(options.similarity, options.find_copies, branches) 3978 print_stats(options.similarity, options.find_copies, branches)
4166 3979
4167 # We want to squash all this branch's commits into one commit with the proper 3980 # We want to squash all this branch's commits into one commit with the proper
4168 # description. We do this by doing a "reset --soft" to the base branch (which 3981 # description. We do this by doing a "reset --soft" to the base branch (which
4169 # keeps the working copy the same), then dcommitting that. If origin/master 3982 # keeps the working copy the same), then dcommitting that.
4170 # has a submodule merge commit, we'll also need to cherry-pick the squashed
4171 # commit onto a branch based on the git-svn head.
4172 MERGE_BRANCH = 'git-cl-commit' 3983 MERGE_BRANCH = 'git-cl-commit'
4173 CHERRY_PICK_BRANCH = 'git-cl-cherry-pick' 3984 CHERRY_PICK_BRANCH = 'git-cl-cherry-pick'
4174 # Delete the branches if they exist. 3985 # Delete the branches if they exist.
4175 for branch in [MERGE_BRANCH, CHERRY_PICK_BRANCH]: 3986 for branch in [MERGE_BRANCH, CHERRY_PICK_BRANCH]:
4176 showref_cmd = ['show-ref', '--quiet', '--verify', 'refs/heads/%s' % branch] 3987 showref_cmd = ['show-ref', '--quiet', '--verify', 'refs/heads/%s' % branch]
4177 result = RunGitWithCode(showref_cmd) 3988 result = RunGitWithCode(showref_cmd)
4178 if result[0] == 0: 3989 if result[0] == 0:
4179 RunGit(['branch', '-D', branch]) 3990 RunGit(['branch', '-D', branch])
4180 3991
4181 # We might be in a directory that's present in this branch but not in the 3992 # We might be in a directory that's present in this branch but not in the
(...skipping 14 matching lines...) Expand all
4196 RunGit(['checkout', '-q', '-b', MERGE_BRANCH]) 4007 RunGit(['checkout', '-q', '-b', MERGE_BRANCH])
4197 RunGit(['reset', '--soft', merge_base]) 4008 RunGit(['reset', '--soft', merge_base])
4198 if options.contributor: 4009 if options.contributor:
4199 RunGit( 4010 RunGit(
4200 [ 4011 [
4201 'commit', '--author', options.contributor, 4012 'commit', '--author', options.contributor,
4202 '-m', commit_desc.description, 4013 '-m', commit_desc.description,
4203 ]) 4014 ])
4204 else: 4015 else:
4205 RunGit(['commit', '-m', commit_desc.description]) 4016 RunGit(['commit', '-m', commit_desc.description])
4206 if base_has_submodules: 4017 remote, branch = cl.FetchUpstreamTuple(cl.GetBranch())
4207 cherry_pick_commit = RunGit(['rev-list', 'HEAD^!']).rstrip() 4018 mirror = settings.GetGitMirror(remote)
4208 RunGit(['branch', CHERRY_PICK_BRANCH, svn_head]) 4019 pushurl = mirror.url if mirror else remote
4209 RunGit(['checkout', CHERRY_PICK_BRANCH]) 4020 pending_prefix = settings.GetPendingRefPrefix()
4210 RunGit(['cherry-pick', cherry_pick_commit]) 4021 if not pending_prefix or branch.startswith(pending_prefix):
4211 if cmd == 'land': 4022 # If not using refs/pending/heads/* at all, or target ref is already set
4212 remote, branch = cl.FetchUpstreamTuple(cl.GetBranch()) 4023 # to pending, then push to the target ref directly.
4213 mirror = settings.GetGitMirror(remote) 4024 retcode, output = RunGitWithCode(
4214 pushurl = mirror.url if mirror else remote 4025 ['push', '--porcelain', pushurl, 'HEAD:%s' % branch])
4215 pending_prefix = settings.GetPendingRefPrefix() 4026 pushed_to_pending = pending_prefix and branch.startswith(pending_prefix)
4216 if not pending_prefix or branch.startswith(pending_prefix):
4217 # If not using refs/pending/heads/* at all, or target ref is already set
4218 # to pending, then push to the target ref directly.
4219 retcode, output = RunGitWithCode(
4220 ['push', '--porcelain', pushurl, 'HEAD:%s' % branch])
4221 pushed_to_pending = pending_prefix and branch.startswith(pending_prefix)
4222 else:
4223 # Cherry-pick the change on top of pending ref and then push it.
4224 assert branch.startswith('refs/'), branch
4225 assert pending_prefix[-1] == '/', pending_prefix
4226 pending_ref = pending_prefix + branch[len('refs/'):]
4227 retcode, output = PushToGitPending(pushurl, pending_ref)
4228 pushed_to_pending = (retcode == 0)
4229 if retcode == 0:
4230 revision = RunGit(['rev-parse', 'HEAD']).strip()
4231 else: 4027 else:
4232 # dcommit the merge branch. 4028 # Cherry-pick the change on top of pending ref and then push it.
4233 cmd_args = [ 4029 assert branch.startswith('refs/'), branch
4234 'svn', 'dcommit', 4030 assert pending_prefix[-1] == '/', pending_prefix
4235 '-C%s' % options.similarity, 4031 pending_ref = pending_prefix + branch[len('refs/'):]
4236 '--no-rebase', '--rmdir', 4032 retcode, output = PushToGitPending(pushurl, pending_ref)
4237 ] 4033 pushed_to_pending = (retcode == 0)
4238 if settings.GetForceHttpsCommitUrl(): 4034 if retcode == 0:
4239 # Allow forcing https commit URLs for some projects that don't allow 4035 revision = RunGit(['rev-parse', 'HEAD']).strip()
4240 # committing to http URLs (like Google Code).
4241 remote_url = cl.GetGitSvnRemoteUrl()
4242 if urlparse.urlparse(remote_url).scheme == 'http':
4243 remote_url = remote_url.replace('http://', 'https://')
4244 cmd_args.append('--commit-url=%s' % remote_url)
4245 _, output = RunGitWithCode(cmd_args)
4246 if 'Committed r' in output:
4247 revision = re.match(
4248 '.*?\nCommitted r(\\d+)', output, re.DOTALL).group(1)
4249 logging.debug(output) 4036 logging.debug(output)
4250 finally: 4037 finally:
4251 # And then swap back to the original branch and clean up. 4038 # And then swap back to the original branch and clean up.
4252 RunGit(['checkout', '-q', cl.GetBranch()]) 4039 RunGit(['checkout', '-q', cl.GetBranch()])
4253 RunGit(['branch', '-D', MERGE_BRANCH]) 4040 RunGit(['branch', '-D', MERGE_BRANCH])
4254 if base_has_submodules:
4255 RunGit(['branch', '-D', CHERRY_PICK_BRANCH])
4256 4041
4257 if not revision: 4042 if not revision:
4258 print('Failed to push. If this persists, please file a bug.') 4043 print('Failed to push. If this persists, please file a bug.')
4259 return 1 4044 return 1
4260 4045
4261 killed = False 4046 killed = False
4262 if pushed_to_pending: 4047 if pushed_to_pending:
4263 try: 4048 try:
4264 revision = WaitForRealCommit(remote, revision, base_branch, branch) 4049 revision = WaitForRealCommit(remote, revision, base_branch, branch)
4265 # We set pushed_to_pending to False, since it made it all the way to the 4050 # We set pushed_to_pending to False, since it made it all the way to the
(...skipping 24 matching lines...) Expand all
4290 else: 4075 else:
4291 comment += ' (presubmit successful).' 4076 comment += ' (presubmit successful).'
4292 cl.RpcServer().add_comment(cl.GetIssue(), comment) 4077 cl.RpcServer().add_comment(cl.GetIssue(), comment)
4293 4078
4294 if pushed_to_pending: 4079 if pushed_to_pending:
4295 _, branch = cl.FetchUpstreamTuple(cl.GetBranch()) 4080 _, branch = cl.FetchUpstreamTuple(cl.GetBranch())
4296 print('The commit is in the pending queue (%s).' % pending_ref) 4081 print('The commit is in the pending queue (%s).' % pending_ref)
4297 print('It will show up on %s in ~1 min, once it gets a Cr-Commit-Position ' 4082 print('It will show up on %s in ~1 min, once it gets a Cr-Commit-Position '
4298 'footer.' % branch) 4083 'footer.' % branch)
4299 4084
4300 hook = POSTUPSTREAM_HOOK_PATTERN % cmd 4085 hook = POSTUPSTREAM_HOOK_PATTERN
4301 if os.path.isfile(hook): 4086 if os.path.isfile(hook):
4302 RunCommand([hook, merge_base], error_ok=True) 4087 RunCommand([hook, merge_base], error_ok=True)
4303 4088
4304 return 1 if killed else 0 4089 return 1 if killed else 0
4305 4090
4306 4091
4307 def WaitForRealCommit(remote, pushed_commit, local_base_ref, real_ref): 4092 def WaitForRealCommit(remote, pushed_commit, local_base_ref, real_ref):
4308 print() 4093 print()
4309 print('Waiting for commit to be landed on %s...' % real_ref) 4094 print('Waiting for commit to be landed on %s...' % real_ref)
4310 print('(If you are impatient, you may Ctrl-C once without harm)') 4095 print('(If you are impatient, you may Ctrl-C once without harm)')
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after
4392 return code, out 4177 return code, out
4393 4178
4394 4179
4395 def IsFatalPushFailure(push_stdout): 4180 def IsFatalPushFailure(push_stdout):
4396 """True if retrying push won't help.""" 4181 """True if retrying push won't help."""
4397 return '(prohibited by Gerrit)' in push_stdout 4182 return '(prohibited by Gerrit)' in push_stdout
4398 4183
4399 4184
4400 @subcommand.usage('[upstream branch to apply against]') 4185 @subcommand.usage('[upstream branch to apply against]')
4401 def CMDdcommit(parser, args): 4186 def CMDdcommit(parser, args):
4402 """Commits the current changelist via git-svn.""" 4187 """DEPRECATED: Used to commit the current changelist via git-svn."""
4403 if not settings.GetIsGitSvn(): 4188 message = ('git-cl no longer supports committing to SVN repositories via'
4404 if git_footers.get_footer_svn_id(): 4189 'git-svn. You probably want to use `git cl land` instead.')
4405 # If it looks like previous commits were mirrored with git-svn. 4190 print(message)
tandrii(chromium) 2016/10/06 08:04:01 I'd add return 1 so command results in error.
agable 2016/10/06 21:43:15 Done.
4406 message = """This repository appears to be a git-svn mirror, but we
4407 don't support git-svn mirrors anymore."""
4408 else:
4409 message = """This doesn't appear to be an SVN repository.
4410 If your project has a true, writeable git repository, you probably want to run
4411 'git cl land' instead.
4412 If your project has a git mirror of an upstream SVN master, you probably need
4413 to run 'git svn init'.
4414
4415 Using the wrong command might cause your commit to appear to succeed, and the
4416 review to be closed, without actually landing upstream. If you choose to
4417 proceed, please verify that the commit lands upstream as expected."""
4418 print(message)
4419 ask_for_data('[Press enter to dcommit or ctrl-C to quit]')
4420 print('WARNING: chrome infrastructure is migrating SVN repos to Git.\n'
4421 'Please let us know of this project you are committing to:'
4422 ' http://crbug.com/600451')
4423 return SendUpstream(parser, args, 'dcommit')
4424 4191
4425 4192
4426 @subcommand.usage('[upstream branch to apply against]') 4193 @subcommand.usage('[upstream branch to apply against]')
4427 def CMDland(parser, args): 4194 def CMDland(parser, args):
4428 """Commits the current changelist via git.""" 4195 """Commits the current changelist via git."""
4429 if settings.GetIsGitSvn() or git_footers.get_footer_svn_id():
4430 print('This appears to be an SVN repository.')
4431 print('Are you sure you didn\'t mean \'git cl dcommit\'?')
4432 print('(Ignore if this is the first commit after migrating from svn->git)')
4433 ask_for_data('[Press enter to push or ctrl-C to quit]')
4434 return SendUpstream(parser, args, 'land') 4196 return SendUpstream(parser, args, 'land')
4435 4197
4436 4198
4437 @subcommand.usage('<patch url or issue id or issue url>') 4199 @subcommand.usage('<patch url or issue id or issue url>')
4438 def CMDpatch(parser, args): 4200 def CMDpatch(parser, args):
4439 """Patches in a code review.""" 4201 """Patches in a code review."""
4440 parser.add_option('-b', dest='newbranch', 4202 parser.add_option('-b', dest='newbranch',
4441 help='create a new branch off trunk for the patch') 4203 help='create a new branch off trunk for the patch')
4442 parser.add_option('-f', '--force', action='store_true', 4204 parser.add_option('-f', '--force', action='store_true',
4443 help='with -b, clobber any existing branch') 4205 help='with -b, clobber any existing branch')
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after
4517 parser.error('--reject is not supported with Gerrit codereview.') 4279 parser.error('--reject is not supported with Gerrit codereview.')
4518 if options.nocommit: 4280 if options.nocommit:
4519 parser.error('--nocommit is not supported with Gerrit codereview.') 4281 parser.error('--nocommit is not supported with Gerrit codereview.')
4520 if options.directory: 4282 if options.directory:
4521 parser.error('--directory is not supported with Gerrit codereview.') 4283 parser.error('--directory is not supported with Gerrit codereview.')
4522 4284
4523 return cl.CMDPatchIssue(args[0], options.reject, options.nocommit, 4285 return cl.CMDPatchIssue(args[0], options.reject, options.nocommit,
4524 options.directory) 4286 options.directory)
4525 4287
4526 4288
4527 def CMDrebase(parser, args):
4528 """Rebases current branch on top of svn repo."""
4529 # Provide a wrapper for git svn rebase to help avoid accidental
4530 # git svn dcommit.
4531 # It's the only command that doesn't use parser at all since we just defer
4532 # execution to git-svn.
4533
4534 return RunGitWithCode(['svn', 'rebase'] + args)[1]
4535
4536
4537 def GetTreeStatus(url=None): 4289 def GetTreeStatus(url=None):
4538 """Fetches the tree status and returns either 'open', 'closed', 4290 """Fetches the tree status and returns either 'open', 'closed',
4539 'unknown' or 'unset'.""" 4291 'unknown' or 'unset'."""
4540 url = url or settings.GetTreeStatusUrl(error_ok=True) 4292 url = url or settings.GetTreeStatusUrl(error_ok=True)
4541 if url: 4293 if url:
4542 status = urllib2.urlopen(url).read().lower() 4294 status = urllib2.urlopen(url).read().lower()
4543 if status.find('closed') != -1 or status == '0': 4295 if status.find('closed') != -1 or status == '0':
4544 return 'closed' 4296 return 'closed'
4545 elif status.find('open') != -1 or status == '1': 4297 elif status.find('open') != -1 or status == '1':
4546 return 'open' 4298 return 'open'
(...skipping 752 matching lines...) Expand 10 before | Expand all | Expand 10 after
5299 if __name__ == '__main__': 5051 if __name__ == '__main__':
5300 # These affect sys.stdout so do it outside of main() to simplify mocks in 5052 # These affect sys.stdout so do it outside of main() to simplify mocks in
5301 # unit testing. 5053 # unit testing.
5302 fix_encoding.fix_encoding() 5054 fix_encoding.fix_encoding()
5303 setup_color.init() 5055 setup_color.init()
5304 try: 5056 try:
5305 sys.exit(main(sys.argv[1:])) 5057 sys.exit(main(sys.argv[1:]))
5306 except KeyboardInterrupt: 5058 except KeyboardInterrupt:
5307 sys.stderr.write('interrupted\n') 5059 sys.stderr.write('interrupted\n')
5308 sys.exit(1) 5060 sys.exit(1)
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698