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

Side by Side Diff: git_cl.py

Issue 11262057: git_cl sanity checks (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: Created 8 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | tests/git_cl_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/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 145 matching lines...) Expand 10 before | Expand all | Expand 10 after
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
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 @staticmethod
431 def FetchUpstreamTuple(branch):
430 """Returns a tuple containg remote and remote ref, 432 """Returns a tuple containg remote and remote ref,
431 e.g. 'origin', 'refs/heads/master' 433 e.g. 'origin', 'refs/heads/master'
432 """ 434 """
433 remote = '.' 435 remote = '.'
434 branch = self.GetBranch()
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 16 matching lines...) Expand all
461 DieWithError("""Unable to determine default branch to diff against. 462 DieWithError("""Unable to determine default branch to diff against.
462 Either pass complete "git diff"-style arguments, like 463 Either pass complete "git diff"-style arguments, like
463 git cl upload origin/master 464 git cl upload origin/master
464 or verify this branch is set up to track another (via the --track argument to 465 or verify this branch is set up to track another (via the --track argument to
465 "git checkout -b ...").""") 466 "git checkout -b ...").""")
466 467
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(self.GetBranch())
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):
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 != '.' or branch.startswith('refs/remotes'):
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 if branch.startswith('refs/remotes'):
507 self._remote = (remote, branch)
508 else:
509 self._remote = (remote, 'refs/remotes/%s/%s' % (remote, branch))
498 return self._remote 510 return self._remote
499 511
512 def GitSanityChecks(self, upstream_git_obj):
513 """Checks git repo status and ensures diff is from local commits."""
514
515 # Verify the commit we're diffing against is in our current branch.
516 upstream_sha = RunGit(['rev-parse', '--verify', upstream_git_obj]).strip()
517 common_ancestor = RunGit(['merge-base', upstream_sha, 'HEAD']).strip()
518 if upstream_sha != common_ancestor:
519 print >> sys.stderr, (
520 'ERROR: %s is not in the current branch. You may need to rebase '
521 'your tracking branch' % upstream_sha)
522 return False
523
524 # List the commits inside the diff, and verify they are all local.
525 commits_in_diff = RunGit(
526 ['rev-list', '^%s' % upstream_sha, 'HEAD']).splitlines()
527 code, remote_branch = RunGitWithCode(['config', 'gitcl.remotebranch'])
528 remote_branch = remote_branch.strip()
529 if code != 0:
530 _, remote_branch = self.GetRemoteBranch()
531
532 commits_in_remote = RunGit(
533 ['rev-list', '^%s' % upstream_sha, remote_branch]).splitlines()
534
535 common_commits = set(commits_in_diff) & set(commits_in_remote)
536 if common_commits:
537 print >> sys.stderr, (
538 'ERROR: Your diff contains %d commits already in %s.\n'
539 'Run "git log --oneline %s..HEAD" to get a list of commits in '
540 'the diff. If you are using a custom git flow, you can override'
541 ' the reference used for this check with "git config '
542 'gitcl.remotebranch <git-ref>".' % (
543 len(common_commits), remote_branch, upstream_git_obj))
544 return False
545 return True
546
500 def GetGitBaseUrlFromConfig(self): 547 def GetGitBaseUrlFromConfig(self):
501 """Return the configured base URL from branch.<branchname>.baseurl. 548 """Return the configured base URL from branch.<branchname>.baseurl.
502 549
503 Returns None if it is not set. 550 Returns None if it is not set.
504 """ 551 """
505 return RunGit(['config', 'branch.%s.base-url' % self.GetBranch()], 552 return RunGit(['config', 'branch.%s.base-url' % self.GetBranch()],
506 error_ok=True).strip() 553 error_ok=True).strip()
507 554
508 def GetRemoteUrl(self): 555 def GetRemoteUrl(self):
509 """Return the configured remote URL, e.g. 'git://example.org/foo.git/'. 556 """Return the configured remote URL, e.g. 'git://example.org/foo.git/'.
510 557
511 Returns None if there is no remote. 558 Returns None if there is no remote.
512 """ 559 """
513 remote = self.GetRemote() 560 remote, _ = self.GetRemoteBranch()
514 return RunGit(['config', 'remote.%s.url' % remote], error_ok=True).strip() 561 return RunGit(['config', 'remote.%s.url' % remote], error_ok=True).strip()
515 562
516 def GetIssue(self): 563 def GetIssue(self):
517 """Returns the issue number as a int or None if not set.""" 564 """Returns the issue number as a int or None if not set."""
518 if not self.has_issue: 565 if not self.has_issue:
519 issue = RunGit(['config', self._IssueSetting()], error_ok=True).strip() 566 issue = RunGit(['config', self._IssueSetting()], error_ok=True).strip()
520 if issue: 567 if issue:
521 self.issue = int(issue) 568 self.issue = int(issue)
522 else: 569 else:
523 self.issue = None 570 self.issue = None
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after
600 if issue: 647 if issue:
601 RunGit(['config', self._IssueSetting(), str(issue)]) 648 RunGit(['config', self._IssueSetting(), str(issue)])
602 if self.rietveld_server: 649 if self.rietveld_server:
603 RunGit(['config', self._RietveldServer(), self.rietveld_server]) 650 RunGit(['config', self._RietveldServer(), self.rietveld_server])
604 else: 651 else:
605 RunGit(['config', '--unset', self._IssueSetting()]) 652 RunGit(['config', '--unset', self._IssueSetting()])
606 self.SetPatchset(0) 653 self.SetPatchset(0)
607 self.has_issue = False 654 self.has_issue = False
608 655
609 def GetChange(self, upstream_branch, author): 656 def GetChange(self, upstream_branch, author):
657 if not self.GitSanityChecks(upstream_branch):
658 DieWithError('\nGit sanity check failure')
659
610 root = RunCommand(['git', 'rev-parse', '--show-cdup']).strip() or '.' 660 root = RunCommand(['git', 'rev-parse', '--show-cdup']).strip() or '.'
611 absroot = os.path.abspath(root) 661 absroot = os.path.abspath(root)
612 662
613 # We use the sha1 of HEAD as a name of this change. 663 # We use the sha1 of HEAD as a name of this change.
614 name = RunCommand(['git', 'rev-parse', 'HEAD']).strip() 664 name = RunCommand(['git', 'rev-parse', 'HEAD']).strip()
615 # Need to pass a relative path for msysgit. 665 # Need to pass a relative path for msysgit.
616 try: 666 try:
617 files = scm.GIT.CaptureStatus([root], '.', upstream_branch) 667 files = scm.GIT.CaptureStatus([root], '.', upstream_branch)
618 except subprocess2.CalledProcessError: 668 except subprocess2.CalledProcessError:
619 DieWithError( 669 DieWithError(
(...skipping 391 matching lines...) Expand 10 before | Expand all | Expand 10 after
1011 (options, args) = parser.parse_args(args) 1061 (options, args) = parser.parse_args(args)
1012 1062
1013 if not options.force and is_dirty_git_tree('presubmit'): 1063 if not options.force and is_dirty_git_tree('presubmit'):
1014 print 'use --force to check even if tree is dirty.' 1064 print 'use --force to check even if tree is dirty.'
1015 return 1 1065 return 1
1016 1066
1017 cl = Changelist() 1067 cl = Changelist()
1018 if args: 1068 if args:
1019 base_branch = args[0] 1069 base_branch = args[0]
1020 else: 1070 else:
1021 # Default to diffing against the "upstream" branch. 1071 # Default to diffing against the common ancestor of the upstream branch.
1022 base_branch = cl.GetUpstreamBranch() 1072 base_branch = RunGit(['merge-base', cl.GetUpstreamBranch(), 'HEAD']).strip()
1023 1073
1024 cl.RunHook(committing=not options.upload, upstream_branch=base_branch, 1074 cl.RunHook(committing=not options.upload, upstream_branch=base_branch,
1025 may_prompt=False, verbose=options.verbose, 1075 may_prompt=False, verbose=options.verbose,
1026 author=None) 1076 author=None)
1027 return 0 1077 return 0
1028 1078
1029 1079
1030 def AddChangeIdToCommitMessage(options, args): 1080 def AddChangeIdToCommitMessage(options, args):
1031 """Re-commits using the current message, assumes the commit hook is in 1081 """Re-commits using the current message, assumes the commit hook is in
1032 place. 1082 place.
(...skipping 176 matching lines...) Expand 10 before | Expand all | Expand 10 after
1209 'See http://goo.gl/JGg0Z for details.\n') 1259 'See http://goo.gl/JGg0Z for details.\n')
1210 1260
1211 if is_dirty_git_tree('upload'): 1261 if is_dirty_git_tree('upload'):
1212 return 1 1262 return 1
1213 1263
1214 cl = Changelist() 1264 cl = Changelist()
1215 if args: 1265 if args:
1216 # TODO(ukai): is it ok for gerrit case? 1266 # TODO(ukai): is it ok for gerrit case?
1217 base_branch = args[0] 1267 base_branch = args[0]
1218 else: 1268 else:
1219 # Default to diffing against the "upstream" branch. 1269 # Default to diffing against common ancestor of upstream branch
1220 base_branch = cl.GetUpstreamBranch() 1270 base_branch = RunGit(['merge-base', cl.GetUpstreamBranch(), 'HEAD']).strip()
1221 args = [base_branch + "..."] 1271 args = [base_branch]
1222 1272
1223 if not options.bypass_hooks: 1273 if not options.bypass_hooks:
1224 hook_results = cl.RunHook(committing=False, upstream_branch=base_branch, 1274 hook_results = cl.RunHook(committing=False, upstream_branch=base_branch,
1225 may_prompt=not options.force, 1275 may_prompt=not options.force,
1226 verbose=options.verbose, 1276 verbose=options.verbose,
1227 author=None) 1277 author=None)
1228 if not hook_results.should_continue(): 1278 if not hook_results.should_continue():
1229 return 1 1279 return 1
1230 if not options.reviewers and hook_results.reviewers: 1280 if not options.reviewers and hook_results.reviewers:
1231 options.reviewers = hook_results.reviewers 1281 options.reviewers = hook_results.reviewers
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after
1303 base_svn_head += '^1' 1353 base_svn_head += '^1'
1304 1354
1305 extra_commits = RunGit(['rev-list', '^' + svn_head, base_svn_head]) 1355 extra_commits = RunGit(['rev-list', '^' + svn_head, base_svn_head])
1306 if extra_commits: 1356 if extra_commits:
1307 print ('This branch has %d additional commits not upstreamed yet.' 1357 print ('This branch has %d additional commits not upstreamed yet.'
1308 % len(extra_commits.splitlines())) 1358 % len(extra_commits.splitlines()))
1309 print ('Upstream "%s" or rebase this branch on top of the upstream trunk ' 1359 print ('Upstream "%s" or rebase this branch on top of the upstream trunk '
1310 'before attempting to %s.' % (base_branch, cmd)) 1360 'before attempting to %s.' % (base_branch, cmd))
1311 return 1 1361 return 1
1312 1362
1363 base_branch = RunGit(['merge-base', base_branch, 'HEAD']).strip()
1313 if not options.bypass_hooks: 1364 if not options.bypass_hooks:
1314 author = None 1365 author = None
1315 if options.contributor: 1366 if options.contributor:
1316 author = re.search(r'\<(.*)\>', options.contributor).group(1) 1367 author = re.search(r'\<(.*)\>', options.contributor).group(1)
1317 hook_results = cl.RunHook( 1368 hook_results = cl.RunHook(
1318 committing=True, 1369 committing=True,
1319 upstream_branch=base_branch, 1370 upstream_branch=base_branch,
1320 may_prompt=not options.force, 1371 may_prompt=not options.force,
1321 verbose=options.verbose, 1372 verbose=options.verbose,
1322 author=author) 1373 author=author)
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after
1396 RunGit(['commit', '--author', options.contributor, '-m', description]) 1447 RunGit(['commit', '--author', options.contributor, '-m', description])
1397 else: 1448 else:
1398 RunGit(['commit', '-m', description]) 1449 RunGit(['commit', '-m', description])
1399 if base_has_submodules: 1450 if base_has_submodules:
1400 cherry_pick_commit = RunGit(['rev-list', 'HEAD^!']).rstrip() 1451 cherry_pick_commit = RunGit(['rev-list', 'HEAD^!']).rstrip()
1401 RunGit(['branch', CHERRY_PICK_BRANCH, svn_head]) 1452 RunGit(['branch', CHERRY_PICK_BRANCH, svn_head])
1402 RunGit(['checkout', CHERRY_PICK_BRANCH]) 1453 RunGit(['checkout', CHERRY_PICK_BRANCH])
1403 RunGit(['cherry-pick', cherry_pick_commit]) 1454 RunGit(['cherry-pick', cherry_pick_commit])
1404 if cmd == 'push': 1455 if cmd == 'push':
1405 # push the merge branch. 1456 # push the merge branch.
1406 remote, branch = cl.FetchUpstreamTuple() 1457 remote, branch = cl.FetchUpstreamTuple(cl.GetBranch())
1407 retcode, output = RunGitWithCode( 1458 retcode, output = RunGitWithCode(
1408 ['push', '--porcelain', remote, 'HEAD:%s' % branch]) 1459 ['push', '--porcelain', remote, 'HEAD:%s' % branch])
1409 logging.debug(output) 1460 logging.debug(output)
1410 else: 1461 else:
1411 # dcommit the merge branch. 1462 # dcommit the merge branch.
1412 retcode, output = RunGitWithCode(['svn', 'dcommit', 1463 retcode, output = RunGitWithCode(['svn', 'dcommit',
1413 '-C%s' % options.similarity, 1464 '-C%s' % options.similarity,
1414 '--no-rebase', '--rmdir']) 1465 '--no-rebase', '--rmdir'])
1415 finally: 1466 finally:
1416 # And then swap back to the original branch and clean up. 1467 # And then swap back to the original branch and clean up.
(...skipping 232 matching lines...) Expand 10 before | Expand all | Expand 10 after
1649 cl = Changelist() 1700 cl = Changelist()
1650 if not cl.GetIssue(): 1701 if not cl.GetIssue():
1651 parser.error('Need to upload first') 1702 parser.error('Need to upload first')
1652 1703
1653 if not options.name: 1704 if not options.name:
1654 options.name = cl.GetBranch() 1705 options.name = cl.GetBranch()
1655 1706
1656 # Process --bot and --testfilter. 1707 # Process --bot and --testfilter.
1657 if not options.bot: 1708 if not options.bot:
1658 # Get try slaves from PRESUBMIT.py files if not specified. 1709 # Get try slaves from PRESUBMIT.py files if not specified.
1659 change = cl.GetChange(cl.GetUpstreamBranch(), None) 1710 change = cl.GetChange(
1711 RunGit(['merge-base', cl.GetUpstreamBranch(), 'HEAD']).strip(),
1712 None)
1660 options.bot = presubmit_support.DoGetTrySlaves( 1713 options.bot = presubmit_support.DoGetTrySlaves(
1661 change, 1714 change,
1662 change.LocalPaths(), 1715 change.LocalPaths(),
1663 settings.GetRoot(), 1716 settings.GetRoot(),
1664 None, 1717 None,
1665 None, 1718 None,
1666 options.verbose, 1719 options.verbose,
1667 sys.stdout) 1720 sys.stdout)
1668 if not options.bot: 1721 if not options.bot:
1669 parser.error('No default try builder to try, use --bot') 1722 parser.error('No default try builder to try, use --bot')
(...skipping 139 matching lines...) Expand 10 before | Expand all | Expand 10 after
1809 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) 1862 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e)))
1810 1863
1811 # Not a known command. Default to help. 1864 # Not a known command. Default to help.
1812 GenUsage(parser, 'help') 1865 GenUsage(parser, 'help')
1813 return CMDhelp(parser, argv) 1866 return CMDhelp(parser, argv)
1814 1867
1815 1868
1816 if __name__ == '__main__': 1869 if __name__ == '__main__':
1817 fix_encoding.fix_encoding() 1870 fix_encoding.fix_encoding()
1818 sys.exit(main(sys.argv[1:])) 1871 sys.exit(main(sys.argv[1:]))
OLDNEW
« no previous file with comments | « no previous file | tests/git_cl_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698