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 |