Chromium Code Reviews| 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.""" | 8 """A git-command for integrating reviews on Rietveld.""" |
| 9 | 9 |
| 10 import json | 10 import json |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 57 except subprocess2.CalledProcessError, e: | 57 except subprocess2.CalledProcessError, e: |
| 58 if not error_ok: | 58 if not error_ok: |
| 59 DieWithError( | 59 DieWithError( |
| 60 'Command "%s" failed.\n%s' % ( | 60 'Command "%s" failed.\n%s' % ( |
| 61 ' '.join(args), error_message or e.stdout or '')) | 61 ' '.join(args), error_message or e.stdout or '')) |
| 62 return e.stdout | 62 return e.stdout |
| 63 | 63 |
| 64 | 64 |
| 65 def RunGit(args, **kwargs): | 65 def RunGit(args, **kwargs): |
| 66 """Returns stdout.""" | 66 """Returns stdout.""" |
| 67 return RunCommand(['git'] + args, **kwargs) | 67 return RunCommand(['git'] + args, **kwargs).strip() |
|
M-A Ruel
2012/11/01 13:46:41
I'd prefer this to be done as a separate CL, becau
Isaac (away)
2012/11/01 21:33:40
Done.
| |
| 68 | 68 |
| 69 | 69 |
| 70 def RunGitWithCode(args): | 70 def RunGitWithCode(args): |
| 71 """Returns return code and stdout.""" | 71 """Returns return code and stdout.""" |
| 72 try: | 72 try: |
| 73 out, code = subprocess2.communicate(['git'] + args, stdout=subprocess2.PIPE) | 73 out, code = subprocess2.communicate(['git'] + args, stdout=subprocess2.PIPE) |
| 74 return code, out[0] | 74 return code, out[0].strip() |
| 75 except ValueError: | 75 except ValueError: |
| 76 # When the subprocess fails, it returns None. That triggers a ValueError | 76 # When the subprocess fails, it returns None. That triggers a ValueError |
| 77 # when trying to unpack the return value into (out, code). | 77 # when trying to unpack the return value into (out, code). |
| 78 return 1, '' | 78 return 1, '' |
| 79 | 79 |
| 80 | 80 |
| 81 def usage(more): | 81 def usage(more): |
| 82 def hook(fn): | 82 def hook(fn): |
| 83 fn.usage_more = more | 83 fn.usage_more = more |
| 84 return fn | 84 return fn |
| (...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 156 dirty = RunGit(['diff-index', '--name-status', 'HEAD']) | 156 dirty = RunGit(['diff-index', '--name-status', 'HEAD']) |
| 157 if dirty: | 157 if dirty: |
| 158 print 'Cannot %s with a dirty tree. You must commit locally first.' % cmd | 158 print 'Cannot %s with a dirty tree. You must commit locally first.' % cmd |
| 159 print 'Uncommitted files: (git diff-index --name-status HEAD)' | 159 print 'Uncommitted files: (git diff-index --name-status HEAD)' |
| 160 print dirty[:4096] | 160 print dirty[:4096] |
| 161 if len(dirty) > 4096: | 161 if len(dirty) > 4096: |
| 162 print '... (run "git diff-index --name-status HEAD" to see full output).' | 162 print '... (run "git diff-index --name-status HEAD" to see full output).' |
| 163 return True | 163 return True |
| 164 return False | 164 return False |
| 165 | 165 |
| 166 | |
| 166 def MatchSvnGlob(url, base_url, glob_spec, allow_wildcards): | 167 def MatchSvnGlob(url, base_url, glob_spec, allow_wildcards): |
| 167 """Return the corresponding git ref if |base_url| together with |glob_spec| | 168 """Return the corresponding git ref if |base_url| together with |glob_spec| |
| 168 matches the full |url|. | 169 matches the full |url|. |
| 169 | 170 |
| 170 If |allow_wildcards| is true, |glob_spec| can contain wildcards (see below). | 171 If |allow_wildcards| is true, |glob_spec| can contain wildcards (see below). |
| 171 """ | 172 """ |
| 172 fetch_suburl, as_ref = glob_spec.split(':') | 173 fetch_suburl, as_ref = glob_spec.split(':') |
| 173 if allow_wildcards: | 174 if allow_wildcards: |
| 174 glob_match = re.match('(.+/)?(\*|{[^/]*})(/.+)?', fetch_suburl) | 175 glob_match = re.match('(.+/)?(\*|{[^/]*})(/.+)?', fetch_suburl) |
| 175 if glob_match: | 176 if glob_match: |
| (...skipping 243 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 419 if not self.branch: | 420 if not self.branch: |
| 420 self.branchref = RunGit(['symbolic-ref', 'HEAD']).strip() | 421 self.branchref = RunGit(['symbolic-ref', 'HEAD']).strip() |
| 421 self.branch = ShortBranchName(self.branchref) | 422 self.branch = ShortBranchName(self.branchref) |
| 422 return self.branch | 423 return self.branch |
| 423 | 424 |
| 424 def GetBranchRef(self): | 425 def GetBranchRef(self): |
| 425 """Returns the full branch name, e.g. 'refs/heads/master'.""" | 426 """Returns the full branch name, e.g. 'refs/heads/master'.""" |
| 426 self.GetBranch() # Poke the lazy loader. | 427 self.GetBranch() # Poke the lazy loader. |
| 427 return self.branchref | 428 return self.branchref |
| 428 | 429 |
| 429 def FetchUpstreamTuple(self): | 430 def FetchUpstreamTuple(self, branch=None): |
|
M-A Ruel
2012/11/01 13:46:41
Let's not make it optional, it'd be easier to foll
Isaac (away)
2012/11/01 21:33:40
Done.
| |
| 430 """Returns a tuple containg remote and remote ref, | 431 """Returns a tuple containg remote and remote ref, |
| 431 e.g. 'origin', 'refs/heads/master' | 432 e.g. 'origin', 'refs/heads/master' |
| 432 """ | 433 """ |
| 433 remote = '.' | 434 remote = '.' |
| 434 branch = self.GetBranch() | 435 branch = branch or self.GetBranch() |
|
M-A Ruel
2012/11/01 13:46:41
Have the caller call self.GetBranch()
Isaac (away)
2012/11/01 21:33:40
Done.
| |
| 435 upstream_branch = RunGit(['config', 'branch.%s.merge' % branch], | 436 upstream_branch = RunGit(['config', 'branch.%s.merge' % branch], |
| 436 error_ok=True).strip() | 437 error_ok=True).strip() |
| 437 if upstream_branch: | 438 if upstream_branch: |
| 438 remote = RunGit(['config', 'branch.%s.remote' % branch]).strip() | 439 remote = RunGit(['config', 'branch.%s.remote' % branch]).strip() |
| 439 else: | 440 else: |
| 440 upstream_branch = RunGit(['config', 'rietveld.upstream-branch'], | 441 upstream_branch = RunGit(['config', 'rietveld.upstream-branch'], |
| 441 error_ok=True).strip() | 442 error_ok=True).strip() |
| 442 if upstream_branch: | 443 if upstream_branch: |
| 443 remote = RunGit(['config', 'rietveld.upstream-remote']).strip() | 444 remote = RunGit(['config', 'rietveld.upstream-remote']).strip() |
| 444 else: | 445 else: |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 467 return remote, upstream_branch | 468 return remote, upstream_branch |
| 468 | 469 |
| 469 def GetUpstreamBranch(self): | 470 def GetUpstreamBranch(self): |
| 470 if self.upstream_branch is None: | 471 if self.upstream_branch is None: |
| 471 remote, upstream_branch = self.FetchUpstreamTuple() | 472 remote, upstream_branch = self.FetchUpstreamTuple() |
| 472 if remote is not '.': | 473 if remote is not '.': |
| 473 upstream_branch = upstream_branch.replace('heads', 'remotes/' + remote) | 474 upstream_branch = upstream_branch.replace('heads', 'remotes/' + remote) |
| 474 self.upstream_branch = upstream_branch | 475 self.upstream_branch = upstream_branch |
| 475 return self.upstream_branch | 476 return self.upstream_branch |
| 476 | 477 |
| 477 def GetRemote(self): | 478 def GetRemoteBranch(self): |
|
M-A Ruel
2012/11/01 13:46:41
I think it'd be better to split this code in parts
Isaac (away)
2012/11/01 21:33:40
I'd like to punt on that. The goal of this patch i
| |
| 478 if not self._remote: | 479 if not self._remote: |
| 479 self._remote = self.FetchUpstreamTuple()[0] | 480 remote, branch = None, self.GetBranch() |
| 480 if self._remote == '.': | 481 seen_branches = set() |
| 481 | 482 while branch not in seen_branches: |
| 483 seen_branches.add(branch) | |
| 484 remote, branch = self.FetchUpstreamTuple(branch) | |
| 485 branch = ShortBranchName(branch) | |
| 486 if remote != '.': | |
| 487 break | |
| 488 else: | |
| 482 remotes = RunGit(['remote'], error_ok=True).split() | 489 remotes = RunGit(['remote'], error_ok=True).split() |
| 483 if len(remotes) == 1: | 490 if len(remotes) == 1: |
| 484 self._remote, = remotes | 491 remote, = remotes |
| 485 elif 'origin' in remotes: | 492 elif 'origin' in remotes: |
| 486 self._remote = 'origin' | 493 remote = 'origin' |
| 487 logging.warning('Could not determine which remote this change is ' | 494 logging.warning('Could not determine which remote this change is ' |
| 488 'associated with, so defaulting to "%s". This may ' | 495 'associated with, so defaulting to "%s". This may ' |
| 489 'not be what you want. You may prevent this message ' | 496 'not be what you want. You may prevent this message ' |
| 490 'by running "git svn info" as documented here: %s', | 497 'by running "git svn info" as documented here: %s', |
| 491 self._remote, | 498 self._remote, |
| 492 GIT_INSTRUCTIONS_URL) | 499 GIT_INSTRUCTIONS_URL) |
| 493 else: | 500 else: |
| 494 logging.warn('Could not determine which remote this change is ' | 501 logging.warn('Could not determine which remote this change is ' |
| 495 'associated with. You may prevent this message by ' | 502 'associated with. You may prevent this message by ' |
| 496 'running "git svn info" as documented here: %s', | 503 'running "git svn info" as documented here: %s', |
| 497 GIT_INSTRUCTIONS_URL) | 504 GIT_INSTRUCTIONS_URL) |
| 505 branch = 'HEAD' | |
| 506 self._remote = (remote, branch) | |
| 498 return self._remote | 507 return self._remote |
| 499 | 508 |
| 509 def GitSanityChecks(self, upstream_git_obj): | |
| 510 """Checks git repo status and ensures diff is from local commits.""" | |
| 511 | |
| 512 # Verify the commit we're diffing against is in our current branch. | |
| 513 upstream_sha = RunGit(['rev-parse', '--verify', upstream_git_obj]) | |
| 514 common_ancestor = RunGit(['merge-base', upstream_sha, 'HEAD']) | |
| 515 if upstream_sha != common_ancestor: | |
| 516 print >> sys.stderr, ( | |
| 517 'ERROR: %s is not in the current branch. You may need to rebase ' | |
| 518 'your tracking branch' % upstream_sha) | |
| 519 return False | |
| 520 | |
| 521 # List the commits inside the diff, and verify they are all local. | |
| 522 commits_in_diff = RunGit( | |
| 523 ['rev-list', '^%s' % upstream_sha, 'HEAD']).splitlines() | |
| 524 code, remote_branch = RunGitWithCode(['config', 'depottools.remotebranch']) | |
| 525 if code != 0: | |
| 526 remote_branch = 'refs/remotes/' + '/'.join(self.GetRemoteBranch()) | |
| 527 | |
| 528 commits_in_remote = RunGit( | |
| 529 ['rev-list', '^%s' % upstream_sha, remote_branch]).splitlines() | |
| 530 | |
| 531 common_commits = set(commits_in_diff) & set(commits_in_remote) | |
| 532 if common_commits: | |
| 533 print >> sys.stderr, ( | |
| 534 'ERROR: Your diff contains %d commits already in %s.\n' | |
| 535 'Run "git log --oneline %s..HEAD" to get a list of commits in ' | |
| 536 'the diff. If you believe this is a mistake, you can override' | |
| 537 ' the branch used for this check with "git config ' | |
| 538 'depot_tools.remotebranch <remote>".' % ( | |
| 539 len(common_commits), remote_branch, upstream_git_obj)) | |
| 540 return False | |
| 541 return True | |
| 542 | |
| 500 def GetGitBaseUrlFromConfig(self): | 543 def GetGitBaseUrlFromConfig(self): |
| 501 """Return the configured base URL from branch.<branchname>.baseurl. | 544 """Return the configured base URL from branch.<branchname>.baseurl. |
| 502 | 545 |
| 503 Returns None if it is not set. | 546 Returns None if it is not set. |
| 504 """ | 547 """ |
| 505 return RunGit(['config', 'branch.%s.base-url' % self.GetBranch()], | 548 return RunGit(['config', 'branch.%s.base-url' % self.GetBranch()], |
| 506 error_ok=True).strip() | 549 error_ok=True).strip() |
| 507 | 550 |
| 508 def GetRemoteUrl(self): | 551 def GetRemoteUrl(self): |
| 509 """Return the configured remote URL, e.g. 'git://example.org/foo.git/'. | 552 """Return the configured remote URL, e.g. 'git://example.org/foo.git/'. |
| 510 | 553 |
| 511 Returns None if there is no remote. | 554 Returns None if there is no remote. |
| 512 """ | 555 """ |
| 513 remote = self.GetRemote() | 556 remote, _ = self.GetRemoteBranch() |
| 514 return RunGit(['config', 'remote.%s.url' % remote], error_ok=True).strip() | 557 return RunGit(['config', 'remote.%s.url' % remote], error_ok=True).strip() |
| 515 | 558 |
| 516 def GetIssue(self): | 559 def GetIssue(self): |
| 517 """Returns the issue number as a int or None if not set.""" | 560 """Returns the issue number as a int or None if not set.""" |
| 518 if not self.has_issue: | 561 if not self.has_issue: |
| 519 issue = RunGit(['config', self._IssueSetting()], error_ok=True).strip() | 562 issue = RunGit(['config', self._IssueSetting()], error_ok=True).strip() |
| 520 if issue: | 563 if issue: |
| 521 self.issue = int(issue) | 564 self.issue = int(issue) |
| 522 else: | 565 else: |
| 523 self.issue = None | 566 self.issue = None |
| (...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 600 if issue: | 643 if issue: |
| 601 RunGit(['config', self._IssueSetting(), str(issue)]) | 644 RunGit(['config', self._IssueSetting(), str(issue)]) |
| 602 if self.rietveld_server: | 645 if self.rietveld_server: |
| 603 RunGit(['config', self._RietveldServer(), self.rietveld_server]) | 646 RunGit(['config', self._RietveldServer(), self.rietveld_server]) |
| 604 else: | 647 else: |
| 605 RunGit(['config', '--unset', self._IssueSetting()]) | 648 RunGit(['config', '--unset', self._IssueSetting()]) |
| 606 self.SetPatchset(0) | 649 self.SetPatchset(0) |
| 607 self.has_issue = False | 650 self.has_issue = False |
| 608 | 651 |
| 609 def GetChange(self, upstream_branch, author): | 652 def GetChange(self, upstream_branch, author): |
| 653 if not self.GitSanityChecks(upstream_branch): | |
| 654 DieWithError('\nGit sanity check failure') | |
| 655 | |
| 610 root = RunCommand(['git', 'rev-parse', '--show-cdup']).strip() or '.' | 656 root = RunCommand(['git', 'rev-parse', '--show-cdup']).strip() or '.' |
| 611 absroot = os.path.abspath(root) | 657 absroot = os.path.abspath(root) |
| 612 | 658 |
| 613 # We use the sha1 of HEAD as a name of this change. | 659 # We use the sha1 of HEAD as a name of this change. |
| 614 name = RunCommand(['git', 'rev-parse', 'HEAD']).strip() | 660 name = RunCommand(['git', 'rev-parse', 'HEAD']).strip() |
| 615 # Need to pass a relative path for msysgit. | 661 # Need to pass a relative path for msysgit. |
| 616 try: | 662 try: |
| 617 files = scm.GIT.CaptureStatus([root], '.', upstream_branch) | 663 files = scm.GIT.CaptureStatus([root], '.', upstream_branch) |
| 618 except subprocess2.CalledProcessError: | 664 except subprocess2.CalledProcessError: |
| 619 DieWithError( | 665 DieWithError( |
| (...skipping 391 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1011 (options, args) = parser.parse_args(args) | 1057 (options, args) = parser.parse_args(args) |
| 1012 | 1058 |
| 1013 if not options.force and is_dirty_git_tree('presubmit'): | 1059 if not options.force and is_dirty_git_tree('presubmit'): |
| 1014 print 'use --force to check even if tree is dirty.' | 1060 print 'use --force to check even if tree is dirty.' |
| 1015 return 1 | 1061 return 1 |
| 1016 | 1062 |
| 1017 cl = Changelist() | 1063 cl = Changelist() |
| 1018 if args: | 1064 if args: |
| 1019 base_branch = args[0] | 1065 base_branch = args[0] |
| 1020 else: | 1066 else: |
| 1021 # Default to diffing against the "upstream" branch. | 1067 # Default to diffing against the common ancestor of the upstream branch. |
| 1022 base_branch = cl.GetUpstreamBranch() | 1068 base_branch = RunGit(['merge-base', cl.GetUpstreamBranch(), 'HEAD']).strip() |
| 1023 | 1069 |
| 1024 cl.RunHook(committing=not options.upload, upstream_branch=base_branch, | 1070 cl.RunHook(committing=not options.upload, upstream_branch=base_branch, |
| 1025 may_prompt=False, verbose=options.verbose, | 1071 may_prompt=False, verbose=options.verbose, |
| 1026 author=None) | 1072 author=None) |
| 1027 return 0 | 1073 return 0 |
| 1028 | 1074 |
| 1029 | 1075 |
| 1030 def AddChangeIdToCommitMessage(options, args): | 1076 def AddChangeIdToCommitMessage(options, args): |
| 1031 """Re-commits using the current message, assumes the commit hook is in | 1077 """Re-commits using the current message, assumes the commit hook is in |
| 1032 place. | 1078 place. |
| (...skipping 176 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1209 'See http://goo.gl/JGg0Z for details.\n') | 1255 'See http://goo.gl/JGg0Z for details.\n') |
| 1210 | 1256 |
| 1211 if is_dirty_git_tree('upload'): | 1257 if is_dirty_git_tree('upload'): |
| 1212 return 1 | 1258 return 1 |
| 1213 | 1259 |
| 1214 cl = Changelist() | 1260 cl = Changelist() |
| 1215 if args: | 1261 if args: |
| 1216 # TODO(ukai): is it ok for gerrit case? | 1262 # TODO(ukai): is it ok for gerrit case? |
| 1217 base_branch = args[0] | 1263 base_branch = args[0] |
| 1218 else: | 1264 else: |
| 1219 # Default to diffing against the "upstream" branch. | 1265 # Default to diffing against common ancestor of upstream branch |
| 1220 base_branch = cl.GetUpstreamBranch() | 1266 base_branch = RunGit(['merge-base', cl.GetUpstreamBranch(), 'HEAD']).strip() |
| 1221 args = [base_branch + "..."] | 1267 args = [base_branch] |
| 1222 | 1268 |
| 1223 if not options.bypass_hooks: | 1269 if not options.bypass_hooks: |
| 1224 hook_results = cl.RunHook(committing=False, upstream_branch=base_branch, | 1270 hook_results = cl.RunHook(committing=False, upstream_branch=base_branch, |
| 1225 may_prompt=not options.force, | 1271 may_prompt=not options.force, |
| 1226 verbose=options.verbose, | 1272 verbose=options.verbose, |
| 1227 author=None) | 1273 author=None) |
| 1228 if not hook_results.should_continue(): | 1274 if not hook_results.should_continue(): |
| 1229 return 1 | 1275 return 1 |
| 1230 if not options.reviewers and hook_results.reviewers: | 1276 if not options.reviewers and hook_results.reviewers: |
| 1231 options.reviewers = hook_results.reviewers | 1277 options.reviewers = hook_results.reviewers |
| (...skipping 417 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1649 cl = Changelist() | 1695 cl = Changelist() |
| 1650 if not cl.GetIssue(): | 1696 if not cl.GetIssue(): |
| 1651 parser.error('Need to upload first') | 1697 parser.error('Need to upload first') |
| 1652 | 1698 |
| 1653 if not options.name: | 1699 if not options.name: |
| 1654 options.name = cl.GetBranch() | 1700 options.name = cl.GetBranch() |
| 1655 | 1701 |
| 1656 # Process --bot and --testfilter. | 1702 # Process --bot and --testfilter. |
| 1657 if not options.bot: | 1703 if not options.bot: |
| 1658 # Get try slaves from PRESUBMIT.py files if not specified. | 1704 # Get try slaves from PRESUBMIT.py files if not specified. |
| 1659 change = cl.GetChange(cl.GetUpstreamBranch(), None) | 1705 change = cl.GetChange( |
| 1706 RunGit(['merge-base', cl.GetUpstreamBranch(), 'HEAD']).strip(), | |
| 1707 None) | |
| 1660 options.bot = presubmit_support.DoGetTrySlaves( | 1708 options.bot = presubmit_support.DoGetTrySlaves( |
| 1661 change, | 1709 change, |
| 1662 change.LocalPaths(), | 1710 change.LocalPaths(), |
| 1663 settings.GetRoot(), | 1711 settings.GetRoot(), |
| 1664 None, | 1712 None, |
| 1665 None, | 1713 None, |
| 1666 options.verbose, | 1714 options.verbose, |
| 1667 sys.stdout) | 1715 sys.stdout) |
| 1668 if not options.bot: | 1716 if not options.bot: |
| 1669 parser.error('No default try builder to try, use --bot') | 1717 parser.error('No default try builder to try, use --bot') |
| (...skipping 139 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1809 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) | 1857 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) |
| 1810 | 1858 |
| 1811 # Not a known command. Default to help. | 1859 # Not a known command. Default to help. |
| 1812 GenUsage(parser, 'help') | 1860 GenUsage(parser, 'help') |
| 1813 return CMDhelp(parser, argv) | 1861 return CMDhelp(parser, argv) |
| 1814 | 1862 |
| 1815 | 1863 |
| 1816 if __name__ == '__main__': | 1864 if __name__ == '__main__': |
| 1817 fix_encoding.fix_encoding() | 1865 fix_encoding.fix_encoding() |
| 1818 sys.exit(main(sys.argv[1:])) | 1866 sys.exit(main(sys.argv[1:])) |
| OLD | NEW |