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

Side by Side Diff: git_cl.py

Issue 7866007: Retry r100190 "Change git_cl.py to use subprocess2"" (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: Created 9 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2011 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 errno
11 import logging 10 import logging
12 import optparse 11 import optparse
13 import os 12 import os
14 import re 13 import re
15 import subprocess
16 import sys 14 import sys
17 import tempfile 15 import tempfile
18 import textwrap 16 import textwrap
19 import urlparse 17 import urlparse
20 import urllib2 18 import urllib2
21 19
22 try: 20 try:
23 import readline # pylint: disable=F0401,W0611 21 import readline # pylint: disable=F0401,W0611
24 except ImportError: 22 except ImportError:
25 pass 23 pass
26 24
27 try: 25 try:
28 import simplejson as json # pylint: disable=F0401 26 import simplejson as json # pylint: disable=F0401
29 except ImportError: 27 except ImportError:
30 try: 28 try:
31 import json # pylint: disable=F0401 29 import json # pylint: disable=F0401
32 except ImportError: 30 except ImportError:
33 # Fall back to the packaged version. 31 # Fall back to the packaged version.
34 sys.path.append(os.path.join(os.path.dirname(__file__), 'third_party')) 32 sys.path.append(os.path.join(os.path.dirname(__file__), 'third_party'))
35 import simplejson as json # pylint: disable=F0401 33 import simplejson as json # pylint: disable=F0401
36 34
37 35
38 from third_party import upload 36 from third_party import upload
39 import breakpad # pylint: disable=W0611 37 import breakpad # pylint: disable=W0611
40 import fix_encoding 38 import fix_encoding
41 import presubmit_support 39 import presubmit_support
42 import rietveld 40 import rietveld
43 import scm 41 import scm
42 import subprocess2
44 import watchlists 43 import watchlists
45 44
46 45
47
48 DEFAULT_SERVER = 'http://codereview.appspot.com' 46 DEFAULT_SERVER = 'http://codereview.appspot.com'
49 POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s' 47 POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s'
50 DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup' 48 DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup'
51 49
52 50
53 def DieWithError(message): 51 def DieWithError(message):
54 print >> sys.stderr, message 52 print >> sys.stderr, message
55 sys.exit(1) 53 sys.exit(1)
56 54
57 55
58 def Popen(cmd, **kwargs): 56 def RunCommand(args, error_ok=False, error_message=None, **kwargs):
59 """Wrapper for subprocess.Popen() that logs and watch for cygwin issues"""
60 logging.debug('Popen: ' + ' '.join(cmd))
61 try: 57 try:
62 return subprocess.Popen(cmd, **kwargs) 58 return subprocess2.check_output(args, shell=False, **kwargs)
63 except OSError, e: 59 except subprocess2.CalledProcessError, e:
64 if e.errno == errno.EAGAIN and sys.platform == 'cygwin': 60 if not error_ok:
65 DieWithError( 61 DieWithError(
66 'Visit ' 62 'Command "%s" failed.\n%s' % (
67 'http://code.google.com/p/chromium/wiki/CygwinDllRemappingFailure to ' 63 ' '.join(args), error_message or e.stdout or ''))
68 'learn how to fix this error; you need to rebase your cygwin dlls') 64 return e.stdout
69 raise
70
71
72 def RunCommand(cmd, error_ok=False, error_message=None,
73 redirect_stdout=True, swallow_stderr=False, **kwargs):
74 if redirect_stdout:
75 stdout = subprocess.PIPE
76 else:
77 stdout = None
78 if swallow_stderr:
79 stderr = subprocess.PIPE
80 else:
81 stderr = None
82 proc = Popen(cmd, stdout=stdout, stderr=stderr, **kwargs)
83 output = proc.communicate()[0]
84 if not error_ok and proc.returncode != 0:
85 DieWithError('Command "%s" failed.\n' % (' '.join(cmd)) +
86 (error_message or output or ''))
87 return output
88 65
89 66
90 def RunGit(args, **kwargs): 67 def RunGit(args, **kwargs):
91 cmd = ['git'] + args 68 """Returns stdout."""
92 return RunCommand(cmd, **kwargs) 69 return RunCommand(['git'] + args, **kwargs)
93 70
94 71
95 def RunGitWithCode(args): 72 def RunGitWithCode(args):
96 proc = Popen(['git'] + args, stdout=subprocess.PIPE) 73 """Returns return code and stdout."""
97 output = proc.communicate()[0] 74 out, code = subprocess2.communicate(['git'] + args, stdout=subprocess2.PIPE)
98 return proc.returncode, output 75 return code, out[0]
99 76
100 77
101 def usage(more): 78 def usage(more):
102 def hook(fn): 79 def hook(fn):
103 fn.usage_more = more 80 fn.usage_more = more
104 return fn 81 return fn
105 return hook 82 return hook
106 83
107 84
108 def ask_for_data(prompt): 85 def ask_for_data(prompt):
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
156 133
157 # Parse specs like "trunk/src:refs/remotes/origin/trunk". 134 # Parse specs like "trunk/src:refs/remotes/origin/trunk".
158 if fetch_suburl: 135 if fetch_suburl:
159 full_url = base_url + '/' + fetch_suburl 136 full_url = base_url + '/' + fetch_suburl
160 else: 137 else:
161 full_url = base_url 138 full_url = base_url
162 if full_url == url: 139 if full_url == url:
163 return as_ref 140 return as_ref
164 return None 141 return None
165 142
143
166 class Settings(object): 144 class Settings(object):
167 def __init__(self): 145 def __init__(self):
168 self.default_server = None 146 self.default_server = None
169 self.cc = None 147 self.cc = None
170 self.root = None 148 self.root = None
171 self.is_git_svn = None 149 self.is_git_svn = None
172 self.svn_branch = None 150 self.svn_branch = None
173 self.tree_status_url = None 151 self.tree_status_url = None
174 self.viewvc_url = None 152 self.viewvc_url = None
175 self.updated = False 153 self.updated = False
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
219 # 1) iterate through our branch history and find the svn URL. 197 # 1) iterate through our branch history and find the svn URL.
220 # 2) find the svn-remote that fetches from the URL. 198 # 2) find the svn-remote that fetches from the URL.
221 199
222 # regexp matching the git-svn line that contains the URL. 200 # regexp matching the git-svn line that contains the URL.
223 git_svn_re = re.compile(r'^\s*git-svn-id: (\S+)@', re.MULTILINE) 201 git_svn_re = re.compile(r'^\s*git-svn-id: (\S+)@', re.MULTILINE)
224 202
225 # We don't want to go through all of history, so read a line from the 203 # We don't want to go through all of history, so read a line from the
226 # pipe at a time. 204 # pipe at a time.
227 # The -100 is an arbitrary limit so we don't search forever. 205 # The -100 is an arbitrary limit so we don't search forever.
228 cmd = ['git', 'log', '-100', '--pretty=medium'] 206 cmd = ['git', 'log', '-100', '--pretty=medium']
229 proc = Popen(cmd, stdout=subprocess.PIPE) 207 proc = subprocess2.Popen(cmd, stdout=subprocess2.PIPE)
230 url = None 208 url = None
231 for line in proc.stdout: 209 for line in proc.stdout:
232 match = git_svn_re.match(line) 210 match = git_svn_re.match(line)
233 if match: 211 if match:
234 url = match.group(1) 212 url = match.group(1)
235 proc.stdout.close() # Cut pipe. 213 proc.stdout.close() # Cut pipe.
236 break 214 break
237 215
238 if url: 216 if url:
239 svn_remote_re = re.compile(r'^svn-remote\.([^.]+)\.url (.*)$') 217 svn_remote_re = re.compile(r'^svn-remote\.([^.]+)\.url (.*)$')
(...skipping 243 matching lines...) Expand 10 before | Expand all | Expand 10 after
483 self.patchset = None 461 self.patchset = None
484 self.has_patchset = True 462 self.has_patchset = True
485 return self.patchset 463 return self.patchset
486 464
487 def SetPatchset(self, patchset): 465 def SetPatchset(self, patchset):
488 """Set this branch's patchset. If patchset=0, clears the patchset.""" 466 """Set this branch's patchset. If patchset=0, clears the patchset."""
489 if patchset: 467 if patchset:
490 RunGit(['config', self._PatchsetSetting(), str(patchset)]) 468 RunGit(['config', self._PatchsetSetting(), str(patchset)])
491 else: 469 else:
492 RunGit(['config', '--unset', self._PatchsetSetting()], 470 RunGit(['config', '--unset', self._PatchsetSetting()],
493 swallow_stderr=True, error_ok=True) 471 stderr=subprocess2.PIPE, error_ok=True)
494 self.has_patchset = False 472 self.has_patchset = False
495 473
496 def GetPatchSetDiff(self, issue): 474 def GetPatchSetDiff(self, issue):
497 patchset = self.RpcServer().get_issue_properties( 475 patchset = self.RpcServer().get_issue_properties(
498 int(issue), False)['patchsets'][-1] 476 int(issue), False)['patchsets'][-1]
499 return self.RpcServer().get( 477 return self.RpcServer().get(
500 '/download/issue%s_%s.diff' % (issue, patchset)) 478 '/download/issue%s_%s.diff' % (issue, patchset))
501 479
502 def SetIssue(self, issue): 480 def SetIssue(self, issue):
503 """Set this branch's issue. If issue=0, clears the issue.""" 481 """Set this branch's issue. If issue=0, clears the issue."""
(...skipping 340 matching lines...) Expand 10 before | Expand all | Expand 10 after
844 fileobj.close() 822 fileobj.close()
845 823
846 # Open up the default editor in the system to get the CL description. 824 # Open up the default editor in the system to get the CL description.
847 try: 825 try:
848 cmd = '%s %s' % (editor, filename) 826 cmd = '%s %s' % (editor, filename)
849 if sys.platform == 'win32' and os.environ.get('TERM') == 'msys': 827 if sys.platform == 'win32' and os.environ.get('TERM') == 'msys':
850 # Msysgit requires the usage of 'env' to be present. 828 # Msysgit requires the usage of 'env' to be present.
851 cmd = 'env ' + cmd 829 cmd = 'env ' + cmd
852 # shell=True to allow the shell to handle all forms of quotes in $EDITOR. 830 # shell=True to allow the shell to handle all forms of quotes in $EDITOR.
853 try: 831 try:
854 subprocess.check_call(cmd, shell=True) 832 subprocess2.check_call(cmd, shell=True)
855 except subprocess.CalledProcessError, e: 833 except subprocess2.CalledProcessError, e:
856 DieWithError('Editor returned %d' % e.returncode) 834 DieWithError('Editor returned %d' % e.returncode)
857 fileobj = open(filename) 835 fileobj = open(filename)
858 text = fileobj.read() 836 text = fileobj.read()
859 fileobj.close() 837 fileobj.close()
860 finally: 838 finally:
861 os.remove(filename) 839 os.remove(filename)
862 840
863 if not text: 841 if not text:
864 return 842 return
865 843
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after
948 if not options.reviewers and hook_results.reviewers: 926 if not options.reviewers and hook_results.reviewers:
949 options.reviewers = hook_results.reviewers 927 options.reviewers = hook_results.reviewers
950 928
951 929
952 # --no-ext-diff is broken in some versions of Git, so try to work around 930 # --no-ext-diff is broken in some versions of Git, so try to work around
953 # this by overriding the environment (but there is still a problem if the 931 # this by overriding the environment (but there is still a problem if the
954 # git config key "diff.external" is used). 932 # git config key "diff.external" is used).
955 env = os.environ.copy() 933 env = os.environ.copy()
956 if 'GIT_EXTERNAL_DIFF' in env: 934 if 'GIT_EXTERNAL_DIFF' in env:
957 del env['GIT_EXTERNAL_DIFF'] 935 del env['GIT_EXTERNAL_DIFF']
958 subprocess.call(['git', 'diff', '--no-ext-diff', '--stat', '-M'] + args, 936 subprocess2.call(
959 env=env) 937 ['git', 'diff', '--no-ext-diff', '--stat', '-M'] + args, env=env)
960 938
961 upload_args = ['--assume_yes'] # Don't ask about untracked files. 939 upload_args = ['--assume_yes'] # Don't ask about untracked files.
962 upload_args.extend(['--server', cl.GetRietveldServer()]) 940 upload_args.extend(['--server', cl.GetRietveldServer()])
963 if options.emulate_svn_auto_props: 941 if options.emulate_svn_auto_props:
964 upload_args.append('--emulate_svn_auto_props') 942 upload_args.append('--emulate_svn_auto_props')
965 if options.send_mail: 943 if options.send_mail:
966 if not options.reviewers: 944 if not options.reviewers:
967 DieWithError("Must specify reviewers to send email.") 945 DieWithError("Must specify reviewers to send email.")
968 upload_args.append('--send_mail') 946 upload_args.append('--send_mail')
969 if options.from_logs and not options.message: 947 if options.from_logs and not options.message:
(...skipping 159 matching lines...) Expand 10 before | Expand all | Expand 10 after
1129 1107
1130 if cl.GetIssue(): 1108 if cl.GetIssue():
1131 description += "\n\nReview URL: %s" % cl.GetIssueURL() 1109 description += "\n\nReview URL: %s" % cl.GetIssueURL()
1132 1110
1133 if options.contributor: 1111 if options.contributor:
1134 description += "\nPatch from %s." % options.contributor 1112 description += "\nPatch from %s." % options.contributor
1135 print 'Description:', repr(description) 1113 print 'Description:', repr(description)
1136 1114
1137 branches = [base_branch, cl.GetBranchRef()] 1115 branches = [base_branch, cl.GetBranchRef()]
1138 if not options.force: 1116 if not options.force:
1139 subprocess.call(['git', 'diff', '--stat'] + branches) 1117 subprocess2.call(['git', 'diff', '--stat'] + branches)
1140 ask_for_data('About to commit; enter to confirm.') 1118 ask_for_data('About to commit; enter to confirm.')
1141 1119
1142 # We want to squash all this branch's commits into one commit with the 1120 # We want to squash all this branch's commits into one commit with the
1143 # proper description. 1121 # proper description.
1144 # We do this by doing a "reset --soft" to the base branch (which keeps 1122 # We do this by doing a "reset --soft" to the base branch (which keeps
1145 # the working copy the same), then dcommitting that. 1123 # the working copy the same), then dcommitting that.
1146 MERGE_BRANCH = 'git-cl-commit' 1124 MERGE_BRANCH = 'git-cl-commit'
1147 # Delete the merge branch if it already exists. 1125 # Delete the merge branch if it already exists.
1148 if RunGitWithCode(['show-ref', '--quiet', '--verify', 1126 if RunGitWithCode(['show-ref', '--quiet', '--verify',
1149 'refs/heads/' + MERGE_BRANCH])[0] == 0: 1127 'refs/heads/' + MERGE_BRANCH])[0] == 0:
(...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after
1266 match = re.match(r'.*?/issue(\d+)_\d+.diff', issue_url) 1244 match = re.match(r'.*?/issue(\d+)_\d+.diff', issue_url)
1267 if not match: 1245 if not match:
1268 DieWithError('Must pass an issue ID or full URL for ' 1246 DieWithError('Must pass an issue ID or full URL for '
1269 '\'Download raw patch set\'') 1247 '\'Download raw patch set\'')
1270 issue = match.group(1) 1248 issue = match.group(1)
1271 patch_data = urllib2.urlopen(issue_arg).read() 1249 patch_data = urllib2.urlopen(issue_arg).read()
1272 1250
1273 if options.newbranch: 1251 if options.newbranch:
1274 if options.force: 1252 if options.force:
1275 RunGit(['branch', '-D', options.newbranch], 1253 RunGit(['branch', '-D', options.newbranch],
1276 swallow_stderr=True, error_ok=True) 1254 stderr=subprocess2.PIPE, error_ok=True)
1277 RunGit(['checkout', '-b', options.newbranch, 1255 RunGit(['checkout', '-b', options.newbranch,
1278 Changelist().GetUpstreamBranch()]) 1256 Changelist().GetUpstreamBranch()])
1279 1257
1280 # Switch up to the top-level directory, if necessary, in preparation for 1258 # Switch up to the top-level directory, if necessary, in preparation for
1281 # applying the patch. 1259 # applying the patch.
1282 top = RunGit(['rev-parse', '--show-cdup']).strip() 1260 top = RunGit(['rev-parse', '--show-cdup']).strip()
1283 if top: 1261 if top:
1284 os.chdir(top) 1262 os.chdir(top)
1285 1263
1286 # Git patches have a/ at the beginning of source paths. We strip that out 1264 # Git patches have a/ at the beginning of source paths. We strip that out
1287 # with a sed script rather than the -p flag to patch so we can feed either 1265 # with a sed script rather than the -p flag to patch so we can feed either
1288 # Git or svn-style patches into the same apply command. 1266 # Git or svn-style patches into the same apply command.
1289 # re.sub() should be used but flags=re.MULTILINE is only in python 2.7. 1267 # re.sub() should be used but flags=re.MULTILINE is only in python 2.7.
1290 sed_proc = Popen(['sed', '-e', 's|^--- a/|--- |; s|^+++ b/|+++ |'], 1268 try:
1291 stdin=subprocess.PIPE, stdout=subprocess.PIPE) 1269 patch_data = subprocess2.check_output(
1292 patch_data = sed_proc.communicate(patch_data)[0] 1270 ['sed', '-e', 's|^--- a/|--- |; s|^+++ b/|+++ |'], stdin=patch_data)
1293 if sed_proc.returncode: 1271 except subprocess2.CalledProcessError:
1294 DieWithError('Git patch mungling failed.') 1272 DieWithError('Git patch mungling failed.')
1295 logging.info(patch_data) 1273 logging.info(patch_data)
1296 # We use "git apply" to apply the patch instead of "patch" so that we can 1274 # We use "git apply" to apply the patch instead of "patch" so that we can
1297 # pick up file adds. 1275 # pick up file adds.
1298 # The --index flag means: also insert into the index (so we catch adds). 1276 # The --index flag means: also insert into the index (so we catch adds).
1299 cmd = ['git', 'apply', '--index', '-p0'] 1277 cmd = ['git', 'apply', '--index', '-p0']
1300 if options.reject: 1278 if options.reject:
1301 cmd.append('--reject') 1279 cmd.append('--reject')
1302 patch_proc = Popen(cmd, stdin=subprocess.PIPE) 1280 try:
1303 patch_proc.communicate(patch_data) 1281 subprocess2.check_call(cmd, stdin=patch_data, stdout=subprocess2.VOID)
1304 if patch_proc.returncode: 1282 except subprocess2.CalledProcessError:
1305 DieWithError('Failed to apply the patch') 1283 DieWithError('Failed to apply the patch')
1306 1284
1307 # If we had an issue, commit the current state and register the issue. 1285 # If we had an issue, commit the current state and register the issue.
1308 if not options.nocommit: 1286 if not options.nocommit:
1309 RunGit(['commit', '-m', 'patch from issue %s' % issue]) 1287 RunGit(['commit', '-m', 'patch from issue %s' % issue])
1310 cl = Changelist() 1288 cl = Changelist()
1311 cl.SetIssue(issue) 1289 cl.SetIssue(issue)
1312 print "Committed patch." 1290 print "Committed patch."
1313 else: 1291 else:
1314 print "Patch applied to index." 1292 print "Patch applied to index."
1315 return 0 1293 return 0
1316 1294
1317 1295
1318 def CMDrebase(parser, args): 1296 def CMDrebase(parser, args):
1319 """rebase current branch on top of svn repo""" 1297 """rebase current branch on top of svn repo"""
1320 # Provide a wrapper for git svn rebase to help avoid accidental 1298 # Provide a wrapper for git svn rebase to help avoid accidental
1321 # git svn dcommit. 1299 # git svn dcommit.
1322 # It's the only command that doesn't use parser at all since we just defer 1300 # It's the only command that doesn't use parser at all since we just defer
1323 # execution to git-svn. 1301 # execution to git-svn.
1324 RunGit(['svn', 'rebase'] + args, redirect_stdout=False) 1302 subprocess2.check_call(['git', 'svn', 'rebase'] + args)
1325 return 0 1303 return 0
1326 1304
1327 1305
1328 def GetTreeStatus(): 1306 def GetTreeStatus():
1329 """Fetches the tree status and returns either 'open', 'closed', 1307 """Fetches the tree status and returns either 'open', 'closed',
1330 'unknown' or 'unset'.""" 1308 'unknown' or 'unset'."""
1331 url = settings.GetTreeStatusUrl(error_ok=True) 1309 url = settings.GetTreeStatusUrl(error_ok=True)
1332 if url: 1310 if url:
1333 status = urllib2.urlopen(url).read().lower() 1311 status = urllib2.urlopen(url).read().lower()
1334 if status.find('closed') != -1 or status == '0': 1312 if status.find('closed') != -1 or status == '0':
(...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after
1451 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) 1429 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e)))
1452 1430
1453 # Not a known command. Default to help. 1431 # Not a known command. Default to help.
1454 GenUsage(parser, 'help') 1432 GenUsage(parser, 'help')
1455 return CMDhelp(parser, argv) 1433 return CMDhelp(parser, argv)
1456 1434
1457 1435
1458 if __name__ == '__main__': 1436 if __name__ == '__main__':
1459 fix_encoding.fix_encoding() 1437 fix_encoding.fix_encoding()
1460 sys.exit(main(sys.argv[1:])) 1438 sys.exit(main(sys.argv[1:]))
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698