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

Side by Side Diff: git_cl.py

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