OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 # Copyright (C) 2008 Evan Martin <martine@danga.com> | 6 # Copyright (C) 2008 Evan Martin <martine@danga.com> |
7 | 7 |
8 """A git-command for integrating reviews on Rietveld.""" | 8 """A git-command for integrating reviews on Rietveld.""" |
9 | 9 |
10 import difflib | 10 import difflib |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
62 logging.debug('Failed running %s', args) | 62 logging.debug('Failed running %s', args) |
63 if not error_ok: | 63 if not error_ok: |
64 DieWithError( | 64 DieWithError( |
65 'Command "%s" failed.\n%s' % ( | 65 'Command "%s" failed.\n%s' % ( |
66 ' '.join(args), error_message or e.stdout or '')) | 66 ' '.join(args), error_message or e.stdout or '')) |
67 return e.stdout | 67 return e.stdout |
68 | 68 |
69 | 69 |
70 def RunGit(args, **kwargs): | 70 def RunGit(args, **kwargs): |
71 """Returns stdout.""" | 71 """Returns stdout.""" |
72 return RunCommand(['git', '--no-pager'] + args, **kwargs) | 72 return RunCommand(['git'] + args, **kwargs) |
73 | 73 |
74 | 74 |
75 def RunGitWithCode(args): | 75 def RunGitWithCode(args): |
76 """Returns return code and stdout.""" | 76 """Returns return code and stdout.""" |
77 try: | 77 try: |
78 out, code = subprocess2.communicate(['git', '--no-pager'] + args, | 78 env = os.environ.copy() |
| 79 # 'cat' is a magical git string that disables pagers on all platforms. |
| 80 env['GIT_PAGER'] = 'cat' |
| 81 out, code = subprocess2.communicate(['git'] + args, |
| 82 env=env, |
79 stdout=subprocess2.PIPE) | 83 stdout=subprocess2.PIPE) |
80 return code, out[0] | 84 return code, out[0] |
81 except ValueError: | 85 except ValueError: |
82 # When the subprocess fails, it returns None. That triggers a ValueError | 86 # When the subprocess fails, it returns None. That triggers a ValueError |
83 # when trying to unpack the return value into (out, code). | 87 # when trying to unpack the return value into (out, code). |
84 return 1, '' | 88 return 1, '' |
85 | 89 |
86 | 90 |
87 def usage(more): | 91 def usage(more): |
88 def hook(fn): | 92 def hook(fn): |
(...skipping 129 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
218 | 222 |
219 | 223 |
220 def print_stats(similarity, find_copies, args): | 224 def print_stats(similarity, find_copies, args): |
221 """Prints statistics about the change to the user.""" | 225 """Prints statistics about the change to the user.""" |
222 # --no-ext-diff is broken in some versions of Git, so try to work around | 226 # --no-ext-diff is broken in some versions of Git, so try to work around |
223 # this by overriding the environment (but there is still a problem if the | 227 # this by overriding the environment (but there is still a problem if the |
224 # git config key "diff.external" is used). | 228 # git config key "diff.external" is used). |
225 env = os.environ.copy() | 229 env = os.environ.copy() |
226 if 'GIT_EXTERNAL_DIFF' in env: | 230 if 'GIT_EXTERNAL_DIFF' in env: |
227 del env['GIT_EXTERNAL_DIFF'] | 231 del env['GIT_EXTERNAL_DIFF'] |
| 232 # 'cat' is a magical git string that disables pagers on all platforms. |
| 233 env['GIT_PAGER'] = 'cat' |
228 | 234 |
229 if find_copies: | 235 if find_copies: |
230 similarity_options = ['--find-copies-harder', '-l100000', | 236 similarity_options = ['--find-copies-harder', '-l100000', |
231 '-C%s' % similarity] | 237 '-C%s' % similarity] |
232 else: | 238 else: |
233 similarity_options = ['-M%s' % similarity] | 239 similarity_options = ['-M%s' % similarity] |
234 | 240 |
235 return subprocess2.call( | 241 return subprocess2.call( |
236 ['git', '--no-pager', | 242 ['git', |
237 'diff', '--no-ext-diff', '--stat'] + similarity_options + args, | 243 'diff', '--no-ext-diff', '--stat'] + similarity_options + args, |
238 env=env) | 244 env=env) |
239 | 245 |
240 | 246 |
241 class Settings(object): | 247 class Settings(object): |
242 def __init__(self): | 248 def __init__(self): |
243 self.default_server = None | 249 self.default_server = None |
244 self.cc = None | 250 self.cc = None |
245 self.root = None | 251 self.root = None |
246 self.is_git_svn = None | 252 self.is_git_svn = None |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
294 DieWithError('Repo doesn\'t appear to be a git-svn repo.') | 300 DieWithError('Repo doesn\'t appear to be a git-svn repo.') |
295 | 301 |
296 # Try to figure out which remote branch we're based on. | 302 # Try to figure out which remote branch we're based on. |
297 # Strategy: | 303 # Strategy: |
298 # 1) iterate through our branch history and find the svn URL. | 304 # 1) iterate through our branch history and find the svn URL. |
299 # 2) find the svn-remote that fetches from the URL. | 305 # 2) find the svn-remote that fetches from the URL. |
300 | 306 |
301 # regexp matching the git-svn line that contains the URL. | 307 # regexp matching the git-svn line that contains the URL. |
302 git_svn_re = re.compile(r'^\s*git-svn-id: (\S+)@', re.MULTILINE) | 308 git_svn_re = re.compile(r'^\s*git-svn-id: (\S+)@', re.MULTILINE) |
303 | 309 |
| 310 env = os.environ.copy() |
| 311 # 'cat' is a magical git string that disables pagers on all platforms. |
| 312 env['GIT_PAGER'] = 'cat' |
| 313 |
304 # We don't want to go through all of history, so read a line from the | 314 # We don't want to go through all of history, so read a line from the |
305 # pipe at a time. | 315 # pipe at a time. |
306 # The -100 is an arbitrary limit so we don't search forever. | 316 # The -100 is an arbitrary limit so we don't search forever. |
307 cmd = ['git', '--no-pager', 'log', '-100', '--pretty=medium'] | 317 cmd = ['git', 'log', '-100', '--pretty=medium'] |
308 proc = subprocess2.Popen(cmd, stdout=subprocess2.PIPE) | 318 proc = subprocess2.Popen(cmd, stdout=subprocess2.PIPE, env=env) |
309 url = None | 319 url = None |
310 for line in proc.stdout: | 320 for line in proc.stdout: |
311 match = git_svn_re.match(line) | 321 match = git_svn_re.match(line) |
312 if match: | 322 if match: |
313 url = match.group(1) | 323 url = match.group(1) |
314 proc.stdout.close() # Cut pipe. | 324 proc.stdout.close() # Cut pipe. |
315 break | 325 break |
316 | 326 |
317 if url: | 327 if url: |
318 svn_remote_re = re.compile(r'^svn-remote\.([^.]+)\.url (.*)$') | 328 svn_remote_re = re.compile(r'^svn-remote\.([^.]+)\.url (.*)$') |
(...skipping 358 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
677 RunGit(['config', self._RietveldServer(), self.rietveld_server]) | 687 RunGit(['config', self._RietveldServer(), self.rietveld_server]) |
678 else: | 688 else: |
679 RunGit(['config', '--unset', self._IssueSetting()]) | 689 RunGit(['config', '--unset', self._IssueSetting()]) |
680 self.SetPatchset(0) | 690 self.SetPatchset(0) |
681 self.has_issue = False | 691 self.has_issue = False |
682 | 692 |
683 def GetChange(self, upstream_branch, author): | 693 def GetChange(self, upstream_branch, author): |
684 if not self.GitSanityChecks(upstream_branch): | 694 if not self.GitSanityChecks(upstream_branch): |
685 DieWithError('\nGit sanity check failure') | 695 DieWithError('\nGit sanity check failure') |
686 | 696 |
687 root = RunCommand(['git', '--no-pager', 'rev-parse', '--show-cdup']).strip() | 697 env = os.environ.copy() |
| 698 # 'cat' is a magical git string that disables pagers on all platforms. |
| 699 env['GIT_PAGER'] = 'cat' |
| 700 |
| 701 root = RunCommand(['git', 'rev-parse', '--show-cdup'], env=env).strip() |
688 if not root: | 702 if not root: |
689 root = '.' | 703 root = '.' |
690 absroot = os.path.abspath(root) | 704 absroot = os.path.abspath(root) |
691 | 705 |
692 # We use the sha1 of HEAD as a name of this change. | 706 # We use the sha1 of HEAD as a name of this change. |
693 name = RunCommand(['git', '--no-pager', 'rev-parse', 'HEAD']).strip() | 707 name = RunCommand(['git', 'rev-parse', 'HEAD'], env=env).strip() |
694 # Need to pass a relative path for msysgit. | 708 # Need to pass a relative path for msysgit. |
695 try: | 709 try: |
696 files = scm.GIT.CaptureStatus([root], '.', upstream_branch) | 710 files = scm.GIT.CaptureStatus([root], '.', upstream_branch) |
697 except subprocess2.CalledProcessError: | 711 except subprocess2.CalledProcessError: |
698 DieWithError( | 712 DieWithError( |
699 ('\nFailed to diff against upstream branch %s!\n\n' | 713 ('\nFailed to diff against upstream branch %s!\n\n' |
700 'This branch probably doesn\'t exist anymore. To reset the\n' | 714 'This branch probably doesn\'t exist anymore. To reset the\n' |
701 'tracking branch, please run\n' | 715 'tracking branch, please run\n' |
702 ' git branch --set-upstream %s trunk\n' | 716 ' git branch --set-upstream %s trunk\n' |
703 'replacing trunk with origin/master or the relevant branch') % | 717 'replacing trunk with origin/master or the relevant branch') % |
704 (upstream_branch, self.GetBranch())) | 718 (upstream_branch, self.GetBranch())) |
705 | 719 |
706 issue = self.GetIssue() | 720 issue = self.GetIssue() |
707 patchset = self.GetPatchset() | 721 patchset = self.GetPatchset() |
708 if issue: | 722 if issue: |
709 description = self.GetDescription() | 723 description = self.GetDescription() |
710 else: | 724 else: |
711 # If the change was never uploaded, use the log messages of all commits | 725 # If the change was never uploaded, use the log messages of all commits |
712 # up to the branch point, as git cl upload will prefill the description | 726 # up to the branch point, as git cl upload will prefill the description |
713 # with these log messages. | 727 # with these log messages. |
714 description = RunCommand(['git', '--no-pager', | 728 description = RunCommand(['git', |
715 'log', '--pretty=format:%s%n%n%b', | 729 'log', '--pretty=format:%s%n%n%b', |
716 '%s...' % (upstream_branch)]).strip() | 730 '%s...' % (upstream_branch)], |
| 731 env=env).strip() |
717 | 732 |
718 if not author: | 733 if not author: |
719 author = RunGit(['config', 'user.email']).strip() or None | 734 author = RunGit(['config', 'user.email']).strip() or None |
720 return presubmit_support.GitChange( | 735 return presubmit_support.GitChange( |
721 name, | 736 name, |
722 description, | 737 description, |
723 absroot, | 738 absroot, |
724 files, | 739 files, |
725 issue, | 740 issue, |
726 patchset, | 741 patchset, |
(...skipping 1017 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1744 # Git patches have a/ at the beginning of source paths. We strip that out | 1759 # Git patches have a/ at the beginning of source paths. We strip that out |
1745 # with a sed script rather than the -p flag to patch so we can feed either | 1760 # with a sed script rather than the -p flag to patch so we can feed either |
1746 # Git or svn-style patches into the same apply command. | 1761 # Git or svn-style patches into the same apply command. |
1747 # re.sub() should be used but flags=re.MULTILINE is only in python 2.7. | 1762 # re.sub() should be used but flags=re.MULTILINE is only in python 2.7. |
1748 try: | 1763 try: |
1749 patch_data = subprocess2.check_output( | 1764 patch_data = subprocess2.check_output( |
1750 ['sed', '-e', 's|^--- a/|--- |; s|^+++ b/|+++ |'], stdin=patch_data) | 1765 ['sed', '-e', 's|^--- a/|--- |; s|^+++ b/|+++ |'], stdin=patch_data) |
1751 except subprocess2.CalledProcessError: | 1766 except subprocess2.CalledProcessError: |
1752 DieWithError('Git patch mungling failed.') | 1767 DieWithError('Git patch mungling failed.') |
1753 logging.info(patch_data) | 1768 logging.info(patch_data) |
| 1769 env = os.environ.copy() |
| 1770 # 'cat' is a magical git string that disables pagers on all platforms. |
| 1771 env['GIT_PAGER'] = 'cat' |
| 1772 |
1754 # We use "git apply" to apply the patch instead of "patch" so that we can | 1773 # We use "git apply" to apply the patch instead of "patch" so that we can |
1755 # pick up file adds. | 1774 # pick up file adds. |
1756 # The --index flag means: also insert into the index (so we catch adds). | 1775 # The --index flag means: also insert into the index (so we catch adds). |
1757 cmd = ['git', '--no-pager', 'apply', '--index', '-p0'] | 1776 cmd = ['git', 'apply', '--index', '-p0'] |
1758 if options.reject: | 1777 if options.reject: |
1759 cmd.append('--reject') | 1778 cmd.append('--reject') |
1760 try: | 1779 try: |
1761 subprocess2.check_call(cmd, stdin=patch_data, stdout=subprocess2.VOID) | 1780 subprocess2.check_call(cmd, env=env, |
| 1781 stdin=patch_data, stdout=subprocess2.VOID) |
1762 except subprocess2.CalledProcessError: | 1782 except subprocess2.CalledProcessError: |
1763 DieWithError('Failed to apply the patch') | 1783 DieWithError('Failed to apply the patch') |
1764 | 1784 |
1765 # If we had an issue, commit the current state and register the issue. | 1785 # If we had an issue, commit the current state and register the issue. |
1766 if not options.nocommit: | 1786 if not options.nocommit: |
1767 RunGit(['commit', '-m', 'patch from issue %s' % issue]) | 1787 RunGit(['commit', '-m', 'patch from issue %s' % issue]) |
1768 cl = Changelist() | 1788 cl = Changelist() |
1769 cl.SetIssue(issue) | 1789 cl.SetIssue(issue) |
1770 cl.SetPatchset(patchset) | 1790 cl.SetPatchset(patchset) |
1771 print "Committed patch locally." | 1791 print "Committed patch locally." |
1772 else: | 1792 else: |
1773 print "Patch applied to index." | 1793 print "Patch applied to index." |
1774 return 0 | 1794 return 0 |
1775 | 1795 |
1776 | 1796 |
1777 def CMDrebase(parser, args): | 1797 def CMDrebase(parser, args): |
1778 """rebase current branch on top of svn repo""" | 1798 """rebase current branch on top of svn repo""" |
1779 # Provide a wrapper for git svn rebase to help avoid accidental | 1799 # Provide a wrapper for git svn rebase to help avoid accidental |
1780 # git svn dcommit. | 1800 # git svn dcommit. |
1781 # It's the only command that doesn't use parser at all since we just defer | 1801 # It's the only command that doesn't use parser at all since we just defer |
1782 # execution to git-svn. | 1802 # execution to git-svn. |
1783 return subprocess2.call(['git', '--no-pager', 'svn', 'rebase'] + args) | 1803 env = os.environ.copy() |
| 1804 # 'cat' is a magical git string that disables pagers on all platforms. |
| 1805 env['GIT_PAGER'] = 'cat' |
| 1806 |
| 1807 return subprocess2.call(['git', 'svn', 'rebase'] + args, env=env) |
1784 | 1808 |
1785 | 1809 |
1786 def GetTreeStatus(): | 1810 def GetTreeStatus(): |
1787 """Fetches the tree status and returns either 'open', 'closed', | 1811 """Fetches the tree status and returns either 'open', 'closed', |
1788 'unknown' or 'unset'.""" | 1812 'unknown' or 'unset'.""" |
1789 url = settings.GetTreeStatusUrl(error_ok=True) | 1813 url = settings.GetTreeStatusUrl(error_ok=True) |
1790 if url: | 1814 if url: |
1791 status = urllib2.urlopen(url).read().lower() | 1815 status = urllib2.urlopen(url).read().lower() |
1792 if status.find('closed') != -1 or status == '0': | 1816 if status.find('closed') != -1 or status == '0': |
1793 return 'closed' | 1817 return 'closed' |
(...skipping 348 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2142 GenUsage(parser, 'help') | 2166 GenUsage(parser, 'help') |
2143 return CMDhelp(parser, argv) | 2167 return CMDhelp(parser, argv) |
2144 | 2168 |
2145 | 2169 |
2146 if __name__ == '__main__': | 2170 if __name__ == '__main__': |
2147 # These affect sys.stdout so do it outside of main() to simplify mocks in | 2171 # These affect sys.stdout so do it outside of main() to simplify mocks in |
2148 # unit testing. | 2172 # unit testing. |
2149 fix_encoding.fix_encoding() | 2173 fix_encoding.fix_encoding() |
2150 colorama.init() | 2174 colorama.init() |
2151 sys.exit(main(sys.argv[1:])) | 2175 sys.exit(main(sys.argv[1:])) |
OLD | NEW |