OLD | NEW |
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 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
59 import rietveld | 59 import rietveld |
60 import scm | 60 import scm |
61 import subcommand | 61 import subcommand |
62 import subprocess2 | 62 import subprocess2 |
63 import watchlists | 63 import watchlists |
64 | 64 |
65 __version__ = '2.0' | 65 __version__ = '2.0' |
66 | 66 |
67 COMMIT_BOT_EMAIL = 'commit-bot@chromium.org' | 67 COMMIT_BOT_EMAIL = 'commit-bot@chromium.org' |
68 DEFAULT_SERVER = 'https://codereview.chromium.org' | 68 DEFAULT_SERVER = 'https://codereview.chromium.org' |
69 POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s' | 69 POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-land' |
70 DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup' | 70 DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup' |
71 GIT_INSTRUCTIONS_URL = 'http://code.google.com/p/chromium/wiki/UsingGit' | |
72 REFS_THAT_ALIAS_TO_OTHER_REFS = { | 71 REFS_THAT_ALIAS_TO_OTHER_REFS = { |
73 'refs/remotes/origin/lkgr': 'refs/remotes/origin/master', | 72 'refs/remotes/origin/lkgr': 'refs/remotes/origin/master', |
74 'refs/remotes/origin/lkcr': 'refs/remotes/origin/master', | 73 'refs/remotes/origin/lkcr': 'refs/remotes/origin/master', |
75 } | 74 } |
76 | 75 |
77 # Valid extensions for files we want to lint. | 76 # Valid extensions for files we want to lint. |
78 DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)" | 77 DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)" |
79 DEFAULT_LINT_IGNORE_REGEX = r"$^" | 78 DEFAULT_LINT_IGNORE_REGEX = r"$^" |
80 | 79 |
81 # Buildbucket master name prefix. | 80 # Buildbucket master name prefix. |
(...skipping 612 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
694 'failure_reason': build.get('failure_reason'), | 693 'failure_reason': build.get('failure_reason'), |
695 'url': build.get('url'), | 694 'url': build.get('url'), |
696 } | 695 } |
697 | 696 |
698 converted = [] | 697 converted = [] |
699 for _, build in sorted(builds.items()): | 698 for _, build in sorted(builds.items()): |
700 converted.append(convert_build_dict(build)) | 699 converted.append(convert_build_dict(build)) |
701 write_json(output_file, converted) | 700 write_json(output_file, converted) |
702 | 701 |
703 | 702 |
704 def MatchSvnGlob(url, base_url, glob_spec, allow_wildcards): | |
705 """Return the corresponding git ref if |base_url| together with |glob_spec| | |
706 matches the full |url|. | |
707 | |
708 If |allow_wildcards| is true, |glob_spec| can contain wildcards (see below). | |
709 """ | |
710 fetch_suburl, as_ref = glob_spec.split(':') | |
711 if allow_wildcards: | |
712 glob_match = re.match('(.+/)?(\*|{[^/]*})(/.+)?', fetch_suburl) | |
713 if glob_match: | |
714 # Parse specs like "branches/*/src:refs/remotes/svn/*" or | |
715 # "branches/{472,597,648}/src:refs/remotes/svn/*". | |
716 branch_re = re.escape(base_url) | |
717 if glob_match.group(1): | |
718 branch_re += '/' + re.escape(glob_match.group(1)) | |
719 wildcard = glob_match.group(2) | |
720 if wildcard == '*': | |
721 branch_re += '([^/]*)' | |
722 else: | |
723 # Escape and replace surrounding braces with parentheses and commas | |
724 # with pipe symbols. | |
725 wildcard = re.escape(wildcard) | |
726 wildcard = re.sub('^\\\\{', '(', wildcard) | |
727 wildcard = re.sub('\\\\,', '|', wildcard) | |
728 wildcard = re.sub('\\\\}$', ')', wildcard) | |
729 branch_re += wildcard | |
730 if glob_match.group(3): | |
731 branch_re += re.escape(glob_match.group(3)) | |
732 match = re.match(branch_re, url) | |
733 if match: | |
734 return re.sub('\*$', match.group(1), as_ref) | |
735 | |
736 # Parse specs like "trunk/src:refs/remotes/origin/trunk". | |
737 if fetch_suburl: | |
738 full_url = base_url + '/' + fetch_suburl | |
739 else: | |
740 full_url = base_url | |
741 if full_url == url: | |
742 return as_ref | |
743 return None | |
744 | |
745 | |
746 def print_stats(similarity, find_copies, args): | 703 def print_stats(similarity, find_copies, args): |
747 """Prints statistics about the change to the user.""" | 704 """Prints statistics about the change to the user.""" |
748 # --no-ext-diff is broken in some versions of Git, so try to work around | 705 # --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 | 706 # this by overriding the environment (but there is still a problem if the |
750 # git config key "diff.external" is used). | 707 # git config key "diff.external" is used). |
751 env = GetNoGitPagerEnv() | 708 env = GetNoGitPagerEnv() |
752 if 'GIT_EXTERNAL_DIFF' in env: | 709 if 'GIT_EXTERNAL_DIFF' in env: |
753 del env['GIT_EXTERNAL_DIFF'] | 710 del env['GIT_EXTERNAL_DIFF'] |
754 | 711 |
755 if find_copies: | 712 if find_copies: |
(...skipping 13 matching lines...) Expand all Loading... |
769 | 726 |
770 class BuildbucketResponseException(Exception): | 727 class BuildbucketResponseException(Exception): |
771 pass | 728 pass |
772 | 729 |
773 | 730 |
774 class Settings(object): | 731 class Settings(object): |
775 def __init__(self): | 732 def __init__(self): |
776 self.default_server = None | 733 self.default_server = None |
777 self.cc = None | 734 self.cc = None |
778 self.root = None | 735 self.root = None |
779 self.is_git_svn = None | |
780 self.svn_branch = None | |
781 self.tree_status_url = None | 736 self.tree_status_url = None |
782 self.viewvc_url = None | 737 self.viewvc_url = None |
783 self.updated = False | 738 self.updated = False |
784 self.is_gerrit = None | 739 self.is_gerrit = None |
785 self.squash_gerrit_uploads = None | 740 self.squash_gerrit_uploads = None |
786 self.gerrit_skip_ensure_authenticated = None | 741 self.gerrit_skip_ensure_authenticated = None |
787 self.git_editor = None | 742 self.git_editor = None |
788 self.project = None | 743 self.project = None |
789 self.force_https_commit_url = None | 744 self.force_https_commit_url = None |
790 self.pending_ref_prefix = None | 745 self.pending_ref_prefix = None |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
832 if not os.path.isdir(local_url): | 787 if not os.path.isdir(local_url): |
833 return None | 788 return None |
834 git_cache.Mirror.SetCachePath(os.path.dirname(local_url)) | 789 git_cache.Mirror.SetCachePath(os.path.dirname(local_url)) |
835 remote_url = git_cache.Mirror.CacheDirToUrl(local_url) | 790 remote_url = git_cache.Mirror.CacheDirToUrl(local_url) |
836 # Use the /dev/null print_func to avoid terminal spew in WaitForRealCommit. | 791 # Use the /dev/null print_func to avoid terminal spew in WaitForRealCommit. |
837 mirror = git_cache.Mirror(remote_url, print_func = lambda *args: None) | 792 mirror = git_cache.Mirror(remote_url, print_func = lambda *args: None) |
838 if mirror.exists(): | 793 if mirror.exists(): |
839 return mirror | 794 return mirror |
840 return None | 795 return None |
841 | 796 |
842 def GetIsGitSvn(self): | |
843 """Return true if this repo looks like it's using git-svn.""" | |
844 if self.is_git_svn is None: | |
845 if self.GetPendingRefPrefix(): | |
846 # If PENDING_REF_PREFIX is set then it's a pure git repo no matter what. | |
847 self.is_git_svn = False | |
848 else: | |
849 # If you have any "svn-remote.*" config keys, we think you're using svn. | |
850 self.is_git_svn = RunGitWithCode( | |
851 ['config', '--local', '--get-regexp', r'^svn-remote\.'])[0] == 0 | |
852 return self.is_git_svn | |
853 | |
854 def GetSVNBranch(self): | |
855 if self.svn_branch is None: | |
856 if not self.GetIsGitSvn(): | |
857 DieWithError('Repo doesn\'t appear to be a git-svn repo.') | |
858 | |
859 # Try to figure out which remote branch we're based on. | |
860 # Strategy: | |
861 # 1) iterate through our branch history and find the svn URL. | |
862 # 2) find the svn-remote that fetches from the URL. | |
863 | |
864 # regexp matching the git-svn line that contains the URL. | |
865 git_svn_re = re.compile(r'^\s*git-svn-id: (\S+)@', re.MULTILINE) | |
866 | |
867 # We don't want to go through all of history, so read a line from the | |
868 # pipe at a time. | |
869 # The -100 is an arbitrary limit so we don't search forever. | |
870 cmd = ['git', 'log', '-100', '--pretty=medium'] | |
871 proc = subprocess2.Popen(cmd, stdout=subprocess2.PIPE, | |
872 env=GetNoGitPagerEnv()) | |
873 url = None | |
874 for line in proc.stdout: | |
875 match = git_svn_re.match(line) | |
876 if match: | |
877 url = match.group(1) | |
878 proc.stdout.close() # Cut pipe. | |
879 break | |
880 | |
881 if url: | |
882 svn_remote_re = re.compile(r'^svn-remote\.([^.]+)\.url (.*)$') | |
883 remotes = RunGit(['config', '--get-regexp', | |
884 r'^svn-remote\..*\.url']).splitlines() | |
885 for remote in remotes: | |
886 match = svn_remote_re.match(remote) | |
887 if match: | |
888 remote = match.group(1) | |
889 base_url = match.group(2) | |
890 rewrite_root = RunGit( | |
891 ['config', 'svn-remote.%s.rewriteRoot' % remote], | |
892 error_ok=True).strip() | |
893 if rewrite_root: | |
894 base_url = rewrite_root | |
895 fetch_spec = RunGit( | |
896 ['config', 'svn-remote.%s.fetch' % remote], | |
897 error_ok=True).strip() | |
898 if fetch_spec: | |
899 self.svn_branch = MatchSvnGlob(url, base_url, fetch_spec, False) | |
900 if self.svn_branch: | |
901 break | |
902 branch_spec = RunGit( | |
903 ['config', 'svn-remote.%s.branches' % remote], | |
904 error_ok=True).strip() | |
905 if branch_spec: | |
906 self.svn_branch = MatchSvnGlob(url, base_url, branch_spec, True) | |
907 if self.svn_branch: | |
908 break | |
909 tag_spec = RunGit( | |
910 ['config', 'svn-remote.%s.tags' % remote], | |
911 error_ok=True).strip() | |
912 if tag_spec: | |
913 self.svn_branch = MatchSvnGlob(url, base_url, tag_spec, True) | |
914 if self.svn_branch: | |
915 break | |
916 | |
917 if not self.svn_branch: | |
918 DieWithError('Can\'t guess svn branch -- try specifying it on the ' | |
919 'command line') | |
920 | |
921 return self.svn_branch | |
922 | |
923 def GetTreeStatusUrl(self, error_ok=False): | 797 def GetTreeStatusUrl(self, error_ok=False): |
924 if not self.tree_status_url: | 798 if not self.tree_status_url: |
925 error_message = ('You must configure your tree status URL by running ' | 799 error_message = ('You must configure your tree status URL by running ' |
926 '"git cl config".') | 800 '"git cl config".') |
927 self.tree_status_url = self._GetRietveldConfig( | 801 self.tree_status_url = self._GetRietveldConfig( |
928 'tree-status-url', error_ok=error_ok, error_message=error_message) | 802 'tree-status-url', error_ok=error_ok, error_message=error_message) |
929 return self.tree_status_url | 803 return self.tree_status_url |
930 | 804 |
931 def GetViewVCUrl(self): | 805 def GetViewVCUrl(self): |
932 if not self.viewvc_url: | 806 if not self.viewvc_url: |
(...skipping 457 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1390 upstream_branch = _git_get_branch_config_value('merge', branch=branch) | 1264 upstream_branch = _git_get_branch_config_value('merge', branch=branch) |
1391 | 1265 |
1392 if upstream_branch: | 1266 if upstream_branch: |
1393 remote = _git_get_branch_config_value('remote', branch=branch) | 1267 remote = _git_get_branch_config_value('remote', branch=branch) |
1394 else: | 1268 else: |
1395 upstream_branch = RunGit(['config', 'rietveld.upstream-branch'], | 1269 upstream_branch = RunGit(['config', 'rietveld.upstream-branch'], |
1396 error_ok=True).strip() | 1270 error_ok=True).strip() |
1397 if upstream_branch: | 1271 if upstream_branch: |
1398 remote = RunGit(['config', 'rietveld.upstream-remote']).strip() | 1272 remote = RunGit(['config', 'rietveld.upstream-remote']).strip() |
1399 else: | 1273 else: |
1400 # Fall back on trying a git-svn upstream branch. | 1274 # Else, try to guess the origin remote. |
1401 if settings.GetIsGitSvn(): | 1275 remote_branches = RunGit(['branch', '-r']).split() |
1402 upstream_branch = settings.GetSVNBranch() | 1276 if 'origin/master' in remote_branches: |
| 1277 # Fall back on origin/master if it exits. |
| 1278 remote = 'origin' |
| 1279 upstream_branch = 'refs/heads/master' |
1403 else: | 1280 else: |
1404 # Else, try to guess the origin remote. | 1281 DieWithError( |
1405 remote_branches = RunGit(['branch', '-r']).split() | 1282 'Unable to determine default branch to diff against.\n' |
1406 if 'origin/master' in remote_branches: | 1283 'Either pass complete "git diff"-style arguments, like\n' |
1407 # Fall back on origin/master if it exits. | 1284 ' git cl upload origin/master\n' |
1408 remote = 'origin' | 1285 'or verify this branch is set up to track another \n' |
1409 upstream_branch = 'refs/heads/master' | 1286 '(via the --track argument to "git checkout -b ...").') |
1410 elif 'origin/trunk' in remote_branches: | |
1411 # Fall back on origin/trunk if it exists. Generally a shared | |
1412 # git-svn clone | |
1413 remote = 'origin' | |
1414 upstream_branch = 'refs/heads/trunk' | |
1415 else: | |
1416 DieWithError( | |
1417 'Unable to determine default branch to diff against.\n' | |
1418 'Either pass complete "git diff"-style arguments, like\n' | |
1419 ' git cl upload origin/master\n' | |
1420 'or verify this branch is set up to track another \n' | |
1421 '(via the --track argument to "git checkout -b ...").') | |
1422 | 1287 |
1423 return remote, upstream_branch | 1288 return remote, upstream_branch |
1424 | 1289 |
1425 def GetCommonAncestorWithUpstream(self): | 1290 def GetCommonAncestorWithUpstream(self): |
1426 upstream_branch = self.GetUpstreamBranch() | 1291 upstream_branch = self.GetUpstreamBranch() |
1427 if not BranchExists(upstream_branch): | 1292 if not BranchExists(upstream_branch): |
1428 DieWithError('The upstream for the current branch (%s) does not exist ' | 1293 DieWithError('The upstream for the current branch (%s) does not exist ' |
1429 'anymore.\nPlease fix it and try again.' % self.GetBranch()) | 1294 'anymore.\nPlease fix it and try again.' % self.GetBranch()) |
1430 return git_common.get_or_create_merge_base(self.GetBranch(), | 1295 return git_common.get_or_create_merge_base(self.GetBranch(), |
1431 upstream_branch) | 1296 upstream_branch) |
(...skipping 18 matching lines...) Expand all Loading... |
1450 remote, branch = self.FetchUpstreamTuple(branch) | 1315 remote, branch = self.FetchUpstreamTuple(branch) |
1451 branch = ShortBranchName(branch) | 1316 branch = ShortBranchName(branch) |
1452 if remote != '.' or branch.startswith('refs/remotes'): | 1317 if remote != '.' or branch.startswith('refs/remotes'): |
1453 break | 1318 break |
1454 else: | 1319 else: |
1455 remotes = RunGit(['remote'], error_ok=True).split() | 1320 remotes = RunGit(['remote'], error_ok=True).split() |
1456 if len(remotes) == 1: | 1321 if len(remotes) == 1: |
1457 remote, = remotes | 1322 remote, = remotes |
1458 elif 'origin' in remotes: | 1323 elif 'origin' in remotes: |
1459 remote = 'origin' | 1324 remote = 'origin' |
1460 logging.warning('Could not determine which remote this change is ' | 1325 logging.warn('Could not determine which remote this change is ' |
1461 'associated with, so defaulting to "%s". This may ' | 1326 'associated with, so defaulting to "%s".' % self._remote) |
1462 'not be what you want. You may prevent this message ' | |
1463 'by running "git svn info" as documented here: %s', | |
1464 self._remote, | |
1465 GIT_INSTRUCTIONS_URL) | |
1466 else: | 1327 else: |
1467 logging.warn('Could not determine which remote this change is ' | 1328 logging.warn('Could not determine which remote this change is ' |
1468 'associated with. You may prevent this message by ' | 1329 'associated with.') |
1469 'running "git svn info" as documented here: %s', | |
1470 GIT_INSTRUCTIONS_URL) | |
1471 branch = 'HEAD' | 1330 branch = 'HEAD' |
1472 if branch.startswith('refs/remotes'): | 1331 if branch.startswith('refs/remotes'): |
1473 self._remote = (remote, branch) | 1332 self._remote = (remote, branch) |
1474 elif branch.startswith('refs/branch-heads/'): | 1333 elif branch.startswith('refs/branch-heads/'): |
1475 self._remote = (remote, branch.replace('refs/', 'refs/remotes/')) | 1334 self._remote = (remote, branch.replace('refs/', 'refs/remotes/')) |
1476 else: | 1335 else: |
1477 self._remote = (remote, 'refs/remotes/%s/%s' % (remote, branch)) | 1336 self._remote = (remote, 'refs/remotes/%s/%s' % (remote, branch)) |
1478 return self._remote | 1337 return self._remote |
1479 | 1338 |
1480 def GitSanityChecks(self, upstream_git_obj): | 1339 def GitSanityChecks(self, upstream_git_obj): |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1519 return False | 1378 return False |
1520 return True | 1379 return True |
1521 | 1380 |
1522 def GetGitBaseUrlFromConfig(self): | 1381 def GetGitBaseUrlFromConfig(self): |
1523 """Return the configured base URL from branch.<branchname>.baseurl. | 1382 """Return the configured base URL from branch.<branchname>.baseurl. |
1524 | 1383 |
1525 Returns None if it is not set. | 1384 Returns None if it is not set. |
1526 """ | 1385 """ |
1527 return self._GitGetBranchConfigValue('base-url') | 1386 return self._GitGetBranchConfigValue('base-url') |
1528 | 1387 |
1529 def GetGitSvnRemoteUrl(self): | |
1530 """Return the configured git-svn remote URL parsed from git svn info. | |
1531 | |
1532 Returns None if it is not set. | |
1533 """ | |
1534 # URL is dependent on the current directory. | |
1535 data = RunGit(['svn', 'info'], cwd=settings.GetRoot()) | |
1536 if data: | |
1537 keys = dict(line.split(': ', 1) for line in data.splitlines() | |
1538 if ': ' in line) | |
1539 return keys.get('URL', None) | |
1540 return None | |
1541 | |
1542 def GetRemoteUrl(self): | 1388 def GetRemoteUrl(self): |
1543 """Return the configured remote URL, e.g. 'git://example.org/foo.git/'. | 1389 """Return the configured remote URL, e.g. 'git://example.org/foo.git/'. |
1544 | 1390 |
1545 Returns None if there is no remote. | 1391 Returns None if there is no remote. |
1546 """ | 1392 """ |
1547 remote, _ = self.GetRemoteBranch() | 1393 remote, _ = self.GetRemoteBranch() |
1548 url = RunGit(['config', 'remote.%s.url' % remote], error_ok=True).strip() | 1394 url = RunGit(['config', 'remote.%s.url' % remote], error_ok=True).strip() |
1549 | 1395 |
1550 # If URL is pointing to a local directory, it is probably a git cache. | 1396 # If URL is pointing to a local directory, it is probably a git cache. |
1551 if os.path.isdir(url): | 1397 if os.path.isdir(url): |
(...skipping 734 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2286 if options.title is not None: | 2132 if options.title is not None: |
2287 upload_args.extend(['--title', options.title]) | 2133 upload_args.extend(['--title', options.title]) |
2288 if options.message: | 2134 if options.message: |
2289 upload_args.extend(['--message', options.message]) | 2135 upload_args.extend(['--message', options.message]) |
2290 upload_args.extend(['--issue', str(self.GetIssue())]) | 2136 upload_args.extend(['--issue', str(self.GetIssue())]) |
2291 print('This branch is associated with issue %s. ' | 2137 print('This branch is associated with issue %s. ' |
2292 'Adding patch to that issue.' % self.GetIssue()) | 2138 'Adding patch to that issue.' % self.GetIssue()) |
2293 else: | 2139 else: |
2294 if options.title is not None: | 2140 if options.title is not None: |
2295 upload_args.extend(['--title', options.title]) | 2141 upload_args.extend(['--title', options.title]) |
2296 message = (options.title or options.message or | 2142 if options.message: |
2297 CreateDescriptionFromLog(args)) | 2143 message = options.message |
| 2144 else: |
| 2145 message = CreateDescriptionFromLog(args) |
| 2146 if options.title: |
| 2147 message = options.title + '\n\n' + message |
2298 change_desc = ChangeDescription(message) | 2148 change_desc = ChangeDescription(message) |
2299 if options.reviewers or options.tbr_owners: | 2149 if options.reviewers or options.tbr_owners: |
2300 change_desc.update_reviewers(options.reviewers, | 2150 change_desc.update_reviewers(options.reviewers, |
2301 options.tbr_owners, | 2151 options.tbr_owners, |
2302 change) | 2152 change) |
2303 if not options.force: | 2153 if not options.force: |
2304 change_desc.prompt(bug=options.bug) | 2154 change_desc.prompt(bug=options.bug) |
2305 | 2155 |
2306 if not change_desc.description: | 2156 if not change_desc.description: |
2307 print('Description is empty; aborting.') | 2157 print('Description is empty; aborting.') |
(...skipping 28 matching lines...) Expand all Loading... |
2336 upload_args.append('--private') | 2186 upload_args.append('--private') |
2337 | 2187 |
2338 upload_args.extend(['--git_similarity', str(options.similarity)]) | 2188 upload_args.extend(['--git_similarity', str(options.similarity)]) |
2339 if not options.find_copies: | 2189 if not options.find_copies: |
2340 upload_args.extend(['--git_no_find_copies']) | 2190 upload_args.extend(['--git_no_find_copies']) |
2341 | 2191 |
2342 # Include the upstream repo's URL in the change -- this is useful for | 2192 # Include the upstream repo's URL in the change -- this is useful for |
2343 # projects that have their source spread across multiple repos. | 2193 # projects that have their source spread across multiple repos. |
2344 remote_url = self.GetGitBaseUrlFromConfig() | 2194 remote_url = self.GetGitBaseUrlFromConfig() |
2345 if not remote_url: | 2195 if not remote_url: |
2346 if settings.GetIsGitSvn(): | 2196 if self.GetRemoteUrl() and '/' in self.GetUpstreamBranch(): |
2347 remote_url = self.GetGitSvnRemoteUrl() | 2197 remote_url = '%s@%s' % (self.GetRemoteUrl(), |
2348 else: | 2198 self.GetUpstreamBranch().split('/')[-1]) |
2349 if self.GetRemoteUrl() and '/' in self.GetUpstreamBranch(): | |
2350 remote_url = '%s@%s' % (self.GetRemoteUrl(), | |
2351 self.GetUpstreamBranch().split('/')[-1]) | |
2352 if remote_url: | 2199 if remote_url: |
2353 remote, remote_branch = self.GetRemoteBranch() | 2200 remote, remote_branch = self.GetRemoteBranch() |
2354 target_ref = GetTargetRef(remote, remote_branch, options.target_branch, | 2201 target_ref = GetTargetRef(remote, remote_branch, options.target_branch, |
2355 pending_prefix_check=True, | 2202 pending_prefix_check=True, |
2356 remote_url=self.GetRemoteUrl()) | 2203 remote_url=self.GetRemoteUrl()) |
2357 if target_ref: | 2204 if target_ref: |
2358 upload_args.extend(['--target_ref', target_ref]) | 2205 upload_args.extend(['--target_ref', target_ref]) |
2359 | 2206 |
2360 # Look for dependent patchsets. See crbug.com/480453 for more details. | 2207 # Look for dependent patchsets. See crbug.com/480453 for more details. |
2361 remote, upstream_branch = self.FetchUpstreamTuple(self.GetBranch()) | 2208 remote, upstream_branch = self.FetchUpstreamTuple(self.GetBranch()) |
(...skipping 1977 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
4339 git config --unset branch.branch_name.skip-deps-uploads | 4186 git config --unset branch.branch_name.skip-deps-uploads |
4340 Can also set the above globally by using the --global flag. | 4187 Can also set the above globally by using the --global flag. |
4341 """ | 4188 """ |
4342 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks', | 4189 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks', |
4343 help='bypass upload presubmit hook') | 4190 help='bypass upload presubmit hook') |
4344 parser.add_option('--bypass-watchlists', action='store_true', | 4191 parser.add_option('--bypass-watchlists', action='store_true', |
4345 dest='bypass_watchlists', | 4192 dest='bypass_watchlists', |
4346 help='bypass watchlists auto CC-ing reviewers') | 4193 help='bypass watchlists auto CC-ing reviewers') |
4347 parser.add_option('-f', action='store_true', dest='force', | 4194 parser.add_option('-f', action='store_true', dest='force', |
4348 help="force yes to questions (don't prompt)") | 4195 help="force yes to questions (don't prompt)") |
4349 parser.add_option('-m', dest='message', help='message for patchset') | 4196 parser.add_option('--message', '-m', dest='message', |
| 4197 help='message for patchset') |
4350 parser.add_option('-b', '--bug', | 4198 parser.add_option('-b', '--bug', |
4351 help='pre-populate the bug number(s) for this issue. ' | 4199 help='pre-populate the bug number(s) for this issue. ' |
4352 'If several, separate with commas') | 4200 'If several, separate with commas') |
4353 parser.add_option('--message-file', dest='message_file', | 4201 parser.add_option('--message-file', dest='message_file', |
4354 help='file which contains message for patchset') | 4202 help='file which contains message for patchset') |
4355 parser.add_option('-t', dest='title', | 4203 parser.add_option('--title', '-t', dest='title', |
4356 help='title for patchset (Rietveld only)') | 4204 help='title for patchset (Rietveld only)') |
4357 parser.add_option('-r', '--reviewers', | 4205 parser.add_option('-r', '--reviewers', |
4358 action='append', default=[], | 4206 action='append', default=[], |
4359 help='reviewer email addresses') | 4207 help='reviewer email addresses') |
4360 parser.add_option('--cc', | 4208 parser.add_option('--cc', |
4361 action='append', default=[], | 4209 action='append', default=[], |
4362 help='cc email addresses') | 4210 help='cc email addresses') |
4363 parser.add_option('-s', '--send-mail', action='store_true', | 4211 parser.add_option('-s', '--send-mail', action='store_true', |
4364 help='send email to reviewer immediately') | 4212 help='send email to reviewer immediately') |
4365 parser.add_option('--emulate_svn_auto_props', | 4213 parser.add_option('--emulate_svn_auto_props', |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
4418 if options.cq_dry_run and options.use_commit_queue: | 4266 if options.cq_dry_run and options.use_commit_queue: |
4419 parser.error('only one of --use-commit-queue and --cq-dry-run allowed.') | 4267 parser.error('only one of --use-commit-queue and --cq-dry-run allowed.') |
4420 | 4268 |
4421 # For sanity of test expectations, do this otherwise lazy-loading *now*. | 4269 # For sanity of test expectations, do this otherwise lazy-loading *now*. |
4422 settings.GetIsGerrit() | 4270 settings.GetIsGerrit() |
4423 | 4271 |
4424 cl = Changelist(auth_config=auth_config, codereview=options.forced_codereview) | 4272 cl = Changelist(auth_config=auth_config, codereview=options.forced_codereview) |
4425 return cl.CMDUpload(options, args, orig_args) | 4273 return cl.CMDUpload(options, args, orig_args) |
4426 | 4274 |
4427 | 4275 |
4428 def IsSubmoduleMergeCommit(ref): | |
4429 # When submodules are added to the repo, we expect there to be a single | |
4430 # non-git-svn merge commit at remote HEAD with a signature comment. | |
4431 pattern = '^SVN changes up to revision [0-9]*$' | |
4432 cmd = ['rev-list', '--merges', '--grep=%s' % pattern, '%s^!' % ref] | |
4433 return RunGit(cmd) != '' | |
4434 | |
4435 | |
4436 def SendUpstream(parser, args, cmd): | 4276 def SendUpstream(parser, args, cmd): |
4437 """Common code for CMDland and CmdDCommit | 4277 """Common code for CMDland and CmdDCommit |
4438 | 4278 |
4439 In case of Gerrit, uses Gerrit REST api to "submit" the issue, which pushes | 4279 In case of Gerrit, uses Gerrit REST api to "submit" the issue, which pushes |
4440 upstream and closes the issue automatically and atomically. | 4280 upstream and closes the issue automatically and atomically. |
4441 | 4281 |
4442 Otherwise (in case of Rietveld): | 4282 Otherwise (in case of Rietveld): |
4443 Squashes branch into a single commit. | 4283 Squashes branch into a single commit. |
4444 Updates commit message with metadata (e.g. pointer to review). | 4284 Updates commit message with metadata (e.g. pointer to review). |
4445 Pushes the code upstream. | 4285 Pushes the code upstream. |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
4479 '"Forge-Author" permission.') | 4319 '"Forge-Author" permission.') |
4480 if not cl.GetIssue(): | 4320 if not cl.GetIssue(): |
4481 DieWithError('You must upload the change first to Gerrit.\n' | 4321 DieWithError('You must upload the change first to Gerrit.\n' |
4482 ' If you would rather have `git cl land` upload ' | 4322 ' If you would rather have `git cl land` upload ' |
4483 'automatically for you, see http://crbug.com/642759') | 4323 'automatically for you, see http://crbug.com/642759') |
4484 return cl._codereview_impl.CMDLand(options.force, options.bypass_hooks, | 4324 return cl._codereview_impl.CMDLand(options.force, options.bypass_hooks, |
4485 options.verbose) | 4325 options.verbose) |
4486 | 4326 |
4487 current = cl.GetBranch() | 4327 current = cl.GetBranch() |
4488 remote, upstream_branch = cl.FetchUpstreamTuple(cl.GetBranch()) | 4328 remote, upstream_branch = cl.FetchUpstreamTuple(cl.GetBranch()) |
4489 if not settings.GetIsGitSvn() and remote == '.': | 4329 if remote == '.': |
4490 print() | 4330 print() |
4491 print('Attempting to push branch %r into another local branch!' % current) | 4331 print('Attempting to push branch %r into another local branch!' % current) |
4492 print() | 4332 print() |
4493 print('Either reparent this branch on top of origin/master:') | 4333 print('Either reparent this branch on top of origin/master:') |
4494 print(' git reparent-branch --root') | 4334 print(' git reparent-branch --root') |
4495 print() | 4335 print() |
4496 print('OR run `git rebase-update` if you think the parent branch is ') | 4336 print('OR run `git rebase-update` if you think the parent branch is ') |
4497 print('already committed.') | 4337 print('already committed.') |
4498 print() | 4338 print() |
4499 print(' Current parent: %r' % upstream_branch) | 4339 print(' Current parent: %r' % upstream_branch) |
4500 return 1 | 4340 return 1 |
4501 | 4341 |
4502 if not args or cmd == 'land': | 4342 if not args: |
4503 # Default to merging against our best guess of the upstream branch. | 4343 # Default to merging against our best guess of the upstream branch. |
4504 args = [cl.GetUpstreamBranch()] | 4344 args = [cl.GetUpstreamBranch()] |
4505 | 4345 |
4506 if options.contributor: | 4346 if options.contributor: |
4507 if not re.match('^.*\s<\S+@\S+>$', options.contributor): | 4347 if not re.match('^.*\s<\S+@\S+>$', options.contributor): |
4508 print("Please provide contibutor as 'First Last <email@example.com>'") | 4348 print("Please provide contibutor as 'First Last <email@example.com>'") |
4509 return 1 | 4349 return 1 |
4510 | 4350 |
4511 base_branch = args[0] | 4351 base_branch = args[0] |
4512 base_has_submodules = IsSubmoduleMergeCommit(base_branch) | |
4513 | 4352 |
4514 if git_common.is_dirty_git_tree(cmd): | 4353 if git_common.is_dirty_git_tree('land'): |
4515 return 1 | 4354 return 1 |
4516 | 4355 |
4517 # This rev-list syntax means "show all commits not in my branch that | 4356 # This rev-list syntax means "show all commits not in my branch that |
4518 # are in base_branch". | 4357 # are in base_branch". |
4519 upstream_commits = RunGit(['rev-list', '^' + cl.GetBranchRef(), | 4358 upstream_commits = RunGit(['rev-list', '^' + cl.GetBranchRef(), |
4520 base_branch]).splitlines() | 4359 base_branch]).splitlines() |
4521 if upstream_commits: | 4360 if upstream_commits: |
4522 print('Base branch "%s" has %d commits ' | 4361 print('Base branch "%s" has %d commits ' |
4523 'not in this branch.' % (base_branch, len(upstream_commits))) | 4362 'not in this branch.' % (base_branch, len(upstream_commits))) |
4524 print('Run "git merge %s" before attempting to %s.' % (base_branch, cmd)) | 4363 print('Run "git merge %s" before attempting to land.' % base_branch) |
4525 return 1 | 4364 return 1 |
4526 | 4365 |
4527 # This is the revision `svn dcommit` will commit on top of. | |
4528 svn_head = None | |
4529 if cmd == 'dcommit' or base_has_submodules: | |
4530 svn_head = RunGit(['log', '--grep=^git-svn-id:', '-1', | |
4531 '--pretty=format:%H']) | |
4532 | |
4533 if cmd == 'dcommit': | |
4534 # If the base_head is a submodule merge commit, the first parent of the | |
4535 # base_head should be a git-svn commit, which is what we're interested in. | |
4536 base_svn_head = base_branch | |
4537 if base_has_submodules: | |
4538 base_svn_head += '^1' | |
4539 | |
4540 extra_commits = RunGit(['rev-list', '^' + svn_head, base_svn_head]) | |
4541 if extra_commits: | |
4542 print('This branch has %d additional commits not upstreamed yet.' | |
4543 % len(extra_commits.splitlines())) | |
4544 print('Upstream "%s" or rebase this branch on top of the upstream trunk ' | |
4545 'before attempting to %s.' % (base_branch, cmd)) | |
4546 return 1 | |
4547 | |
4548 merge_base = RunGit(['merge-base', base_branch, 'HEAD']).strip() | 4366 merge_base = RunGit(['merge-base', base_branch, 'HEAD']).strip() |
4549 if not options.bypass_hooks: | 4367 if not options.bypass_hooks: |
4550 author = None | 4368 author = None |
4551 if options.contributor: | 4369 if options.contributor: |
4552 author = re.search(r'\<(.*)\>', options.contributor).group(1) | 4370 author = re.search(r'\<(.*)\>', options.contributor).group(1) |
4553 hook_results = cl.RunHook( | 4371 hook_results = cl.RunHook( |
4554 committing=True, | 4372 committing=True, |
4555 may_prompt=not options.force, | 4373 may_prompt=not options.force, |
4556 verbose=options.verbose, | 4374 verbose=options.verbose, |
4557 change=cl.GetChange(merge_base, author)) | 4375 change=cl.GetChange(merge_base, author)) |
4558 if not hook_results.should_continue(): | 4376 if not hook_results.should_continue(): |
4559 return 1 | 4377 return 1 |
4560 | 4378 |
4561 # Check the tree status if the tree status URL is set. | 4379 # Check the tree status if the tree status URL is set. |
4562 status = GetTreeStatus() | 4380 status = GetTreeStatus() |
4563 if 'closed' == status: | 4381 if 'closed' == status: |
4564 print('The tree is closed. Please wait for it to reopen. Use ' | 4382 print('The tree is closed. Please wait for it to reopen. Use ' |
4565 '"git cl %s --bypass-hooks" to commit on a closed tree.' % cmd) | 4383 '"git cl land --bypass-hooks" to commit on a closed tree.') |
4566 return 1 | 4384 return 1 |
4567 elif 'unknown' == status: | 4385 elif 'unknown' == status: |
4568 print('Unable to determine tree status. Please verify manually and ' | 4386 print('Unable to determine tree status. Please verify manually and ' |
4569 'use "git cl %s --bypass-hooks" to commit on a closed tree.' % cmd) | 4387 'use "git cl land --bypass-hooks" to commit on a closed tree.') |
4570 return 1 | 4388 return 1 |
4571 | 4389 |
4572 change_desc = ChangeDescription(options.message) | 4390 change_desc = ChangeDescription(options.message) |
4573 if not change_desc.description and cl.GetIssue(): | 4391 if not change_desc.description and cl.GetIssue(): |
4574 change_desc = ChangeDescription(cl.GetDescription()) | 4392 change_desc = ChangeDescription(cl.GetDescription()) |
4575 | 4393 |
4576 if not change_desc.description: | 4394 if not change_desc.description: |
4577 if not cl.GetIssue() and options.bypass_hooks: | 4395 if not cl.GetIssue() and options.bypass_hooks: |
4578 change_desc = ChangeDescription(CreateDescriptionFromLog([merge_base])) | 4396 change_desc = ChangeDescription(CreateDescriptionFromLog([merge_base])) |
4579 else: | 4397 else: |
(...skipping 19 matching lines...) Expand all Loading... |
4599 | 4417 |
4600 print('Description:') | 4418 print('Description:') |
4601 print(commit_desc.description) | 4419 print(commit_desc.description) |
4602 | 4420 |
4603 branches = [merge_base, cl.GetBranchRef()] | 4421 branches = [merge_base, cl.GetBranchRef()] |
4604 if not options.force: | 4422 if not options.force: |
4605 print_stats(options.similarity, options.find_copies, branches) | 4423 print_stats(options.similarity, options.find_copies, branches) |
4606 | 4424 |
4607 # We want to squash all this branch's commits into one commit with the proper | 4425 # We want to squash all this branch's commits into one commit with the proper |
4608 # description. We do this by doing a "reset --soft" to the base branch (which | 4426 # description. We do this by doing a "reset --soft" to the base branch (which |
4609 # keeps the working copy the same), then dcommitting that. If origin/master | 4427 # keeps the working copy the same), then dcommitting that. |
4610 # has a submodule merge commit, we'll also need to cherry-pick the squashed | |
4611 # commit onto a branch based on the git-svn head. | |
4612 MERGE_BRANCH = 'git-cl-commit' | 4428 MERGE_BRANCH = 'git-cl-commit' |
4613 CHERRY_PICK_BRANCH = 'git-cl-cherry-pick' | 4429 CHERRY_PICK_BRANCH = 'git-cl-cherry-pick' |
4614 # Delete the branches if they exist. | 4430 # Delete the branches if they exist. |
4615 for branch in [MERGE_BRANCH, CHERRY_PICK_BRANCH]: | 4431 for branch in [MERGE_BRANCH, CHERRY_PICK_BRANCH]: |
4616 showref_cmd = ['show-ref', '--quiet', '--verify', 'refs/heads/%s' % branch] | 4432 showref_cmd = ['show-ref', '--quiet', '--verify', 'refs/heads/%s' % branch] |
4617 result = RunGitWithCode(showref_cmd) | 4433 result = RunGitWithCode(showref_cmd) |
4618 if result[0] == 0: | 4434 if result[0] == 0: |
4619 RunGit(['branch', '-D', branch]) | 4435 RunGit(['branch', '-D', branch]) |
4620 | 4436 |
4621 # We might be in a directory that's present in this branch but not in the | 4437 # We might be in a directory that's present in this branch but not in the |
(...skipping 14 matching lines...) Expand all Loading... |
4636 RunGit(['checkout', '-q', '-b', MERGE_BRANCH]) | 4452 RunGit(['checkout', '-q', '-b', MERGE_BRANCH]) |
4637 RunGit(['reset', '--soft', merge_base]) | 4453 RunGit(['reset', '--soft', merge_base]) |
4638 if options.contributor: | 4454 if options.contributor: |
4639 RunGit( | 4455 RunGit( |
4640 [ | 4456 [ |
4641 'commit', '--author', options.contributor, | 4457 'commit', '--author', options.contributor, |
4642 '-m', commit_desc.description, | 4458 '-m', commit_desc.description, |
4643 ]) | 4459 ]) |
4644 else: | 4460 else: |
4645 RunGit(['commit', '-m', commit_desc.description]) | 4461 RunGit(['commit', '-m', commit_desc.description]) |
4646 if base_has_submodules: | 4462 |
4647 cherry_pick_commit = RunGit(['rev-list', 'HEAD^!']).rstrip() | 4463 remote, branch = cl.FetchUpstreamTuple(cl.GetBranch()) |
4648 RunGit(['branch', CHERRY_PICK_BRANCH, svn_head]) | 4464 mirror = settings.GetGitMirror(remote) |
4649 RunGit(['checkout', CHERRY_PICK_BRANCH]) | 4465 if mirror: |
4650 RunGit(['cherry-pick', cherry_pick_commit]) | 4466 pushurl = mirror.url |
4651 if cmd == 'land': | 4467 git_numberer = _GitNumbererState.load(pushurl, branch) |
4652 remote, branch = cl.FetchUpstreamTuple(cl.GetBranch()) | 4468 else: |
4653 logging.debug('remote: %s, branch %s', remote, branch) | 4469 pushurl = remote # Usually, this is 'origin'. |
4654 mirror = settings.GetGitMirror(remote) | 4470 git_numberer = _GitNumbererState.load( |
4655 if mirror: | 4471 RunGit(['config', 'remote.%s.url' % remote]).strip(), branch) |
4656 pushurl = mirror.url | |
4657 git_numberer = _GitNumbererState.load(pushurl, branch) | |
4658 else: | |
4659 pushurl = remote # Usually, this is 'origin'. | |
4660 git_numberer = _GitNumbererState.load( | |
4661 RunGit(['config', 'remote.%s.url' % remote]).strip(), branch) | |
4662 | 4472 |
4663 pending_prefix = git_numberer.pending_prefix | 4473 pending_prefix = git_numberer.pending_prefix |
4664 | 4474 |
4665 if git_numberer.should_git_number: | 4475 if git_numberer.should_git_number: |
4666 # TODO(tandrii): run git fetch in a loop + autorebase when there there | 4476 # TODO(tandrii): run git fetch in a loop + autorebase when there there |
4667 # is no pending ref to push to? | 4477 # is no pending ref to push to? |
4668 logging.debug('Adding git number footers') | 4478 logging.debug('Adding git number footers') |
4669 parent_msg = RunGit(['show', '-s', '--format=%B', merge_base]).strip() | 4479 parent_msg = RunGit(['show', '-s', '--format=%B', merge_base]).strip() |
4670 commit_desc.update_with_git_number_footers(merge_base, parent_msg, | 4480 commit_desc.update_with_git_number_footers(merge_base, parent_msg, |
4671 branch) | 4481 branch) |
4672 # Ensure timestamps are monotonically increasing. | 4482 # Ensure timestamps are monotonically increasing. |
4673 timestamp = max(1 + _get_committer_timestamp(merge_base), | 4483 timestamp = max(1 + _get_committer_timestamp(merge_base), |
4674 _get_committer_timestamp('HEAD')) | 4484 _get_committer_timestamp('HEAD')) |
4675 _git_amend_head(commit_desc.description, timestamp) | 4485 _git_amend_head(commit_desc.description, timestamp) |
4676 change_desc = ChangeDescription(commit_desc.description) | 4486 change_desc = ChangeDescription(commit_desc.description) |
4677 # If gnumbd is sitll ON and we ultimately push to branch with | 4487 # If gnumbd is sitll ON and we ultimately push to branch with |
4678 # pending_prefix, gnumbd will modify footers we've just inserted with | 4488 # pending_prefix, gnumbd will modify footers we've just inserted with |
4679 # 'Original-', which is annoying but still technically correct. | 4489 # 'Original-', which is annoying but still technically correct. |
4680 | 4490 |
4681 if not pending_prefix or branch.startswith(pending_prefix): | 4491 if not pending_prefix or branch.startswith(pending_prefix): |
4682 # If not using refs/pending/heads/* at all, or target ref is already set | 4492 # If not using refs/pending/heads/* at all, or target ref is already set |
4683 # to pending, then push to the target ref directly. | 4493 # to pending, then push to the target ref directly. |
4684 # NB(tandrii): I think branch.startswith(pending_prefix) never happens | 4494 # NB(tandrii): I think branch.startswith(pending_prefix) never happens |
4685 # in practise. I really tried to create a new branch tracking | 4495 # in practise. I really tried to create a new branch tracking |
4686 # refs/pending/heads/master directly and git cl land failed long before | 4496 # refs/pending/heads/master directly and git cl land failed long before |
4687 # reaching this. Disagree? Comment on http://crbug.com/642493. | 4497 # reaching this. Disagree? Comment on http://crbug.com/642493. |
4688 if pending_prefix: | 4498 if pending_prefix: |
4689 print('\n\nYOU GOT A CHANCE TO WIN A FREE GIFT!\n\n' | 4499 print('\n\nYOU GOT A CHANCE TO WIN A FREE GIFT!\n\n' |
4690 'Grab your .git/config, add instructions how to reproduce ' | 4500 'Grab your .git/config, add instructions how to reproduce ' |
4691 'this, and post it to http://crbug.com/642493.\n' | 4501 'this, and post it to http://crbug.com/642493.\n' |
4692 'The first reporter gets a free "Black Swan" book from ' | 4502 'The first reporter gets a free "Black Swan" book from ' |
4693 'tandrii@\n\n') | 4503 'tandrii@\n\n') |
4694 retcode, output = RunGitWithCode( | 4504 retcode, output = RunGitWithCode( |
4695 ['push', '--porcelain', pushurl, 'HEAD:%s' % branch]) | 4505 ['push', '--porcelain', pushurl, 'HEAD:%s' % branch]) |
4696 pushed_to_pending = pending_prefix and branch.startswith(pending_prefix) | 4506 pushed_to_pending = pending_prefix and branch.startswith(pending_prefix) |
4697 else: | |
4698 # Cherry-pick the change on top of pending ref and then push it. | |
4699 assert branch.startswith('refs/'), branch | |
4700 assert pending_prefix[-1] == '/', pending_prefix | |
4701 pending_ref = pending_prefix + branch[len('refs/'):] | |
4702 retcode, output = PushToGitPending(pushurl, pending_ref) | |
4703 pushed_to_pending = (retcode == 0) | |
4704 if retcode == 0: | |
4705 revision = RunGit(['rev-parse', 'HEAD']).strip() | |
4706 else: | 4507 else: |
4707 # dcommit the merge branch. | 4508 # Cherry-pick the change on top of pending ref and then push it. |
4708 cmd_args = [ | 4509 assert branch.startswith('refs/'), branch |
4709 'svn', 'dcommit', | 4510 assert pending_prefix[-1] == '/', pending_prefix |
4710 '-C%s' % options.similarity, | 4511 pending_ref = pending_prefix + branch[len('refs/'):] |
4711 '--no-rebase', '--rmdir', | 4512 retcode, output = PushToGitPending(pushurl, pending_ref) |
4712 ] | 4513 pushed_to_pending = (retcode == 0) |
4713 if settings.GetForceHttpsCommitUrl(): | 4514 if retcode == 0: |
4714 # Allow forcing https commit URLs for some projects that don't allow | 4515 revision = RunGit(['rev-parse', 'HEAD']).strip() |
4715 # committing to http URLs (like Google Code). | |
4716 remote_url = cl.GetGitSvnRemoteUrl() | |
4717 if urlparse.urlparse(remote_url).scheme == 'http': | |
4718 remote_url = remote_url.replace('http://', 'https://') | |
4719 cmd_args.append('--commit-url=%s' % remote_url) | |
4720 _, output = RunGitWithCode(cmd_args) | |
4721 if 'Committed r' in output: | |
4722 revision = re.match( | |
4723 '.*?\nCommitted r(\\d+)', output, re.DOTALL).group(1) | |
4724 logging.debug(output) | 4516 logging.debug(output) |
4725 except: # pylint: disable=W0702 | 4517 except: # pylint: disable=W0702 |
4726 if _IS_BEING_TESTED: | 4518 if _IS_BEING_TESTED: |
4727 logging.exception('this is likely your ACTUAL cause of test failure.\n' | 4519 logging.exception('this is likely your ACTUAL cause of test failure.\n' |
4728 + '-' * 30 + '8<' + '-' * 30) | 4520 + '-' * 30 + '8<' + '-' * 30) |
4729 logging.error('\n' + '-' * 30 + '8<' + '-' * 30 + '\n\n\n') | 4521 logging.error('\n' + '-' * 30 + '8<' + '-' * 30 + '\n\n\n') |
4730 raise | 4522 raise |
4731 finally: | 4523 finally: |
4732 # And then swap back to the original branch and clean up. | 4524 # And then swap back to the original branch and clean up. |
4733 RunGit(['checkout', '-q', cl.GetBranch()]) | 4525 RunGit(['checkout', '-q', cl.GetBranch()]) |
4734 RunGit(['branch', '-D', MERGE_BRANCH]) | 4526 RunGit(['branch', '-D', MERGE_BRANCH]) |
4735 if base_has_submodules: | |
4736 RunGit(['branch', '-D', CHERRY_PICK_BRANCH]) | |
4737 | 4527 |
4738 if not revision: | 4528 if not revision: |
4739 print('Failed to push. If this persists, please file a bug.') | 4529 print('Failed to push. If this persists, please file a bug.') |
4740 return 1 | 4530 return 1 |
4741 | 4531 |
4742 killed = False | 4532 killed = False |
4743 if pushed_to_pending: | 4533 if pushed_to_pending: |
4744 try: | 4534 try: |
4745 revision = WaitForRealCommit(remote, revision, base_branch, branch) | 4535 revision = WaitForRealCommit(remote, revision, base_branch, branch) |
4746 # We set pushed_to_pending to False, since it made it all the way to the | 4536 # We set pushed_to_pending to False, since it made it all the way to the |
(...skipping 24 matching lines...) Expand all Loading... |
4771 else: | 4561 else: |
4772 comment += ' (presubmit successful).' | 4562 comment += ' (presubmit successful).' |
4773 cl.RpcServer().add_comment(cl.GetIssue(), comment) | 4563 cl.RpcServer().add_comment(cl.GetIssue(), comment) |
4774 | 4564 |
4775 if pushed_to_pending: | 4565 if pushed_to_pending: |
4776 _, branch = cl.FetchUpstreamTuple(cl.GetBranch()) | 4566 _, branch = cl.FetchUpstreamTuple(cl.GetBranch()) |
4777 print('The commit is in the pending queue (%s).' % pending_ref) | 4567 print('The commit is in the pending queue (%s).' % pending_ref) |
4778 print('It will show up on %s in ~1 min, once it gets a Cr-Commit-Position ' | 4568 print('It will show up on %s in ~1 min, once it gets a Cr-Commit-Position ' |
4779 'footer.' % branch) | 4569 'footer.' % branch) |
4780 | 4570 |
4781 hook = POSTUPSTREAM_HOOK_PATTERN % cmd | 4571 hook = POSTUPSTREAM_HOOK_PATTERN |
4782 if os.path.isfile(hook): | 4572 if os.path.isfile(hook): |
4783 RunCommand([hook, merge_base], error_ok=True) | 4573 RunCommand([hook, merge_base], error_ok=True) |
4784 | 4574 |
4785 return 1 if killed else 0 | 4575 return 1 if killed else 0 |
4786 | 4576 |
4787 | 4577 |
4788 def WaitForRealCommit(remote, pushed_commit, local_base_ref, real_ref): | 4578 def WaitForRealCommit(remote, pushed_commit, local_base_ref, real_ref): |
4789 print() | 4579 print() |
4790 print('Waiting for commit to be landed on %s...' % real_ref) | 4580 print('Waiting for commit to be landed on %s...' % real_ref) |
4791 print('(If you are impatient, you may Ctrl-C once without harm)') | 4581 print('(If you are impatient, you may Ctrl-C once without harm)') |
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
4871 | 4661 |
4872 print('All attempts to push to pending ref failed.') | 4662 print('All attempts to push to pending ref failed.') |
4873 return code, out | 4663 return code, out |
4874 | 4664 |
4875 | 4665 |
4876 def IsFatalPushFailure(push_stdout): | 4666 def IsFatalPushFailure(push_stdout): |
4877 """True if retrying push won't help.""" | 4667 """True if retrying push won't help.""" |
4878 return '(prohibited by Gerrit)' in push_stdout | 4668 return '(prohibited by Gerrit)' in push_stdout |
4879 | 4669 |
4880 | 4670 |
4881 @subcommand.usage('[upstream branch to apply against]') | 4671 @subcommand.usage('DEPRECATED') |
4882 def CMDdcommit(parser, args): | 4672 def CMDdcommit(parser, args): |
4883 """Commits the current changelist via git-svn.""" | 4673 """DEPRECATED: Used to commit the current changelist via git-svn.""" |
4884 if not settings.GetIsGitSvn(): | 4674 message = ('git-cl no longer supports committing to SVN repositories via ' |
4885 if git_footers.get_footer_svn_id(): | 4675 'git-svn. You probably want to use `git cl land` instead.') |
4886 # If it looks like previous commits were mirrored with git-svn. | 4676 print(message) |
4887 message = """This repository appears to be a git-svn mirror, but we | 4677 return 1 |
4888 don't support git-svn mirrors anymore.""" | |
4889 else: | |
4890 message = """This doesn't appear to be an SVN repository. | |
4891 If your project has a true, writeable git repository, you probably want to run | |
4892 'git cl land' instead. | |
4893 If your project has a git mirror of an upstream SVN master, you probably need | |
4894 to run 'git svn init'. | |
4895 | |
4896 Using the wrong command might cause your commit to appear to succeed, and the | |
4897 review to be closed, without actually landing upstream. If you choose to | |
4898 proceed, please verify that the commit lands upstream as expected.""" | |
4899 print(message) | |
4900 ask_for_data('[Press enter to dcommit or ctrl-C to quit]') | |
4901 print('WARNING: chrome infrastructure is migrating SVN repos to Git.\n' | |
4902 'Please let us know of this project you are committing to:' | |
4903 ' http://crbug.com/600451') | |
4904 return SendUpstream(parser, args, 'dcommit') | |
4905 | 4678 |
4906 | 4679 |
4907 @subcommand.usage('[upstream branch to apply against]') | 4680 @subcommand.usage('[upstream branch to apply against]') |
4908 def CMDland(parser, args): | 4681 def CMDland(parser, args): |
4909 """Commits the current changelist via git.""" | 4682 """Commits the current changelist via git.""" |
4910 if settings.GetIsGitSvn() or git_footers.get_footer_svn_id(): | |
4911 print('This appears to be an SVN repository.') | |
4912 print('Are you sure you didn\'t mean \'git cl dcommit\'?') | |
4913 print('(Ignore if this is the first commit after migrating from svn->git)') | |
4914 ask_for_data('[Press enter to push or ctrl-C to quit]') | |
4915 return SendUpstream(parser, args, 'land') | 4683 return SendUpstream(parser, args, 'land') |
4916 | 4684 |
4917 | 4685 |
4918 @subcommand.usage('<patch url or issue id or issue url>') | 4686 @subcommand.usage('<patch url or issue id or issue url>') |
4919 def CMDpatch(parser, args): | 4687 def CMDpatch(parser, args): |
4920 """Patches in a code review.""" | 4688 """Patches in a code review.""" |
4921 parser.add_option('-b', dest='newbranch', | 4689 parser.add_option('-b', dest='newbranch', |
4922 help='create a new branch off trunk for the patch') | 4690 help='create a new branch off trunk for the patch') |
4923 parser.add_option('-f', '--force', action='store_true', | 4691 parser.add_option('-f', '--force', action='store_true', |
4924 help='with -b, clobber any existing branch') | 4692 help='with -b, clobber any existing branch') |
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
4998 parser.error('--reject is not supported with Gerrit codereview.') | 4766 parser.error('--reject is not supported with Gerrit codereview.') |
4999 if options.nocommit: | 4767 if options.nocommit: |
5000 parser.error('--nocommit is not supported with Gerrit codereview.') | 4768 parser.error('--nocommit is not supported with Gerrit codereview.') |
5001 if options.directory: | 4769 if options.directory: |
5002 parser.error('--directory is not supported with Gerrit codereview.') | 4770 parser.error('--directory is not supported with Gerrit codereview.') |
5003 | 4771 |
5004 return cl.CMDPatchIssue(args[0], options.reject, options.nocommit, | 4772 return cl.CMDPatchIssue(args[0], options.reject, options.nocommit, |
5005 options.directory) | 4773 options.directory) |
5006 | 4774 |
5007 | 4775 |
5008 def CMDrebase(parser, args): | |
5009 """Rebases current branch on top of svn repo.""" | |
5010 # Provide a wrapper for git svn rebase to help avoid accidental | |
5011 # git svn dcommit. | |
5012 # It's the only command that doesn't use parser at all since we just defer | |
5013 # execution to git-svn. | |
5014 | |
5015 return RunGitWithCode(['svn', 'rebase'] + args)[1] | |
5016 | |
5017 | |
5018 def GetTreeStatus(url=None): | 4776 def GetTreeStatus(url=None): |
5019 """Fetches the tree status and returns either 'open', 'closed', | 4777 """Fetches the tree status and returns either 'open', 'closed', |
5020 'unknown' or 'unset'.""" | 4778 'unknown' or 'unset'.""" |
5021 url = url or settings.GetTreeStatusUrl(error_ok=True) | 4779 url = url or settings.GetTreeStatusUrl(error_ok=True) |
5022 if url: | 4780 if url: |
5023 status = urllib2.urlopen(url).read().lower() | 4781 status = urllib2.urlopen(url).read().lower() |
5024 if status.find('closed') != -1 or status == '0': | 4782 if status.find('closed') != -1 or status == '0': |
5025 return 'closed' | 4783 return 'closed' |
5026 elif status.find('open') != -1 or status == '1': | 4784 elif status.find('open') != -1 or status == '1': |
5027 return 'open' | 4785 return 'open' |
(...skipping 624 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
5652 if __name__ == '__main__': | 5410 if __name__ == '__main__': |
5653 # These affect sys.stdout so do it outside of main() to simplify mocks in | 5411 # These affect sys.stdout so do it outside of main() to simplify mocks in |
5654 # unit testing. | 5412 # unit testing. |
5655 fix_encoding.fix_encoding() | 5413 fix_encoding.fix_encoding() |
5656 setup_color.init() | 5414 setup_color.init() |
5657 try: | 5415 try: |
5658 sys.exit(main(sys.argv[1:])) | 5416 sys.exit(main(sys.argv[1:])) |
5659 except KeyboardInterrupt: | 5417 except KeyboardInterrupt: |
5660 sys.stderr.write('interrupted\n') | 5418 sys.stderr.write('interrupted\n') |
5661 sys.exit(1) | 5419 sys.exit(1) |
OLD | NEW |