| 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 from distutils.version import LooseVersion | 10 from distutils.version import LooseVersion |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 56 | 56 |
| 57 # Initialized in main() | 57 # Initialized in main() |
| 58 settings = None | 58 settings = None |
| 59 | 59 |
| 60 | 60 |
| 61 def DieWithError(message): | 61 def DieWithError(message): |
| 62 print >> sys.stderr, message | 62 print >> sys.stderr, message |
| 63 sys.exit(1) | 63 sys.exit(1) |
| 64 | 64 |
| 65 | 65 |
| 66 def GetNoGitPagerEnv(): |
| 67 env = os.environ.copy() |
| 68 # 'cat' is a magical git string that disables pagers on all platforms. |
| 69 env['GIT_PAGER'] = 'cat' |
| 70 return env |
| 71 |
| 66 def RunCommand(args, error_ok=False, error_message=None, **kwargs): | 72 def RunCommand(args, error_ok=False, error_message=None, **kwargs): |
| 67 try: | 73 try: |
| 68 return subprocess2.check_output(args, shell=False, **kwargs) | 74 return subprocess2.check_output(args, shell=False, **kwargs) |
| 69 except subprocess2.CalledProcessError as e: | 75 except subprocess2.CalledProcessError as e: |
| 70 logging.debug('Failed running %s', args) | 76 logging.debug('Failed running %s', args) |
| 71 if not error_ok: | 77 if not error_ok: |
| 72 DieWithError( | 78 DieWithError( |
| 73 'Command "%s" failed.\n%s' % ( | 79 'Command "%s" failed.\n%s' % ( |
| 74 ' '.join(args), error_message or e.stdout or '')) | 80 ' '.join(args), error_message or e.stdout or '')) |
| 75 return e.stdout | 81 return e.stdout |
| 76 | 82 |
| 77 | 83 |
| 78 def RunGit(args, **kwargs): | 84 def RunGit(args, **kwargs): |
| 79 """Returns stdout.""" | 85 """Returns stdout.""" |
| 80 return RunCommand(['git'] + args, **kwargs) | 86 return RunCommand(['git'] + args, **kwargs) |
| 81 | 87 |
| 82 | 88 |
| 83 def RunGitWithCode(args, suppress_stderr=False): | 89 def RunGitWithCode(args, suppress_stderr=False): |
| 84 """Returns return code and stdout.""" | 90 """Returns return code and stdout.""" |
| 85 try: | 91 try: |
| 86 env = os.environ.copy() | |
| 87 # 'cat' is a magical git string that disables pagers on all platforms. | |
| 88 env['GIT_PAGER'] = 'cat' | |
| 89 if suppress_stderr: | 92 if suppress_stderr: |
| 90 stderr = subprocess2.VOID | 93 stderr = subprocess2.VOID |
| 91 else: | 94 else: |
| 92 stderr = sys.stderr | 95 stderr = sys.stderr |
| 93 out, code = subprocess2.communicate(['git'] + args, | 96 out, code = subprocess2.communicate(['git'] + args, |
| 94 env=env, | 97 env=GetNoGitPagerEnv(), |
| 95 stdout=subprocess2.PIPE, | 98 stdout=subprocess2.PIPE, |
| 96 stderr=stderr) | 99 stderr=stderr) |
| 97 return code, out[0] | 100 return code, out[0] |
| 98 except ValueError: | 101 except ValueError: |
| 99 # When the subprocess fails, it returns None. That triggers a ValueError | 102 # When the subprocess fails, it returns None. That triggers a ValueError |
| 100 # when trying to unpack the return value into (out, code). | 103 # when trying to unpack the return value into (out, code). |
| 101 return 1, '' | 104 return 1, '' |
| 102 | 105 |
| 103 | 106 |
| 104 def IsGitVersionAtLeast(min_version): | 107 def IsGitVersionAtLeast(min_version): |
| (...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 232 if full_url == url: | 235 if full_url == url: |
| 233 return as_ref | 236 return as_ref |
| 234 return None | 237 return None |
| 235 | 238 |
| 236 | 239 |
| 237 def print_stats(similarity, find_copies, args): | 240 def print_stats(similarity, find_copies, args): |
| 238 """Prints statistics about the change to the user.""" | 241 """Prints statistics about the change to the user.""" |
| 239 # --no-ext-diff is broken in some versions of Git, so try to work around | 242 # --no-ext-diff is broken in some versions of Git, so try to work around |
| 240 # this by overriding the environment (but there is still a problem if the | 243 # this by overriding the environment (but there is still a problem if the |
| 241 # git config key "diff.external" is used). | 244 # git config key "diff.external" is used). |
| 242 env = os.environ.copy() | 245 env = GetNoGitPagerEnv() |
| 243 if 'GIT_EXTERNAL_DIFF' in env: | 246 if 'GIT_EXTERNAL_DIFF' in env: |
| 244 del env['GIT_EXTERNAL_DIFF'] | 247 del env['GIT_EXTERNAL_DIFF'] |
| 245 # 'cat' is a magical git string that disables pagers on all platforms. | |
| 246 env['GIT_PAGER'] = 'cat' | |
| 247 | 248 |
| 248 if find_copies: | 249 if find_copies: |
| 249 similarity_options = ['--find-copies-harder', '-l100000', | 250 similarity_options = ['--find-copies-harder', '-l100000', |
| 250 '-C%s' % similarity] | 251 '-C%s' % similarity] |
| 251 else: | 252 else: |
| 252 similarity_options = ['-M%s' % similarity] | 253 similarity_options = ['-M%s' % similarity] |
| 253 | 254 |
| 254 return subprocess2.call( | 255 return subprocess2.call( |
| 255 ['git', | 256 ['git', |
| 256 'diff', '--no-ext-diff', '--stat'] + similarity_options + args, | 257 'diff', '--no-ext-diff', '--stat'] + similarity_options + args, |
| 257 env=env) | 258 env=env) |
| 258 | 259 |
| 259 | 260 |
| 260 class Settings(object): | 261 class Settings(object): |
| 261 def __init__(self): | 262 def __init__(self): |
| 262 self.default_server = None | 263 self.default_server = None |
| 263 self.cc = None | 264 self.cc = None |
| 264 self.root = None | 265 self.relative_root = None |
| 265 self.is_git_svn = None | 266 self.is_git_svn = None |
| 266 self.svn_branch = None | 267 self.svn_branch = None |
| 267 self.tree_status_url = None | 268 self.tree_status_url = None |
| 268 self.viewvc_url = None | 269 self.viewvc_url = None |
| 269 self.updated = False | 270 self.updated = False |
| 270 self.is_gerrit = None | 271 self.is_gerrit = None |
| 271 self.git_editor = None | 272 self.git_editor = None |
| 272 | 273 |
| 273 def LazyUpdateIfNeeded(self): | 274 def LazyUpdateIfNeeded(self): |
| 274 """Updates the settings from a codereview.settings file, if available.""" | 275 """Updates the settings from a codereview.settings file, if available.""" |
| (...skipping 10 matching lines...) Expand all Loading... |
| 285 # set updated to True to avoid infinite calling loop | 286 # set updated to True to avoid infinite calling loop |
| 286 # through DownloadHooks | 287 # through DownloadHooks |
| 287 self.updated = True | 288 self.updated = True |
| 288 DownloadHooks(False) | 289 DownloadHooks(False) |
| 289 self.updated = True | 290 self.updated = True |
| 290 | 291 |
| 291 def GetDefaultServerUrl(self, error_ok=False): | 292 def GetDefaultServerUrl(self, error_ok=False): |
| 292 if not self.default_server: | 293 if not self.default_server: |
| 293 self.LazyUpdateIfNeeded() | 294 self.LazyUpdateIfNeeded() |
| 294 self.default_server = gclient_utils.UpgradeToHttps( | 295 self.default_server = gclient_utils.UpgradeToHttps( |
| 295 self._GetConfig('rietveld.server', error_ok=True)) | 296 self._GetRietveldConfig('server', error_ok=True)) |
| 296 if error_ok: | 297 if error_ok: |
| 297 return self.default_server | 298 return self.default_server |
| 298 if not self.default_server: | 299 if not self.default_server: |
| 299 error_message = ('Could not find settings file. You must configure ' | 300 error_message = ('Could not find settings file. You must configure ' |
| 300 'your review setup by running "git cl config".') | 301 'your review setup by running "git cl config".') |
| 301 self.default_server = gclient_utils.UpgradeToHttps( | 302 self.default_server = gclient_utils.UpgradeToHttps( |
| 302 self._GetConfig('rietveld.server', error_message=error_message)) | 303 self._GetRietveldConfig('server', error_message=error_message)) |
| 303 return self.default_server | 304 return self.default_server |
| 304 | 305 |
| 306 def GetRelativeRoot(self): |
| 307 if self.relative_root is None: |
| 308 self.relative_root = RunGit(['rev-parse', '--show-cdup']).strip() |
| 309 return self.relative_root |
| 310 |
| 305 def GetRoot(self): | 311 def GetRoot(self): |
| 306 if not self.root: | 312 return os.path.abspath(self.GetRelativeRoot()) |
| 307 self.root = os.path.abspath(RunGit(['rev-parse', '--show-cdup']).strip()) | |
| 308 return self.root | |
| 309 | 313 |
| 310 def GetIsGitSvn(self): | 314 def GetIsGitSvn(self): |
| 311 """Return true if this repo looks like it's using git-svn.""" | 315 """Return true if this repo looks like it's using git-svn.""" |
| 312 if self.is_git_svn is None: | 316 if self.is_git_svn is None: |
| 313 # If you have any "svn-remote.*" config keys, we think you're using svn. | 317 # If you have any "svn-remote.*" config keys, we think you're using svn. |
| 314 self.is_git_svn = RunGitWithCode( | 318 self.is_git_svn = RunGitWithCode( |
| 315 ['config', '--local', '--get-regexp', r'^svn-remote\.'])[0] == 0 | 319 ['config', '--local', '--get-regexp', r'^svn-remote\.'])[0] == 0 |
| 316 return self.is_git_svn | 320 return self.is_git_svn |
| 317 | 321 |
| 318 def GetSVNBranch(self): | 322 def GetSVNBranch(self): |
| 319 if self.svn_branch is None: | 323 if self.svn_branch is None: |
| 320 if not self.GetIsGitSvn(): | 324 if not self.GetIsGitSvn(): |
| 321 DieWithError('Repo doesn\'t appear to be a git-svn repo.') | 325 DieWithError('Repo doesn\'t appear to be a git-svn repo.') |
| 322 | 326 |
| 323 # Try to figure out which remote branch we're based on. | 327 # Try to figure out which remote branch we're based on. |
| 324 # Strategy: | 328 # Strategy: |
| 325 # 1) iterate through our branch history and find the svn URL. | 329 # 1) iterate through our branch history and find the svn URL. |
| 326 # 2) find the svn-remote that fetches from the URL. | 330 # 2) find the svn-remote that fetches from the URL. |
| 327 | 331 |
| 328 # regexp matching the git-svn line that contains the URL. | 332 # regexp matching the git-svn line that contains the URL. |
| 329 git_svn_re = re.compile(r'^\s*git-svn-id: (\S+)@', re.MULTILINE) | 333 git_svn_re = re.compile(r'^\s*git-svn-id: (\S+)@', re.MULTILINE) |
| 330 | 334 |
| 331 env = os.environ.copy() | |
| 332 # 'cat' is a magical git string that disables pagers on all platforms. | |
| 333 env['GIT_PAGER'] = 'cat' | |
| 334 | |
| 335 # We don't want to go through all of history, so read a line from the | 335 # We don't want to go through all of history, so read a line from the |
| 336 # pipe at a time. | 336 # pipe at a time. |
| 337 # The -100 is an arbitrary limit so we don't search forever. | 337 # The -100 is an arbitrary limit so we don't search forever. |
| 338 cmd = ['git', 'log', '-100', '--pretty=medium'] | 338 cmd = ['git', 'log', '-100', '--pretty=medium'] |
| 339 proc = subprocess2.Popen(cmd, stdout=subprocess2.PIPE, env=env) | 339 proc = subprocess2.Popen(cmd, stdout=subprocess2.PIPE, |
| 340 env=GetNoGitPagerEnv()) |
| 340 url = None | 341 url = None |
| 341 for line in proc.stdout: | 342 for line in proc.stdout: |
| 342 match = git_svn_re.match(line) | 343 match = git_svn_re.match(line) |
| 343 if match: | 344 if match: |
| 344 url = match.group(1) | 345 url = match.group(1) |
| 345 proc.stdout.close() # Cut pipe. | 346 proc.stdout.close() # Cut pipe. |
| 346 break | 347 break |
| 347 | 348 |
| 348 if url: | 349 if url: |
| 349 svn_remote_re = re.compile(r'^svn-remote\.([^.]+)\.url (.*)$') | 350 svn_remote_re = re.compile(r'^svn-remote\.([^.]+)\.url (.*)$') |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 384 if not self.svn_branch: | 385 if not self.svn_branch: |
| 385 DieWithError('Can\'t guess svn branch -- try specifying it on the ' | 386 DieWithError('Can\'t guess svn branch -- try specifying it on the ' |
| 386 'command line') | 387 'command line') |
| 387 | 388 |
| 388 return self.svn_branch | 389 return self.svn_branch |
| 389 | 390 |
| 390 def GetTreeStatusUrl(self, error_ok=False): | 391 def GetTreeStatusUrl(self, error_ok=False): |
| 391 if not self.tree_status_url: | 392 if not self.tree_status_url: |
| 392 error_message = ('You must configure your tree status URL by running ' | 393 error_message = ('You must configure your tree status URL by running ' |
| 393 '"git cl config".') | 394 '"git cl config".') |
| 394 self.tree_status_url = self._GetConfig('rietveld.tree-status-url', | 395 self.tree_status_url = self._GetRietveldConfig( |
| 395 error_ok=error_ok, | 396 'tree-status-url', error_ok=error_ok, error_message=error_message) |
| 396 error_message=error_message) | |
| 397 return self.tree_status_url | 397 return self.tree_status_url |
| 398 | 398 |
| 399 def GetViewVCUrl(self): | 399 def GetViewVCUrl(self): |
| 400 if not self.viewvc_url: | 400 if not self.viewvc_url: |
| 401 self.viewvc_url = self._GetConfig('rietveld.viewvc-url', error_ok=True) | 401 self.viewvc_url = self._GetRietveldConfig('viewvc-url', error_ok=True) |
| 402 return self.viewvc_url | 402 return self.viewvc_url |
| 403 | 403 |
| 404 def GetBugPrefix(self): | 404 def GetBugPrefix(self): |
| 405 return self._GetConfig('rietveld.bug-prefix', error_ok=True) | 405 return self._GetRietveldConfig('bug-prefix', error_ok=True) |
| 406 | 406 |
| 407 def GetDefaultCCList(self): | 407 def GetDefaultCCList(self): |
| 408 return self._GetConfig('rietveld.cc', error_ok=True) | 408 return self._GetRietveldConfig('cc', error_ok=True) |
| 409 | 409 |
| 410 def GetDefaultPrivateFlag(self): | 410 def GetDefaultPrivateFlag(self): |
| 411 return self._GetConfig('rietveld.private', error_ok=True) | 411 return self._GetRietveldConfig('private', error_ok=True) |
| 412 | 412 |
| 413 def GetIsGerrit(self): | 413 def GetIsGerrit(self): |
| 414 """Return true if this repo is assosiated with gerrit code review system.""" | 414 """Return true if this repo is assosiated with gerrit code review system.""" |
| 415 if self.is_gerrit is None: | 415 if self.is_gerrit is None: |
| 416 self.is_gerrit = self._GetConfig('gerrit.host', error_ok=True) | 416 self.is_gerrit = self._GetConfig('gerrit.host', error_ok=True) |
| 417 return self.is_gerrit | 417 return self.is_gerrit |
| 418 | 418 |
| 419 def GetGitEditor(self): | 419 def GetGitEditor(self): |
| 420 """Return the editor specified in the git config, or None if none is.""" | 420 """Return the editor specified in the git config, or None if none is.""" |
| 421 if self.git_editor is None: | 421 if self.git_editor is None: |
| 422 self.git_editor = self._GetConfig('core.editor', error_ok=True) | 422 self.git_editor = self._GetConfig('core.editor', error_ok=True) |
| 423 return self.git_editor or None | 423 return self.git_editor or None |
| 424 | 424 |
| 425 def _GetRietveldConfig(self, param, **kwargs): |
| 426 return self._GetConfig('rietveld.' + param, **kwargs) |
| 427 |
| 425 def _GetConfig(self, param, **kwargs): | 428 def _GetConfig(self, param, **kwargs): |
| 426 self.LazyUpdateIfNeeded() | 429 self.LazyUpdateIfNeeded() |
| 427 return RunGit(['config', param], **kwargs).strip() | 430 return RunGit(['config', param], **kwargs).strip() |
| 428 | 431 |
| 429 | 432 |
| 430 def ShortBranchName(branch): | 433 def ShortBranchName(branch): |
| 431 """Convert a name like 'refs/heads/foo' to just 'foo'.""" | 434 """Convert a name like 'refs/heads/foo' to just 'foo'.""" |
| 432 return branch.replace('refs/heads/', '') | 435 return branch.replace('refs/heads/', '') |
| 433 | 436 |
| 434 | 437 |
| (...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 527 upstream_branch = 'refs/heads/trunk' | 530 upstream_branch = 'refs/heads/trunk' |
| 528 else: | 531 else: |
| 529 DieWithError("""Unable to determine default branch to diff against. | 532 DieWithError("""Unable to determine default branch to diff against. |
| 530 Either pass complete "git diff"-style arguments, like | 533 Either pass complete "git diff"-style arguments, like |
| 531 git cl upload origin/master | 534 git cl upload origin/master |
| 532 or verify this branch is set up to track another (via the --track argument to | 535 or verify this branch is set up to track another (via the --track argument to |
| 533 "git checkout -b ...").""") | 536 "git checkout -b ...").""") |
| 534 | 537 |
| 535 return remote, upstream_branch | 538 return remote, upstream_branch |
| 536 | 539 |
| 540 def GetCommonAncestorWithUpstream(self): |
| 541 return RunGit(['merge-base', self.GetUpstreamBranch(), 'HEAD']).strip() |
| 542 |
| 537 def GetUpstreamBranch(self): | 543 def GetUpstreamBranch(self): |
| 538 if self.upstream_branch is None: | 544 if self.upstream_branch is None: |
| 539 remote, upstream_branch = self.FetchUpstreamTuple(self.GetBranch()) | 545 remote, upstream_branch = self.FetchUpstreamTuple(self.GetBranch()) |
| 540 if remote is not '.': | 546 if remote is not '.': |
| 541 upstream_branch = upstream_branch.replace('heads', 'remotes/' + remote) | 547 upstream_branch = upstream_branch.replace('heads', 'remotes/' + remote) |
| 542 self.upstream_branch = upstream_branch | 548 self.upstream_branch = upstream_branch |
| 543 return self.upstream_branch | 549 return self.upstream_branch |
| 544 | 550 |
| 545 def GetRemoteBranch(self): | 551 def GetRemoteBranch(self): |
| 546 if not self._remote: | 552 if not self._remote: |
| (...skipping 181 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 728 current_issue = self.GetIssue() | 734 current_issue = self.GetIssue() |
| 729 if current_issue: | 735 if current_issue: |
| 730 RunGit(['config', '--unset', self._IssueSetting()]) | 736 RunGit(['config', '--unset', self._IssueSetting()]) |
| 731 self.issue = None | 737 self.issue = None |
| 732 self.SetPatchset(None) | 738 self.SetPatchset(None) |
| 733 | 739 |
| 734 def GetChange(self, upstream_branch, author): | 740 def GetChange(self, upstream_branch, author): |
| 735 if not self.GitSanityChecks(upstream_branch): | 741 if not self.GitSanityChecks(upstream_branch): |
| 736 DieWithError('\nGit sanity check failure') | 742 DieWithError('\nGit sanity check failure') |
| 737 | 743 |
| 738 env = os.environ.copy() | 744 root = settings.GetRelativeRoot() |
| 739 # 'cat' is a magical git string that disables pagers on all platforms. | |
| 740 env['GIT_PAGER'] = 'cat' | |
| 741 | |
| 742 root = RunCommand(['git', 'rev-parse', '--show-cdup'], env=env).strip() | |
| 743 if not root: | 745 if not root: |
| 744 root = '.' | 746 root = '.' |
| 745 absroot = os.path.abspath(root) | 747 absroot = os.path.abspath(root) |
| 746 | 748 |
| 747 # We use the sha1 of HEAD as a name of this change. | 749 # We use the sha1 of HEAD as a name of this change. |
| 748 name = RunCommand(['git', 'rev-parse', 'HEAD'], env=env).strip() | 750 name = RunGitWithCode(['rev-parse', 'HEAD'])[1].strip() |
| 749 # Need to pass a relative path for msysgit. | 751 # Need to pass a relative path for msysgit. |
| 750 try: | 752 try: |
| 751 files = scm.GIT.CaptureStatus([root], '.', upstream_branch) | 753 files = scm.GIT.CaptureStatus([root], '.', upstream_branch) |
| 752 except subprocess2.CalledProcessError: | 754 except subprocess2.CalledProcessError: |
| 753 DieWithError( | 755 DieWithError( |
| 754 ('\nFailed to diff against upstream branch %s\n\n' | 756 ('\nFailed to diff against upstream branch %s\n\n' |
| 755 'This branch probably doesn\'t exist anymore. To reset the\n' | 757 'This branch probably doesn\'t exist anymore. To reset the\n' |
| 756 'tracking branch, please run\n' | 758 'tracking branch, please run\n' |
| 757 ' git branch --set-upstream %s trunk\n' | 759 ' git branch --set-upstream %s trunk\n' |
| 758 'replacing trunk with origin/master or the relevant branch') % | 760 'replacing trunk with origin/master or the relevant branch') % |
| 759 (upstream_branch, self.GetBranch())) | 761 (upstream_branch, self.GetBranch())) |
| 760 | 762 |
| 761 issue = self.GetIssue() | 763 issue = self.GetIssue() |
| 762 patchset = self.GetPatchset() | 764 patchset = self.GetPatchset() |
| 763 if issue: | 765 if issue: |
| 764 description = self.GetDescription() | 766 description = self.GetDescription() |
| 765 else: | 767 else: |
| 766 # If the change was never uploaded, use the log messages of all commits | 768 # If the change was never uploaded, use the log messages of all commits |
| 767 # up to the branch point, as git cl upload will prefill the description | 769 # up to the branch point, as git cl upload will prefill the description |
| 768 # with these log messages. | 770 # with these log messages. |
| 769 description = RunCommand(['git', | 771 args = ['log', '--pretty=format:%s%n%n%b', '%s...' % (upstream_branch)] |
| 770 'log', '--pretty=format:%s%n%n%b', | 772 description = RunGitWithCode(args)[1].strip() |
| 771 '%s...' % (upstream_branch)], | |
| 772 env=env).strip() | |
| 773 | 773 |
| 774 if not author: | 774 if not author: |
| 775 author = RunGit(['config', 'user.email']).strip() or None | 775 author = RunGit(['config', 'user.email']).strip() or None |
| 776 return presubmit_support.GitChange( | 776 return presubmit_support.GitChange( |
| 777 name, | 777 name, |
| 778 description, | 778 description, |
| 779 absroot, | 779 absroot, |
| 780 files, | 780 files, |
| 781 issue, | 781 issue, |
| 782 patchset, | 782 patchset, |
| (...skipping 222 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1005 | 1005 |
| 1006 | 1006 |
| 1007 def FindCodereviewSettingsFile(filename='codereview.settings'): | 1007 def FindCodereviewSettingsFile(filename='codereview.settings'): |
| 1008 """Finds the given file starting in the cwd and going up. | 1008 """Finds the given file starting in the cwd and going up. |
| 1009 | 1009 |
| 1010 Only looks up to the top of the repository unless an | 1010 Only looks up to the top of the repository unless an |
| 1011 'inherit-review-settings-ok' file exists in the root of the repository. | 1011 'inherit-review-settings-ok' file exists in the root of the repository. |
| 1012 """ | 1012 """ |
| 1013 inherit_ok_file = 'inherit-review-settings-ok' | 1013 inherit_ok_file = 'inherit-review-settings-ok' |
| 1014 cwd = os.getcwd() | 1014 cwd = os.getcwd() |
| 1015 root = os.path.abspath(RunGit(['rev-parse', '--show-cdup']).strip()) | 1015 root = settings.GetRoot() |
| 1016 if os.path.isfile(os.path.join(root, inherit_ok_file)): | 1016 if os.path.isfile(os.path.join(root, inherit_ok_file)): |
| 1017 root = '/' | 1017 root = '/' |
| 1018 while True: | 1018 while True: |
| 1019 if filename in os.listdir(cwd): | 1019 if filename in os.listdir(cwd): |
| 1020 if os.path.isfile(os.path.join(cwd, filename)): | 1020 if os.path.isfile(os.path.join(cwd, filename)): |
| 1021 return open(os.path.join(cwd, filename)) | 1021 return open(os.path.join(cwd, filename)) |
| 1022 if cwd == root: | 1022 if cwd == root: |
| 1023 break | 1023 break |
| 1024 cwd = os.path.dirname(cwd) | 1024 cwd = os.path.dirname(cwd) |
| 1025 | 1025 |
| (...skipping 349 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1375 | 1375 |
| 1376 if not options.force and is_dirty_git_tree('presubmit'): | 1376 if not options.force and is_dirty_git_tree('presubmit'): |
| 1377 print 'use --force to check even if tree is dirty.' | 1377 print 'use --force to check even if tree is dirty.' |
| 1378 return 1 | 1378 return 1 |
| 1379 | 1379 |
| 1380 cl = Changelist() | 1380 cl = Changelist() |
| 1381 if args: | 1381 if args: |
| 1382 base_branch = args[0] | 1382 base_branch = args[0] |
| 1383 else: | 1383 else: |
| 1384 # Default to diffing against the common ancestor of the upstream branch. | 1384 # Default to diffing against the common ancestor of the upstream branch. |
| 1385 base_branch = RunGit(['merge-base', cl.GetUpstreamBranch(), 'HEAD']).strip() | 1385 base_branch = cl.GetCommonAncestorWithUpstream() |
| 1386 | 1386 |
| 1387 cl.RunHook( | 1387 cl.RunHook( |
| 1388 committing=not options.upload, | 1388 committing=not options.upload, |
| 1389 may_prompt=False, | 1389 may_prompt=False, |
| 1390 verbose=options.verbose, | 1390 verbose=options.verbose, |
| 1391 change=cl.GetChange(base_branch, None)) | 1391 change=cl.GetChange(base_branch, None)) |
| 1392 return 0 | 1392 return 0 |
| 1393 | 1393 |
| 1394 | 1394 |
| 1395 def AddChangeIdToCommitMessage(options, args): | 1395 def AddChangeIdToCommitMessage(options, args): |
| (...skipping 215 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1611 | 1611 |
| 1612 options.reviewers = cleanup_list(options.reviewers) | 1612 options.reviewers = cleanup_list(options.reviewers) |
| 1613 options.cc = cleanup_list(options.cc) | 1613 options.cc = cleanup_list(options.cc) |
| 1614 | 1614 |
| 1615 cl = Changelist() | 1615 cl = Changelist() |
| 1616 if args: | 1616 if args: |
| 1617 # TODO(ukai): is it ok for gerrit case? | 1617 # TODO(ukai): is it ok for gerrit case? |
| 1618 base_branch = args[0] | 1618 base_branch = args[0] |
| 1619 else: | 1619 else: |
| 1620 # Default to diffing against common ancestor of upstream branch | 1620 # Default to diffing against common ancestor of upstream branch |
| 1621 base_branch = RunGit(['merge-base', cl.GetUpstreamBranch(), 'HEAD']).strip() | 1621 base_branch = cl.GetCommonAncestorWithUpstream() |
| 1622 args = [base_branch, 'HEAD'] | 1622 args = [base_branch, 'HEAD'] |
| 1623 | 1623 |
| 1624 # Apply watchlists on upload. | 1624 # Apply watchlists on upload. |
| 1625 change = cl.GetChange(base_branch, None) | 1625 change = cl.GetChange(base_branch, None) |
| 1626 watchlist = watchlists.Watchlists(change.RepositoryRoot()) | 1626 watchlist = watchlists.Watchlists(change.RepositoryRoot()) |
| 1627 files = [f.LocalPath() for f in change.AffectedFiles()] | 1627 files = [f.LocalPath() for f in change.AffectedFiles()] |
| 1628 if not options.bypass_watchlists: | 1628 if not options.bypass_watchlists: |
| 1629 cl.SetWatchers(watchlist.GetWatchersForPaths(files)) | 1629 cl.SetWatchers(watchlist.GetWatchersForPaths(files)) |
| 1630 | 1630 |
| 1631 if not options.bypass_hooks: | 1631 if not options.bypass_hooks: |
| (...skipping 176 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1808 # Delete the branches if they exist. | 1808 # Delete the branches if they exist. |
| 1809 for branch in [MERGE_BRANCH, CHERRY_PICK_BRANCH]: | 1809 for branch in [MERGE_BRANCH, CHERRY_PICK_BRANCH]: |
| 1810 showref_cmd = ['show-ref', '--quiet', '--verify', 'refs/heads/%s' % branch] | 1810 showref_cmd = ['show-ref', '--quiet', '--verify', 'refs/heads/%s' % branch] |
| 1811 result = RunGitWithCode(showref_cmd) | 1811 result = RunGitWithCode(showref_cmd) |
| 1812 if result[0] == 0: | 1812 if result[0] == 0: |
| 1813 RunGit(['branch', '-D', branch]) | 1813 RunGit(['branch', '-D', branch]) |
| 1814 | 1814 |
| 1815 # We might be in a directory that's present in this branch but not in the | 1815 # We might be in a directory that's present in this branch but not in the |
| 1816 # trunk. Move up to the top of the tree so that git commands that expect a | 1816 # trunk. Move up to the top of the tree so that git commands that expect a |
| 1817 # valid CWD won't fail after we check out the merge branch. | 1817 # valid CWD won't fail after we check out the merge branch. |
| 1818 rel_base_path = RunGit(['rev-parse', '--show-cdup']).strip() | 1818 rel_base_path = settings.GetRelativeRoot() |
| 1819 if rel_base_path: | 1819 if rel_base_path: |
| 1820 os.chdir(rel_base_path) | 1820 os.chdir(rel_base_path) |
| 1821 | 1821 |
| 1822 # Stuff our change into the merge branch. | 1822 # Stuff our change into the merge branch. |
| 1823 # We wrap in a try...finally block so if anything goes wrong, | 1823 # We wrap in a try...finally block so if anything goes wrong, |
| 1824 # we clean up the branches. | 1824 # we clean up the branches. |
| 1825 retcode = -1 | 1825 retcode = -1 |
| 1826 try: | 1826 try: |
| 1827 RunGit(['checkout', '-q', '-b', MERGE_BRANCH]) | 1827 RunGit(['checkout', '-q', '-b', MERGE_BRANCH]) |
| 1828 RunGit(['reset', '--soft', base_branch]) | 1828 RunGit(['reset', '--soft', base_branch]) |
| (...skipping 142 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1971 match = re.match(r'.*?/issue(\d+)_(\d+).diff', issue_url) | 1971 match = re.match(r'.*?/issue(\d+)_(\d+).diff', issue_url) |
| 1972 if not match: | 1972 if not match: |
| 1973 DieWithError('Must pass an issue ID or full URL for ' | 1973 DieWithError('Must pass an issue ID or full URL for ' |
| 1974 '\'Download raw patch set\'') | 1974 '\'Download raw patch set\'') |
| 1975 issue = int(match.group(1)) | 1975 issue = int(match.group(1)) |
| 1976 patchset = int(match.group(2)) | 1976 patchset = int(match.group(2)) |
| 1977 patch_data = urllib2.urlopen(issue_arg).read() | 1977 patch_data = urllib2.urlopen(issue_arg).read() |
| 1978 | 1978 |
| 1979 # Switch up to the top-level directory, if necessary, in preparation for | 1979 # Switch up to the top-level directory, if necessary, in preparation for |
| 1980 # applying the patch. | 1980 # applying the patch. |
| 1981 top = RunGit(['rev-parse', '--show-cdup']).strip() | 1981 top = settings.GetRelativeRoot() |
| 1982 if top: | 1982 if top: |
| 1983 os.chdir(top) | 1983 os.chdir(top) |
| 1984 | 1984 |
| 1985 # Git patches have a/ at the beginning of source paths. We strip that out | 1985 # Git patches have a/ at the beginning of source paths. We strip that out |
| 1986 # with a sed script rather than the -p flag to patch so we can feed either | 1986 # with a sed script rather than the -p flag to patch so we can feed either |
| 1987 # Git or svn-style patches into the same apply command. | 1987 # Git or svn-style patches into the same apply command. |
| 1988 # re.sub() should be used but flags=re.MULTILINE is only in python 2.7. | 1988 # re.sub() should be used but flags=re.MULTILINE is only in python 2.7. |
| 1989 try: | 1989 try: |
| 1990 patch_data = subprocess2.check_output( | 1990 patch_data = subprocess2.check_output( |
| 1991 ['sed', '-e', 's|^--- a/|--- |; s|^+++ b/|+++ |'], stdin=patch_data) | 1991 ['sed', '-e', 's|^--- a/|--- |; s|^+++ b/|+++ |'], stdin=patch_data) |
| 1992 except subprocess2.CalledProcessError: | 1992 except subprocess2.CalledProcessError: |
| 1993 DieWithError('Git patch mungling failed.') | 1993 DieWithError('Git patch mungling failed.') |
| 1994 logging.info(patch_data) | 1994 logging.info(patch_data) |
| 1995 env = os.environ.copy() | |
| 1996 # 'cat' is a magical git string that disables pagers on all platforms. | |
| 1997 env['GIT_PAGER'] = 'cat' | |
| 1998 | 1995 |
| 1999 # We use "git apply" to apply the patch instead of "patch" so that we can | 1996 # We use "git apply" to apply the patch instead of "patch" so that we can |
| 2000 # pick up file adds. | 1997 # pick up file adds. |
| 2001 # The --index flag means: also insert into the index (so we catch adds). | 1998 # The --index flag means: also insert into the index (so we catch adds). |
| 2002 cmd = ['git', 'apply', '--index', '-p0'] | 1999 cmd = ['git', 'apply', '--index', '-p0'] |
| 2003 if directory: | 2000 if directory: |
| 2004 cmd.extend(('--directory', directory)) | 2001 cmd.extend(('--directory', directory)) |
| 2005 if reject: | 2002 if reject: |
| 2006 cmd.append('--reject') | 2003 cmd.append('--reject') |
| 2007 elif IsGitVersionAtLeast('1.7.12'): | 2004 elif IsGitVersionAtLeast('1.7.12'): |
| 2008 cmd.append('--3way') | 2005 cmd.append('--3way') |
| 2009 try: | 2006 try: |
| 2010 subprocess2.check_call(cmd, env=env, | 2007 subprocess2.check_call(cmd, env=GetNoGitPagerEnv(), |
| 2011 stdin=patch_data, stdout=subprocess2.VOID) | 2008 stdin=patch_data, stdout=subprocess2.VOID) |
| 2012 except subprocess2.CalledProcessError: | 2009 except subprocess2.CalledProcessError: |
| 2013 DieWithError('Failed to apply the patch') | 2010 DieWithError('Failed to apply the patch') |
| 2014 | 2011 |
| 2015 # If we had an issue, commit the current state and register the issue. | 2012 # If we had an issue, commit the current state and register the issue. |
| 2016 if not nocommit: | 2013 if not nocommit: |
| 2017 RunGit(['commit', '-m', 'patch from issue %s' % issue]) | 2014 RunGit(['commit', '-m', 'patch from issue %s' % issue]) |
| 2018 cl = Changelist() | 2015 cl = Changelist() |
| 2019 cl.SetIssue(issue) | 2016 cl.SetIssue(issue) |
| 2020 cl.SetPatchset(patchset) | 2017 cl.SetPatchset(patchset) |
| 2021 print "Committed patch locally." | 2018 print "Committed patch locally." |
| 2022 else: | 2019 else: |
| 2023 print "Patch applied to index." | 2020 print "Patch applied to index." |
| 2024 return 0 | 2021 return 0 |
| 2025 | 2022 |
| 2026 | 2023 |
| 2027 def CMDrebase(parser, args): | 2024 def CMDrebase(parser, args): |
| 2028 """Rebases current branch on top of svn repo.""" | 2025 """Rebases current branch on top of svn repo.""" |
| 2029 # Provide a wrapper for git svn rebase to help avoid accidental | 2026 # Provide a wrapper for git svn rebase to help avoid accidental |
| 2030 # git svn dcommit. | 2027 # git svn dcommit. |
| 2031 # It's the only command that doesn't use parser at all since we just defer | 2028 # It's the only command that doesn't use parser at all since we just defer |
| 2032 # execution to git-svn. | 2029 # execution to git-svn. |
| 2033 env = os.environ.copy() | |
| 2034 # 'cat' is a magical git string that disables pagers on all platforms. | |
| 2035 env['GIT_PAGER'] = 'cat' | |
| 2036 | 2030 |
| 2037 return subprocess2.call(['git', 'svn', 'rebase'] + args, env=env) | 2031 return RunGitWithCode(['svn', 'rebase'] + args)[1] |
| 2038 | 2032 |
| 2039 | 2033 |
| 2040 def GetTreeStatus(url=None): | 2034 def GetTreeStatus(url=None): |
| 2041 """Fetches the tree status and returns either 'open', 'closed', | 2035 """Fetches the tree status and returns either 'open', 'closed', |
| 2042 'unknown' or 'unset'.""" | 2036 'unknown' or 'unset'.""" |
| 2043 url = url or settings.GetTreeStatusUrl(error_ok=True) | 2037 url = url or settings.GetTreeStatusUrl(error_ok=True) |
| 2044 if url: | 2038 if url: |
| 2045 status = urllib2.urlopen(url).read().lower() | 2039 status = urllib2.urlopen(url).read().lower() |
| 2046 if status.find('closed') != -1 or status == '0': | 2040 if status.find('closed') != -1 or status == '0': |
| 2047 return 'closed' | 2041 return 'closed' |
| (...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2118 cl = Changelist() | 2112 cl = Changelist() |
| 2119 if not cl.GetIssue(): | 2113 if not cl.GetIssue(): |
| 2120 parser.error('Need to upload first') | 2114 parser.error('Need to upload first') |
| 2121 | 2115 |
| 2122 if not options.name: | 2116 if not options.name: |
| 2123 options.name = cl.GetBranch() | 2117 options.name = cl.GetBranch() |
| 2124 | 2118 |
| 2125 # Process --bot and --testfilter. | 2119 # Process --bot and --testfilter. |
| 2126 if not options.bot: | 2120 if not options.bot: |
| 2127 # Get try slaves from PRESUBMIT.py files if not specified. | 2121 # Get try slaves from PRESUBMIT.py files if not specified. |
| 2128 change = cl.GetChange( | 2122 change = cl.GetChange(cl.GetCommonAncestorWithUpstream(), None) |
| 2129 RunGit(['merge-base', cl.GetUpstreamBranch(), 'HEAD']).strip(), | |
| 2130 None) | |
| 2131 options.bot = presubmit_support.DoGetTrySlaves( | 2123 options.bot = presubmit_support.DoGetTrySlaves( |
| 2132 change, | 2124 change, |
| 2133 change.LocalPaths(), | 2125 change.LocalPaths(), |
| 2134 settings.GetRoot(), | 2126 settings.GetRoot(), |
| 2135 None, | 2127 None, |
| 2136 None, | 2128 None, |
| 2137 options.verbose, | 2129 options.verbose, |
| 2138 sys.stdout) | 2130 sys.stdout) |
| 2139 if not options.bot: | 2131 if not options.bot: |
| 2140 parser.error('No default try builder to try, use --bot') | 2132 parser.error('No default try builder to try, use --bot') |
| (...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2247 | 2239 |
| 2248 | 2240 |
| 2249 def CMDdiff(parser, args): | 2241 def CMDdiff(parser, args): |
| 2250 """shows differences between local tree and last upload.""" | 2242 """shows differences between local tree and last upload.""" |
| 2251 cl = Changelist() | 2243 cl = Changelist() |
| 2252 issue = cl.GetIssue() | 2244 issue = cl.GetIssue() |
| 2253 branch = cl.GetBranch() | 2245 branch = cl.GetBranch() |
| 2254 if not issue: | 2246 if not issue: |
| 2255 DieWithError('No issue found for current branch (%s)' % branch) | 2247 DieWithError('No issue found for current branch (%s)' % branch) |
| 2256 TMP_BRANCH = 'git-cl-diff' | 2248 TMP_BRANCH = 'git-cl-diff' |
| 2257 base_branch = RunGit(['merge-base', cl.GetUpstreamBranch(), 'HEAD']).strip() | 2249 base_branch = cl.GetCommonAncestorWithUpstream() |
| 2258 | 2250 |
| 2259 # Create a new branch based on the merge-base | 2251 # Create a new branch based on the merge-base |
| 2260 RunGit(['checkout', '-q', '-b', TMP_BRANCH, base_branch]) | 2252 RunGit(['checkout', '-q', '-b', TMP_BRANCH, base_branch]) |
| 2261 try: | 2253 try: |
| 2262 # Patch in the latest changes from rietveld. | 2254 # Patch in the latest changes from rietveld. |
| 2263 rtn = PatchIssue(issue, False, False, None) | 2255 rtn = PatchIssue(issue, False, False, None) |
| 2264 if rtn != 0: | 2256 if rtn != 0: |
| 2265 return rtn | 2257 return rtn |
| 2266 | 2258 |
| 2267 # Switch back to starting brand and diff against the temporary | 2259 # Switch back to starting brand and diff against the temporary |
| (...skipping 17 matching lines...) Expand all Loading... |
| 2285 author = RunGit(['config', 'user.email']).strip() or None | 2277 author = RunGit(['config', 'user.email']).strip() or None |
| 2286 | 2278 |
| 2287 cl = Changelist() | 2279 cl = Changelist() |
| 2288 | 2280 |
| 2289 if args: | 2281 if args: |
| 2290 if len(args) > 1: | 2282 if len(args) > 1: |
| 2291 parser.error('Unknown args') | 2283 parser.error('Unknown args') |
| 2292 base_branch = args[0] | 2284 base_branch = args[0] |
| 2293 else: | 2285 else: |
| 2294 # Default to diffing against the common ancestor of the upstream branch. | 2286 # Default to diffing against the common ancestor of the upstream branch. |
| 2295 base_branch = RunGit(['merge-base', cl.GetUpstreamBranch(), 'HEAD']).strip() | 2287 base_branch = cl.GetCommonAncestorWithUpstream() |
| 2296 | 2288 |
| 2297 change = cl.GetChange(base_branch, None) | 2289 change = cl.GetChange(base_branch, None) |
| 2298 return owners_finder.OwnersFinder( | 2290 return owners_finder.OwnersFinder( |
| 2299 [f.LocalPath() for f in | 2291 [f.LocalPath() for f in |
| 2300 cl.GetChange(base_branch, None).AffectedFiles()], | 2292 cl.GetChange(base_branch, None).AffectedFiles()], |
| 2301 change.RepositoryRoot(), author, | 2293 change.RepositoryRoot(), author, |
| 2302 fopen=file, os_path=os.path, glob=glob.glob, | 2294 fopen=file, os_path=os.path, glob=glob.glob, |
| 2303 disable_color=options.no_color).run() | 2295 disable_color=options.no_color).run() |
| 2304 | 2296 |
| 2305 | 2297 |
| 2306 @subcommand.usage('[files or directories to diff]') | 2298 @subcommand.usage('[files or directories to diff]') |
| 2307 def CMDformat(parser, args): | 2299 def CMDformat(parser, args): |
| 2308 """Runs clang-format on the diff.""" | 2300 """Runs clang-format on the diff.""" |
| 2309 CLANG_EXTS = ['.cc', '.cpp', '.h', '.mm'] | 2301 CLANG_EXTS = ['.cc', '.cpp', '.h', '.mm'] |
| 2310 parser.add_option('--full', action='store_true', | 2302 parser.add_option('--full', action='store_true', |
| 2311 help='Reformat the full content of all touched files') | 2303 help='Reformat the full content of all touched files') |
| 2312 parser.add_option('--dry-run', action='store_true', | 2304 parser.add_option('--dry-run', action='store_true', |
| 2313 help='Don\'t modify any file on disk.') | 2305 help='Don\'t modify any file on disk.') |
| 2314 opts, args = parser.parse_args(args) | 2306 opts, args = parser.parse_args(args) |
| 2315 | 2307 |
| 2316 # git diff generates paths against the root of the repository. Change | 2308 # git diff generates paths against the root of the repository. Change |
| 2317 # to that directory so clang-format can find files even within subdirs. | 2309 # to that directory so clang-format can find files even within subdirs. |
| 2318 rel_base_path = RunGit(['rev-parse', '--show-cdup']).strip() | 2310 rel_base_path = settings.GetRelativeRoot() |
| 2319 if rel_base_path: | 2311 if rel_base_path: |
| 2320 os.chdir(rel_base_path) | 2312 os.chdir(rel_base_path) |
| 2321 | 2313 |
| 2322 # Generate diff for the current branch's changes. | 2314 # Generate diff for the current branch's changes. |
| 2323 diff_cmd = ['diff', '--no-ext-diff', '--no-prefix'] | 2315 diff_cmd = ['diff', '--no-ext-diff', '--no-prefix'] |
| 2324 if opts.full: | 2316 if opts.full: |
| 2325 # Only list the names of modified files. | 2317 # Only list the names of modified files. |
| 2326 diff_cmd.append('--name-only') | 2318 diff_cmd.append('--name-only') |
| 2327 else: | 2319 else: |
| 2328 # Only generate context-less patches. | 2320 # Only generate context-less patches. |
| (...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2437 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' | 2429 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' |
| 2438 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) | 2430 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) |
| 2439 | 2431 |
| 2440 | 2432 |
| 2441 if __name__ == '__main__': | 2433 if __name__ == '__main__': |
| 2442 # These affect sys.stdout so do it outside of main() to simplify mocks in | 2434 # These affect sys.stdout so do it outside of main() to simplify mocks in |
| 2443 # unit testing. | 2435 # unit testing. |
| 2444 fix_encoding.fix_encoding() | 2436 fix_encoding.fix_encoding() |
| 2445 colorama.init() | 2437 colorama.init() |
| 2446 sys.exit(main(sys.argv[1:])) | 2438 sys.exit(main(sys.argv[1:])) |
| OLD | NEW |