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

Side by Side Diff: scm.py

Issue 18262002: Misc gclient cleanup (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: Improve IsValidRevision logic Created 7 years, 5 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 | « gclient_scm.py ('k') | 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 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """SCM-specific utility classes.""" 5 """SCM-specific utility classes."""
6 6
7 import cStringIO 7 import cStringIO
8 import glob 8 import glob
9 import logging 9 import logging
10 import os 10 import os
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after
93 return 0 93 return 0
94 94
95 95
96 class GIT(object): 96 class GIT(object):
97 current_version = None 97 current_version = None
98 98
99 @staticmethod 99 @staticmethod
100 def Capture(args, cwd, **kwargs): 100 def Capture(args, cwd, **kwargs):
101 return subprocess2.check_output( 101 return subprocess2.check_output(
102 ['git', '--no-pager'] + args, 102 ['git', '--no-pager'] + args,
103 cwd=cwd, stderr=subprocess2.PIPE, **kwargs) 103 cwd=cwd, stderr=subprocess2.PIPE, **kwargs).strip()
104 104
105 @staticmethod 105 @staticmethod
106 def CaptureStatus(files, cwd, upstream_branch): 106 def CaptureStatus(files, cwd, upstream_branch):
107 """Returns git status. 107 """Returns git status.
108 108
109 @files can be a string (one file) or a list of files. 109 @files can be a string (one file) or a list of files.
110 110
111 Returns an array of (status, file) tuples.""" 111 Returns an array of (status, file) tuples."""
112 if upstream_branch is None: 112 if upstream_branch is None:
113 upstream_branch = GIT.GetUpstreamBranch(cwd) 113 upstream_branch = GIT.GetUpstreamBranch(cwd)
114 if upstream_branch is None: 114 if upstream_branch is None:
115 raise gclient_utils.Error('Cannot determine upstream branch') 115 raise gclient_utils.Error('Cannot determine upstream branch')
116 command = ['diff', '--name-status', '--no-renames', 116 command = ['diff', '--name-status', '--no-renames',
117 '-r', '%s...' % upstream_branch] 117 '-r', '%s...' % upstream_branch]
118 if not files: 118 if not files:
119 pass 119 pass
120 elif isinstance(files, basestring): 120 elif isinstance(files, basestring):
121 command.append(files) 121 command.append(files)
122 else: 122 else:
123 command.extend(files) 123 command.extend(files)
124 status = GIT.Capture(command, cwd).rstrip() 124 status = GIT.Capture(command, cwd)
125 results = [] 125 results = []
126 if status: 126 if status:
127 for statusline in status.splitlines(): 127 for statusline in status.splitlines():
128 # 3-way merges can cause the status can be 'MMM' instead of 'M'. This 128 # 3-way merges can cause the status can be 'MMM' instead of 'M'. This
129 # can happen when the user has 2 local branches and he diffs between 129 # can happen when the user has 2 local branches and he diffs between
130 # these 2 branches instead diffing to upstream. 130 # these 2 branches instead diffing to upstream.
131 m = re.match('^(\w)+\t(.+)$', statusline) 131 m = re.match('^(\w)+\t(.+)$', statusline)
132 if not m: 132 if not m:
133 raise gclient_utils.Error( 133 raise gclient_utils.Error(
134 'status currently unsupported: %s' % statusline) 134 'status currently unsupported: %s' % statusline)
135 # Only grab the first letter. 135 # Only grab the first letter.
136 results.append(('%s ' % m.group(1)[0], m.group(2))) 136 results.append(('%s ' % m.group(1)[0], m.group(2)))
137 return results 137 return results
138 138
139 @staticmethod 139 @staticmethod
140 def GetEmail(cwd): 140 def GetEmail(cwd):
141 """Retrieves the user email address if known.""" 141 """Retrieves the user email address if known."""
142 # We could want to look at the svn cred when it has a svn remote but it 142 # We could want to look at the svn cred when it has a svn remote but it
143 # should be fine for now, users should simply configure their git settings. 143 # should be fine for now, users should simply configure their git settings.
144 try: 144 try:
145 return GIT.Capture(['config', 'user.email'], cwd=cwd).strip() 145 return GIT.Capture(['config', 'user.email'], cwd=cwd)
146 except subprocess2.CalledProcessError: 146 except subprocess2.CalledProcessError:
147 return '' 147 return ''
148 148
149 @staticmethod 149 @staticmethod
150 def ShortBranchName(branch): 150 def ShortBranchName(branch):
151 """Converts a name like 'refs/heads/foo' to just 'foo'.""" 151 """Converts a name like 'refs/heads/foo' to just 'foo'."""
152 return branch.replace('refs/heads/', '') 152 return branch.replace('refs/heads/', '')
153 153
154 @staticmethod 154 @staticmethod
155 def GetBranchRef(cwd): 155 def GetBranchRef(cwd):
156 """Returns the full branch reference, e.g. 'refs/heads/master'.""" 156 """Returns the full branch reference, e.g. 'refs/heads/master'."""
157 return GIT.Capture(['symbolic-ref', 'HEAD'], cwd=cwd).strip() 157 return GIT.Capture(['symbolic-ref', 'HEAD'], cwd=cwd)
158 158
159 @staticmethod 159 @staticmethod
160 def GetBranch(cwd): 160 def GetBranch(cwd):
161 """Returns the short branch name, e.g. 'master'.""" 161 """Returns the short branch name, e.g. 'master'."""
162 return GIT.ShortBranchName(GIT.GetBranchRef(cwd)) 162 return GIT.ShortBranchName(GIT.GetBranchRef(cwd))
163 163
164 @staticmethod 164 @staticmethod
165 def IsGitSvn(cwd): 165 def IsGitSvn(cwd):
166 """Returns true if this repo looks like it's using git-svn.""" 166 """Returns true if this repo looks like it's using git-svn."""
167 # If you have any "svn-remote.*" config keys, we think you're using svn. 167 # If you have any "svn-remote.*" config keys, we think you're using svn.
168 try: 168 try:
169 GIT.Capture(['config', '--get-regexp', r'^svn-remote\.'], cwd=cwd) 169 GIT.Capture(['config', '--local', '--get-regexp', r'^svn-remote\.'],
170 cwd=cwd)
170 return True 171 return True
171 except subprocess2.CalledProcessError: 172 except subprocess2.CalledProcessError:
172 return False 173 return False
173 174
174 @staticmethod 175 @staticmethod
175 def MatchSvnGlob(url, base_url, glob_spec, allow_wildcards): 176 def MatchSvnGlob(url, base_url, glob_spec, allow_wildcards):
176 """Return the corresponding git ref if |base_url| together with |glob_spec| 177 """Return the corresponding git ref if |base_url| together with |glob_spec|
177 matches the full |url|. 178 matches the full |url|.
178 179
179 If |allow_wildcards| is true, |glob_spec| can contain wildcards (see below). 180 If |allow_wildcards| is true, |glob_spec| can contain wildcards (see below).
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
233 for line in proc.stdout: 234 for line in proc.stdout:
234 match = git_svn_re.match(line) 235 match = git_svn_re.match(line)
235 if match: 236 if match:
236 url = match.group(1) 237 url = match.group(1)
237 proc.stdout.close() # Cut pipe. 238 proc.stdout.close() # Cut pipe.
238 break 239 break
239 240
240 if url: 241 if url:
241 svn_remote_re = re.compile(r'^svn-remote\.([^.]+)\.url (.*)$') 242 svn_remote_re = re.compile(r'^svn-remote\.([^.]+)\.url (.*)$')
242 remotes = GIT.Capture( 243 remotes = GIT.Capture(
243 ['config', '--get-regexp', r'^svn-remote\..*\.url'], 244 ['config', '--local', '--get-regexp', r'^svn-remote\..*\.url'],
244 cwd=cwd).splitlines() 245 cwd=cwd).splitlines()
245 for remote in remotes: 246 for remote in remotes:
246 match = svn_remote_re.match(remote) 247 match = svn_remote_re.match(remote)
247 if match: 248 if match:
248 remote = match.group(1) 249 remote = match.group(1)
249 base_url = match.group(2) 250 base_url = match.group(2)
250 try: 251 try:
251 fetch_spec = GIT.Capture( 252 fetch_spec = GIT.Capture(
252 ['config', 'svn-remote.%s.fetch' % remote], 253 ['config', '--local', 'svn-remote.%s.fetch' % remote],
253 cwd=cwd).strip() 254 cwd=cwd)
254 branch = GIT.MatchSvnGlob(url, base_url, fetch_spec, False) 255 branch = GIT.MatchSvnGlob(url, base_url, fetch_spec, False)
255 except subprocess2.CalledProcessError: 256 except subprocess2.CalledProcessError:
256 branch = None 257 branch = None
257 if branch: 258 if branch:
258 return branch 259 return branch
259 try: 260 try:
260 branch_spec = GIT.Capture( 261 branch_spec = GIT.Capture(
261 ['config', 'svn-remote.%s.branches' % remote], 262 ['config', '--local', 'svn-remote.%s.branches' % remote],
262 cwd=cwd).strip() 263 cwd=cwd)
263 branch = GIT.MatchSvnGlob(url, base_url, branch_spec, True) 264 branch = GIT.MatchSvnGlob(url, base_url, branch_spec, True)
264 except subprocess2.CalledProcessError: 265 except subprocess2.CalledProcessError:
265 branch = None 266 branch = None
266 if branch: 267 if branch:
267 return branch 268 return branch
268 try: 269 try:
269 tag_spec = GIT.Capture( 270 tag_spec = GIT.Capture(
270 ['config', 'svn-remote.%s.tags' % remote], 271 ['config', '--local', 'svn-remote.%s.tags' % remote],
271 cwd=cwd).strip() 272 cwd=cwd)
272 branch = GIT.MatchSvnGlob(url, base_url, tag_spec, True) 273 branch = GIT.MatchSvnGlob(url, base_url, tag_spec, True)
273 except subprocess2.CalledProcessError: 274 except subprocess2.CalledProcessError:
274 branch = None 275 branch = None
275 if branch: 276 if branch:
276 return branch 277 return branch
277 278
278 @staticmethod 279 @staticmethod
279 def FetchUpstreamTuple(cwd): 280 def FetchUpstreamTuple(cwd):
280 """Returns a tuple containg remote and remote ref, 281 """Returns a tuple containg remote and remote ref,
281 e.g. 'origin', 'refs/heads/master' 282 e.g. 'origin', 'refs/heads/master'
282 Tries to be intelligent and understand git-svn. 283 Tries to be intelligent and understand git-svn.
283 """ 284 """
284 remote = '.' 285 remote = '.'
285 branch = GIT.GetBranch(cwd) 286 branch = GIT.GetBranch(cwd)
286 try: 287 try:
287 upstream_branch = GIT.Capture( 288 upstream_branch = GIT.Capture(
288 ['config', 'branch.%s.merge' % branch], cwd=cwd).strip() 289 ['config', '--local', 'branch.%s.merge' % branch], cwd=cwd)
289 except subprocess2.CalledProcessError: 290 except subprocess2.CalledProcessError:
290 upstream_branch = None 291 upstream_branch = None
291 if upstream_branch: 292 if upstream_branch:
292 try: 293 try:
293 remote = GIT.Capture( 294 remote = GIT.Capture(
294 ['config', 'branch.%s.remote' % branch], cwd=cwd).strip() 295 ['config', '--local', 'branch.%s.remote' % branch], cwd=cwd)
295 except subprocess2.CalledProcessError: 296 except subprocess2.CalledProcessError:
296 pass 297 pass
297 else: 298 else:
298 try: 299 try:
299 upstream_branch = GIT.Capture( 300 upstream_branch = GIT.Capture(
300 ['config', 'rietveld.upstream-branch'], cwd=cwd).strip() 301 ['config', '--local', 'rietveld.upstream-branch'], cwd=cwd)
301 except subprocess2.CalledProcessError: 302 except subprocess2.CalledProcessError:
302 upstream_branch = None 303 upstream_branch = None
303 if upstream_branch: 304 if upstream_branch:
304 try: 305 try:
305 remote = GIT.Capture( 306 remote = GIT.Capture(
306 ['config', 'rietveld.upstream-remote'], cwd=cwd).strip() 307 ['config', '--local', 'rietveld.upstream-remote'], cwd=cwd)
307 except subprocess2.CalledProcessError: 308 except subprocess2.CalledProcessError:
308 pass 309 pass
309 else: 310 else:
310 # Fall back on trying a git-svn upstream branch. 311 # Fall back on trying a git-svn upstream branch.
311 if GIT.IsGitSvn(cwd): 312 if GIT.IsGitSvn(cwd):
312 upstream_branch = GIT.GetSVNBranch(cwd) 313 upstream_branch = GIT.GetSVNBranch(cwd)
313 else: 314 else:
314 # Else, try to guess the origin remote. 315 # Else, try to guess the origin remote.
315 remote_branches = GIT.Capture(['branch', '-r'], cwd=cwd).split() 316 remote_branches = GIT.Capture(['branch', '-r'], cwd=cwd).split()
316 if 'origin/master' in remote_branches: 317 if 'origin/master' in remote_branches:
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after
367 def GetDifferentFiles(cwd, branch=None, branch_head='HEAD'): 368 def GetDifferentFiles(cwd, branch=None, branch_head='HEAD'):
368 """Returns the list of modified files between two branches.""" 369 """Returns the list of modified files between two branches."""
369 if not branch: 370 if not branch:
370 branch = GIT.GetUpstreamBranch(cwd) 371 branch = GIT.GetUpstreamBranch(cwd)
371 command = ['diff', '--name-only', branch + "..." + branch_head] 372 command = ['diff', '--name-only', branch + "..." + branch_head]
372 return GIT.Capture(command, cwd=cwd).splitlines(False) 373 return GIT.Capture(command, cwd=cwd).splitlines(False)
373 374
374 @staticmethod 375 @staticmethod
375 def GetPatchName(cwd): 376 def GetPatchName(cwd):
376 """Constructs a name for this patch.""" 377 """Constructs a name for this patch."""
377 short_sha = GIT.Capture(['rev-parse', '--short=4', 'HEAD'], cwd=cwd).strip() 378 short_sha = GIT.Capture(['rev-parse', '--short=4', 'HEAD'], cwd=cwd)
378 return "%s#%s" % (GIT.GetBranch(cwd), short_sha) 379 return "%s#%s" % (GIT.GetBranch(cwd), short_sha)
379 380
380 @staticmethod 381 @staticmethod
381 def GetCheckoutRoot(cwd): 382 def GetCheckoutRoot(cwd):
382 """Returns the top level directory of a git checkout as an absolute path. 383 """Returns the top level directory of a git checkout as an absolute path.
383 """ 384 """
384 root = GIT.Capture(['rev-parse', '--show-cdup'], cwd=cwd).strip() 385 root = GIT.Capture(['rev-parse', '--show-cdup'], cwd=cwd)
385 return os.path.abspath(os.path.join(cwd, root)) 386 return os.path.abspath(os.path.join(cwd, root))
386 387
387 @staticmethod 388 @staticmethod
388 def GetGitSvnHeadRev(cwd): 389 def GetGitSvnHeadRev(cwd):
389 """Gets the most recently pulled git-svn revision.""" 390 """Gets the most recently pulled git-svn revision."""
390 try: 391 try:
391 output = GIT.Capture(['svn', 'info'], cwd=cwd) 392 output = GIT.Capture(['svn', 'info'], cwd=cwd)
392 match = re.search(r'^Revision: ([0-9]+)$', output, re.MULTILINE) 393 match = re.search(r'^Revision: ([0-9]+)$', output, re.MULTILINE)
393 return int(match.group(1)) if match else None 394 return int(match.group(1)) if match else None
394 except (subprocess2.CalledProcessError, ValueError): 395 except (subprocess2.CalledProcessError, ValueError):
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
433 if not sha1: 434 if not sha1:
434 return None 435 return None
435 output = GIT.Capture(['rev-list', '-n', '1', '%s^1' % sha1], cwd=cwd) 436 output = GIT.Capture(['rev-list', '-n', '1', '%s^1' % sha1], cwd=cwd)
436 if git_svn_rev != output.rstrip(): 437 if git_svn_rev != output.rstrip():
437 raise gclient_utils.Error(sha1) 438 raise gclient_utils.Error(sha1)
438 return sha1 439 return sha1
439 except subprocess2.CalledProcessError: 440 except subprocess2.CalledProcessError:
440 return None 441 return None
441 442
442 @staticmethod 443 @staticmethod
443 def IsValidRevision(cwd, rev): 444 def IsValidRevision(cwd, rev, sha_only=False):
444 """Verifies the revision is a proper git revision.""" 445 """Verifies the revision is a proper git revision.
446
447 sha_only: Fail unless rev is a sha hash.
448 """
445 # 'git rev-parse foo' where foo is *any* 40 character hex string will return 449 # 'git rev-parse foo' where foo is *any* 40 character hex string will return
446 # the string and return code 0. So strip one character to force 'git 450 # the string and return code 0. So strip one character to force 'git
447 # rev-parse' to do a hash table look-up and returns 128 if the hash is not 451 # rev-parse' to do a hash table look-up and returns 128 if the hash is not
448 # present. 452 # present.
453 lookup_rev = rev
449 if re.match(r'^[0-9a-fA-F]{40}$', rev): 454 if re.match(r'^[0-9a-fA-F]{40}$', rev):
450 rev = rev[:-1] 455 lookup_rev = rev[:-1]
451 try: 456 try:
452 GIT.Capture(['rev-parse', rev], cwd=cwd) 457 sha = GIT.Capture(['rev-parse', lookup_rev], cwd=cwd)
458 if lookup_rev != rev:
459 # Make sure we get the original 40 chars back.
460 return rev == sha
M-A Ruel 2013/07/08 18:44:41 what about lower/upper case? return rev.lower() ==
Isaac (away) 2013/07/08 20:03:34 Good point. It looks like git always returns lowe
461 if sha_only:
462 return sha.startswith(rev)
M-A Ruel 2013/07/08 18:44:41 sha.lower().startswith(rev.lower())
453 return True 463 return True
454 except subprocess2.CalledProcessError: 464 except subprocess2.CalledProcessError:
455 return False 465 return False
456 466
457 @classmethod 467 @classmethod
458 def AssertVersion(cls, min_version): 468 def AssertVersion(cls, min_version):
459 """Asserts git's version is at least min_version.""" 469 """Asserts git's version is at least min_version."""
460 if cls.current_version is None: 470 if cls.current_version is None:
461 current_version = cls.Capture(['--version'], '.') 471 current_version = cls.Capture(['--version'], '.')
462 matched = re.search(r'version ([0-9\.]+)', current_version) 472 matched = re.search(r'version ([0-9\.]+)', current_version)
(...skipping 627 matching lines...) Expand 10 before | Expand all | Expand 10 after
1090 # revert, like for properties. 1100 # revert, like for properties.
1091 if not os.path.isdir(cwd): 1101 if not os.path.isdir(cwd):
1092 # '.' was deleted. It's not worth continuing. 1102 # '.' was deleted. It's not worth continuing.
1093 return 1103 return
1094 try: 1104 try:
1095 SVN.Capture(['revert', file_status[1]], cwd=cwd) 1105 SVN.Capture(['revert', file_status[1]], cwd=cwd)
1096 except subprocess2.CalledProcessError: 1106 except subprocess2.CalledProcessError:
1097 if not os.path.exists(file_path): 1107 if not os.path.exists(file_path):
1098 continue 1108 continue
1099 raise 1109 raise
OLDNEW
« no previous file with comments | « gclient_scm.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698