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