OLD | NEW |
---|---|
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 |
OLD | NEW |