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

Side by Side Diff: gclient_scm.py

Issue 328843005: Consolidated 'git' refish parsing into a class (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: Removed legacy code; must have been merged back in during a rebase. Created 6 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.py ('k') | tests/gclient_scm_test.py » ('j') | 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 """Gclient-specific SCM-specific operations.""" 5 """Gclient-specific SCM-specific operations."""
6 6
7 from __future__ import print_function 7 from __future__ import print_function
8 8
9 import collections
9 import errno 10 import errno
10 import logging 11 import logging
11 import os 12 import os
12 import posixpath 13 import posixpath
13 import re 14 import re
14 import shlex 15 import shlex
15 import sys 16 import sys
16 import tempfile 17 import tempfile
17 import traceback 18 import traceback
18 import urlparse 19 import urlparse
(...skipping 133 matching lines...) Expand 10 before | Expand all | Expand 10 after
152 153
153 if not command in commands: 154 if not command in commands:
154 raise gclient_utils.Error('Unknown command %s' % command) 155 raise gclient_utils.Error('Unknown command %s' % command)
155 156
156 if not command in dir(self): 157 if not command in dir(self):
157 raise gclient_utils.Error('Command %s not implemented in %s wrapper' % ( 158 raise gclient_utils.Error('Command %s not implemented in %s wrapper' % (
158 command, self.__class__.__name__)) 159 command, self.__class__.__name__))
159 160
160 return getattr(self, command)(options, args, file_list) 161 return getattr(self, command)(options, args, file_list)
161 162
162 def GetActualRemoteURL(self, options): 163 def GetActualRemoteURL(self):
163 """Attempt to determine the remote URL for this SCMWrapper.""" 164 """Attempt to determine the remote URL for this SCMWrapper."""
164 # Git
165 if os.path.exists(os.path.join(self.checkout_path, '.git')): 165 if os.path.exists(os.path.join(self.checkout_path, '.git')):
166 actual_remote_url = shlex.split(scm.GIT.Capture( 166 actual_remote_url = shlex.split(scm.GIT.Capture(
167 ['config', '--local', '--get-regexp', r'remote.*.url'], 167 ['config', '--local', '--get-regexp', r'remote.*.url'],
168 cwd=self.checkout_path))[1] 168 cwd=self.checkout_path))[1]
169 169
170 # If a cache_dir is used, obtain the actual remote URL from the cache. 170 # If a cache_dir is used, obtain the actual remote URL from the cache.
171 if getattr(self, 'cache_dir', None): 171 if getattr(self, 'cache_dir', None):
172 url, _ = gclient_utils.SplitUrlRevision(self.url) 172 url, _ = gclient_utils.SplitUrlRevision(self.url)
173 mirror = git_cache.Mirror(url) 173 mirror = git_cache.Mirror(url)
174 if (mirror.exists() and mirror.mirror_path.replace('\\', '/') == 174 if (mirror.exists() and mirror.mirror_path.replace('\\', '/') ==
175 actual_remote_url.replace('\\', '/')): 175 actual_remote_url.replace('\\', '/')):
176 actual_remote_url = shlex.split(scm.GIT.Capture( 176 actual_remote_url = shlex.split(scm.GIT.Capture(
177 ['config', '--local', '--get-regexp', r'remote.*.url'], 177 ['config', '--local', '--get-regexp', r'remote.*.url'],
178 cwd=mirror.mirror_path))[1] 178 cwd=mirror.mirror_path))[1]
179 return actual_remote_url 179 return actual_remote_url
180 180
181 # Svn 181 # Svn
182 if os.path.exists(os.path.join(self.checkout_path, '.svn')): 182 if os.path.exists(os.path.join(self.checkout_path, '.svn')):
183 return scm.SVN.CaptureLocalInfo([], self.checkout_path)['URL'] 183 return scm.SVN.CaptureLocalInfo([], self.checkout_path)['URL']
184 return None 184 return None
185 185
186 def DoesRemoteURLMatch(self, options): 186 def DoesRemoteURLMatch(self, options):
187 """Determine whether the remote URL of this checkout is the expected URL.""" 187 """Determine whether the remote URL of this checkout is the expected URL."""
188 if not os.path.exists(self.checkout_path): 188 if not os.path.exists(self.checkout_path):
189 # A checkout which doesn't exist can't be broken. 189 # A checkout which doesn't exist can't be broken.
190 return True 190 return True
191 191
192 actual_remote_url = self.GetActualRemoteURL(options) 192 actual_remote_url = self.GetActualRemoteURL()
193 if actual_remote_url: 193 if actual_remote_url:
194 return (gclient_utils.SplitUrlRevision(actual_remote_url)[0].rstrip('/') 194 return (gclient_utils.SplitUrlRevision(actual_remote_url)[0].rstrip('/')
195 == gclient_utils.SplitUrlRevision(self.url)[0].rstrip('/')) 195 == gclient_utils.SplitUrlRevision(self.url)[0].rstrip('/'))
196 else: 196 else:
197 # This may occur if the self.checkout_path exists but does not contain a 197 # This may occur if the self.checkout_path exists but does not contain a
198 # valid git or svn checkout. 198 # valid git or svn checkout.
199 return False 199 return False
200 200
201 def _DeleteOrMove(self, force): 201 def _DeleteOrMove(self, force):
202 """Delete the checkout directory or move it out of the way. 202 """Delete the checkout directory or move it out of the way.
(...skipping 20 matching lines...) Expand all
223 dest_path = tempfile.mkdtemp( 223 dest_path = tempfile.mkdtemp(
224 prefix=os.path.basename(self.relpath), 224 prefix=os.path.basename(self.relpath),
225 dir=bad_scm_dir) 225 dir=bad_scm_dir)
226 self.Print('_____ Conflicting directory found in %s. Moving to %s.' 226 self.Print('_____ Conflicting directory found in %s. Moving to %s.'
227 % (self.checkout_path, dest_path)) 227 % (self.checkout_path, dest_path))
228 gclient_utils.AddWarning('Conflicting directory %s moved to %s.' 228 gclient_utils.AddWarning('Conflicting directory %s moved to %s.'
229 % (self.checkout_path, dest_path)) 229 % (self.checkout_path, dest_path))
230 shutil.move(self.checkout_path, dest_path) 230 shutil.move(self.checkout_path, dest_path)
231 231
232 232
233 _GitRefishBase = collections.namedtuple('GitRef', (
234 # The initial string that the parsed Refish came from
235 'source',
236 # Is this ref a branch?
237 'is_branch',
238 # The name of this ref when accessed through the local repository
239 'local_ref',
240 # The name of the remote that this refish resides on
241 'remote',
242 # The path of this refish on the remote (e.g., 'master')
243 'remote_ref',
244 # The remote-qualified path to this refish (e.g., 'origin/master')
245 'remote_refspec',
246 # If a branch, the expected name of the upstream branch that it should
247 # track; otherwise, 'None'
248 'upstream_branch',
249 ))
250
251
252 class GitRefish(_GitRefishBase):
253 # Disable 'no __init__' warning | pylint: disable=W0232
254 """Implements refish parsing and properties.
255
256 This class is designed to concentrate and codify the assumptions made by
257 'gclient' code about refs and revisions.
258 """
259
260 _DEFAULT_REMOTE = 'origin'
261
262 _GIT_SHA1_RE = re.compile('[0-9a-f]{40}', re.IGNORECASE)
263 _GIT_SHA1_SHORT_RE = re.compile('[0-9a-f]{4,40}', re.IGNORECASE)
264
265 def __str__(self):
266 return self.source
267
268 @classmethod
269 def Parse(cls, ref, remote=None, other_remotes=None):
270 """Parses a ref into a 'GitRefish' instance.
271
272 Args:
273 ref: (str) The refish (branch, tag, hash, etc.) to parse
274 remote: (None/str) If supplied, the name of the primary remote. In
275 addtion to being recognized as a remote string, the primary remote
276 will also be used (as needed) when generating the remote refspec. If
277 omitted, the default remote name ('origin') will be used.
278 other_remotes: (None/iterable) If supplied, a list of strings to
279 recognize as remotes in addition to 'remote'.
280 """
281 assert ref == ref.strip()
282
283 # Use default set of remotes
284 remote = remote or cls._DEFAULT_REMOTE
285 remotes = {remote}
286 if other_remotes:
287 remotes.update(other_remotes)
288
289 # Treat 'ref' as a '/'-delimited set of items; analyze their contents
290 ref_split = local_ref = remote_ref = tuple(ref.split('/'))
291 is_branch = True
292 if len(ref_split) > 1:
293 if ref_split[0] == 'refs':
294 if len(ref_split) >= 3 and ref_split[1] == 'heads':
295 # refs/heads/foo/bar
296 #
297 # Local Ref: foo/bar
298 # Remote Ref: foo/bar
299 local_ref = remote_ref = ref_split[2:]
300 elif len(ref_split) >= 4 and ref_split[1] == 'remotes':
301 # refs/remotes/<REMOTE>/foo/bar
302 # This is a bad assumption, and this logic can be improved, but it
303 # is consistent with traditional 'gclient' logic.
304 #
305 # Local Ref: refs/remotes/<REMOTE>/foo/bar
306 # Remote: <REMOTE>
307 # Remote Ref: foo/bar
308 remote = ref_split[2]
309 remote_ref = ref_split[3:]
310 elif len(ref_split) >= 2 and ref_split[0] in remotes:
311 # origin/master
312 #
313 # Local Ref: refs/remotes/origin/master
314 # Remote Ref: master
315 remote = ref_split[0]
316 remote_ref = ref_split[1:]
317 local_ref = ('refs', 'remotes') + ref_split
318
319 # (Default) The refish has multiple paths. Assume at least that it's a
320 # branch name.
321 #
322 # foo/bar
323 # refs/foo/bar
324 #
325 # Local Ref: foo/bar
326 # Remote ref: foo/bar
327 else:
328 # It could be a hash, a short-hash, or a tag
329 is_branch = False
330
331 # Determine how the ref should be referenced remotely
332 if is_branch:
333 # foo/bar/baz => origin/foo/bar/baz
334 remote_refspec = (remote,) + remote_ref
335 else:
336 # Refer to the hash/tag directly. This is actually not allowed in
337 # 'fetch', although it oftentimes works regardless.
338 #
339 # <HASH/TAG> => <HASH/TAG>
340 remote_refspec = (ref,)
341
342 # Calculate the upstream branch
343 if is_branch:
344 # Convert '/refs/heads/...' to 'refs/remotes/REMOTE/...'
345 if ref_split[:2] == ('refs', 'heads'):
346 upstream_branch = ('refs', 'remotes', remote) + ref_split[2:]
347 else:
348 upstream_branch = ref_split
349 else:
350 upstream_branch = None
351
352 def compose(ref_tuple):
353 return '/'.join(ref_tuple) if ref_tuple else ref_tuple
354
355 return cls(
356 source=ref,
357 is_branch=is_branch,
358 local_ref=compose(local_ref),
359 remote=remote,
360 remote_ref=compose(remote_ref),
361 remote_refspec=compose(remote_refspec),
362 upstream_branch=compose(upstream_branch),
363 )
364
365
233 class GitWrapper(SCMWrapper): 366 class GitWrapper(SCMWrapper):
234 """Wrapper for Git""" 367 """Wrapper for Git"""
235 name = 'git' 368 name = 'git'
236 remote = 'origin' 369 remote = 'origin'
237 370
238 cache_dir = None 371 cache_dir = None
239 372
240 def __init__(self, url=None, *args): 373 def __init__(self, url=None, *args):
241 """Removes 'git+' fake prefix from git URL.""" 374 """Removes 'git+' fake prefix from git URL."""
242 if url.startswith('git+http://') or url.startswith('git+https://'): 375 if url.startswith('git+http://') or url.startswith('git+https://'):
243 url = url[4:] 376 url = url[4:]
244 SCMWrapper.__init__(self, url, *args) 377 SCMWrapper.__init__(self, url, *args)
245 filter_kwargs = { 'time_throttle': 1, 'out_fh': self.out_fh } 378 filter_kwargs = { 'time_throttle': 1, 'out_fh': self.out_fh }
246 if self.out_cb: 379 if self.out_cb:
247 filter_kwargs['predicate'] = self.out_cb 380 filter_kwargs['predicate'] = self.out_cb
248 self.filter = gclient_utils.GitFilter(**filter_kwargs) 381 self.filter = gclient_utils.GitFilter(**filter_kwargs)
249 382
250 @staticmethod 383 @staticmethod
251 def BinaryExists(): 384 def BinaryExists():
252 """Returns true if the command exists.""" 385 """Returns true if the command exists."""
253 try: 386 try:
254 # We assume git is newer than 1.7. See: crbug.com/114483 387 # We assume git is newer than 1.7. See: crbug.com/114483
255 result, version = scm.GIT.AssertVersion('1.7') 388 result, version = scm.GIT.AssertVersion('1.7')
256 if not result: 389 if not result:
257 raise gclient_utils.Error('Git version is older than 1.7: %s' % version) 390 raise gclient_utils.Error('Git version is older than 1.7: %s' % version)
258 return result 391 return result
259 except OSError: 392 except OSError:
260 return False 393 return False
261 394
395 def ParseRefish(self, value, **kwargs):
396 kwargs.setdefault('remote', self.remote)
397 return GitRefish.Parse(value, **kwargs)
398
262 def GetCheckoutRoot(self): 399 def GetCheckoutRoot(self):
263 return scm.GIT.GetCheckoutRoot(self.checkout_path) 400 return scm.GIT.GetCheckoutRoot(self.checkout_path)
264 401
265 def GetRevisionDate(self, _revision): 402 def GetRevisionDate(self, _revision):
266 """Returns the given revision's date in ISO-8601 format (which contains the 403 """Returns the given revision's date in ISO-8601 format (which contains the
267 time zone).""" 404 time zone)."""
268 # TODO(floitsch): get the time-stamp of the given revision and not just the 405 # TODO(floitsch): get the time-stamp of the given revision and not just the
269 # time-stamp of the currently checked out revision. 406 # time-stamp of the currently checked out revision.
270 return self._Capture(['log', '-n', '1', '--format=%ai']) 407 return self._Capture(['log', '-n', '1', '--format=%ai'])
271 408
(...skipping 14 matching lines...) Expand all
286 423
287 The patch file is generated from a diff of the merge base of HEAD and 424 The patch file is generated from a diff of the merge base of HEAD and
288 its upstream branch. 425 its upstream branch.
289 """ 426 """
290 merge_base = self._Capture(['merge-base', 'HEAD', self.remote]) 427 merge_base = self._Capture(['merge-base', 'HEAD', self.remote])
291 gclient_utils.CheckCallAndFilter( 428 gclient_utils.CheckCallAndFilter(
292 ['git', 'diff', merge_base], 429 ['git', 'diff', merge_base],
293 cwd=self.checkout_path, 430 cwd=self.checkout_path,
294 filter_fn=GitDiffFilterer(self.relpath).Filter, print_func=self.Print) 431 filter_fn=GitDiffFilterer(self.relpath).Filter, print_func=self.Print)
295 432
296 def _FetchAndReset(self, revision, file_list, options): 433 def _FetchAndReset(self, refish, file_list, options):
297 """Equivalent to git fetch; git reset.""" 434 """Equivalent to git fetch; git reset."""
298 quiet = [] 435 quiet = []
299 if not options.verbose: 436 if not options.verbose:
300 quiet = ['--quiet'] 437 quiet = ['--quiet']
301 self._UpdateBranchHeads(options, fetch=False) 438 self._UpdateBranchHeads(options, fetch=False)
302 439
303 self._Fetch(options, prune=True, quiet=options.verbose) 440 self._Fetch(options, prune=True, quiet=options.verbose)
304 self._Run(['reset', '--hard', revision] + quiet, options) 441 self._Run(['reset', '--hard', refish.local_ref] + quiet, options)
305 if file_list is not None: 442 if file_list is not None:
306 files = self._Capture(['ls-files']).splitlines() 443 files = self._Capture(['ls-files']).splitlines()
307 file_list.extend([os.path.join(self.checkout_path, f) for f in files]) 444 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
308 445
309 def _DisableHooks(self): 446 def _DisableHooks(self):
310 hook_dir = os.path.join(self.checkout_path, '.git', 'hooks') 447 hook_dir = os.path.join(self.checkout_path, '.git', 'hooks')
311 if not os.path.isdir(hook_dir): 448 if not os.path.isdir(hook_dir):
312 return 449 return
313 for f in os.listdir(hook_dir): 450 for f in os.listdir(hook_dir):
314 if not f.endswith('.sample') and not f.endswith('.disabled'): 451 if not f.endswith('.sample') and not f.endswith('.disabled'):
315 os.rename(os.path.join(hook_dir, f), 452 os.rename(os.path.join(hook_dir, f),
316 os.path.join(hook_dir, f + '.disabled')) 453 os.path.join(hook_dir, f + '.disabled'))
317 454
318 def update(self, options, args, file_list): 455 def update(self, options, args, file_list):
319 """Runs git to update or transparently checkout the working copy. 456 """Runs git to update or transparently checkout the working copy.
320 457
321 All updated files will be appended to file_list. 458 All updated files will be appended to file_list.
322 459
323 Raises: 460 Raises:
324 Error: if can't get URL for relative path. 461 Error: if can't get URL for relative path.
325 """ 462 """
326 if args: 463 if args:
327 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args)) 464 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args))
328 465
329 self._CheckMinVersion("1.6.6") 466 self._CheckMinVersion("1.6.6")
330 467
331 # If a dependency is not pinned, track the default remote branch. 468 # If a dependency is not pinned, track the default remote branch.
332 default_rev = 'refs/remotes/%s/master' % self.remote 469 default_rev = 'refs/remotes/%s/master' % (self.remote,)
333 url, deps_revision = gclient_utils.SplitUrlRevision(self.url) 470 url, deps_revision = gclient_utils.SplitUrlRevision(self.url)
334 rev_str = "" 471 rev_str = ""
335 revision = deps_revision 472 revision = deps_revision
336 managed = True 473 managed = True
337 if options.revision: 474 if options.revision:
338 # Override the revision number. 475 # Override the revision number.
339 revision = str(options.revision) 476 revision = str(options.revision)
340 if revision == 'unmanaged': 477 if revision == 'unmanaged':
341 # Check again for a revision in case an initial ref was specified 478 # Check again for a revision in case an initial ref was specified
342 # in the url, for example bla.git@refs/heads/custombranch 479 # in the url, for example bla.git@refs/heads/custombranch
(...skipping 16 matching lines...) Expand all
359 rev_str = ' at %s' % revision 496 rev_str = ' at %s' % revision
360 files = [] if file_list is not None else None 497 files = [] if file_list is not None else None
361 498
362 printed_path = False 499 printed_path = False
363 verbose = [] 500 verbose = []
364 if options.verbose: 501 if options.verbose:
365 self.Print('_____ %s%s' % (self.relpath, rev_str), timestamp=False) 502 self.Print('_____ %s%s' % (self.relpath, rev_str), timestamp=False)
366 verbose = ['--verbose'] 503 verbose = ['--verbose']
367 printed_path = True 504 printed_path = True
368 505
369 if revision.startswith('refs/'): 506 refish = self.ParseRefish(revision)
370 rev_type = "branch"
371 elif revision.startswith(self.remote + '/'):
372 # Rewrite remote refs to their local equivalents.
373 revision = 'refs/remotes/' + revision
374 rev_type = "branch"
375 else:
376 # hash is also a tag, only make a distinction at checkout
377 rev_type = "hash"
378
379 mirror = self._GetMirror(url, options) 507 mirror = self._GetMirror(url, options)
380 if mirror: 508 if mirror:
381 url = mirror.mirror_path 509 url = mirror.mirror_path
382 510
383 if (not os.path.exists(self.checkout_path) or 511 if (not os.path.exists(self.checkout_path) or
384 (os.path.isdir(self.checkout_path) and 512 (os.path.isdir(self.checkout_path) and
385 not os.path.exists(os.path.join(self.checkout_path, '.git')))): 513 not os.path.exists(os.path.join(self.checkout_path, '.git')))):
386 if mirror: 514 if mirror:
387 self._UpdateMirror(mirror, options) 515 self._UpdateMirror(mirror, options)
388 try: 516 try:
389 self._Clone(revision, url, options) 517 self._Clone(refish.local_ref, url, options)
390 except subprocess2.CalledProcessError: 518 except subprocess2.CalledProcessError:
391 self._DeleteOrMove(options.force) 519 self._DeleteOrMove(options.force)
392 self._Clone(revision, url, options) 520 self._Clone(refish.local_ref, url, options)
393 if deps_revision and deps_revision.startswith('branch-heads/'): 521 if deps_revision and deps_revision.startswith('branch-heads/'):
394 deps_branch = deps_revision.replace('branch-heads/', '') 522 deps_branch = deps_revision.replace('branch-heads/', '')
395 self._Capture(['branch', deps_branch, deps_revision]) 523 self._Capture(['branch', deps_branch, deps_revision])
396 self._Checkout(options, deps_branch, quiet=True) 524 self._Checkout(options, deps_branch, quiet=True)
397 if file_list is not None: 525 if file_list is not None:
398 files = self._Capture(['ls-files']).splitlines() 526 files = self._Capture(['ls-files']).splitlines()
399 file_list.extend([os.path.join(self.checkout_path, f) for f in files]) 527 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
400 if not verbose: 528 if not verbose:
401 # Make the output a little prettier. It's nice to have some whitespace 529 # Make the output a little prettier. It's nice to have some whitespace
402 # between projects when cloning. 530 # between projects when cloning.
(...skipping 20 matching lines...) Expand all
423 if (current_url.rstrip('/') != url.rstrip('/') and 551 if (current_url.rstrip('/') != url.rstrip('/') and
424 url != 'git://foo' and 552 url != 'git://foo' and
425 subprocess2.capture( 553 subprocess2.capture(
426 ['git', 'config', 'remote.%s.gclient-auto-fix-url' % self.remote], 554 ['git', 'config', 'remote.%s.gclient-auto-fix-url' % self.remote],
427 cwd=self.checkout_path).strip() != 'False'): 555 cwd=self.checkout_path).strip() != 'False'):
428 self.Print('_____ switching %s to a new upstream' % self.relpath) 556 self.Print('_____ switching %s to a new upstream' % self.relpath)
429 # Make sure it's clean 557 # Make sure it's clean
430 self._CheckClean(rev_str) 558 self._CheckClean(rev_str)
431 # Switch over to the new upstream 559 # Switch over to the new upstream
432 self._Run(['remote', 'set-url', self.remote, url], options) 560 self._Run(['remote', 'set-url', self.remote, url], options)
433 self._FetchAndReset(revision, file_list, options) 561 self._FetchAndReset(refish, file_list, options)
434 return_early = True 562 return_early = True
435 563
436 if return_early: 564 if return_early:
437 return self._Capture(['rev-parse', '--verify', 'HEAD']) 565 return self._Capture(['rev-parse', '--verify', 'HEAD'])
438 566
439 cur_branch = self._GetCurrentBranch() 567 cur_branch = self._GetCurrentBranch()
440 568
441 # Cases: 569 # Cases:
442 # 0) HEAD is detached. Probably from our initial clone. 570 # 0) HEAD is detached. Probably from our initial clone.
443 # - make sure HEAD is contained by a named ref, then update. 571 # - make sure HEAD is contained by a named ref, then update.
(...skipping 22 matching lines...) Expand all
466 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path) 594 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
467 if not upstream_branch or not upstream_branch.startswith('refs/remotes'): 595 if not upstream_branch or not upstream_branch.startswith('refs/remotes'):
468 current_type = "hash" 596 current_type = "hash"
469 logging.debug("Current branch is not tracking an upstream (remote)" 597 logging.debug("Current branch is not tracking an upstream (remote)"
470 " branch.") 598 " branch.")
471 elif upstream_branch.startswith('refs/remotes'): 599 elif upstream_branch.startswith('refs/remotes'):
472 current_type = "branch" 600 current_type = "branch"
473 else: 601 else:
474 raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch) 602 raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch)
475 603
476 if not scm.GIT.IsValidRevision(self.checkout_path, revision, sha_only=True): 604 if not scm.GIT.IsValidRevision(self.checkout_path, refish.local_ref,
605 sha_only=True):
477 # Update the remotes first so we have all the refs. 606 # Update the remotes first so we have all the refs.
478 remote_output = scm.GIT.Capture(['remote'] + verbose + ['update'], 607 remote_output = scm.GIT.Capture(['remote'] + verbose + ['update'],
479 cwd=self.checkout_path) 608 cwd=self.checkout_path)
480 if verbose: 609 if verbose:
481 self.Print(remote_output) 610 self.Print(remote_output)
482 611
483 self._UpdateBranchHeads(options, fetch=True) 612 self._UpdateBranchHeads(options, fetch=True)
484 613
485 # This is a big hammer, debatable if it should even be here... 614 # This is a big hammer, debatable if it should even be here...
486 if options.force or options.reset: 615 if options.force or options.reset:
487 target = 'HEAD' 616 target = 'HEAD'
488 if options.upstream and upstream_branch: 617 if options.upstream and upstream_branch:
489 target = upstream_branch 618 target = upstream_branch
490 self._Run(['reset', '--hard', target], options) 619 self._Run(['reset', '--hard', target], options)
491 620
492 if current_type == 'detached': 621 if current_type == 'detached':
493 # case 0 622 # case 0
494 self._CheckClean(rev_str) 623 self._CheckClean(rev_str)
495 self._CheckDetachedHead(rev_str, options) 624 self._CheckDetachedHead(rev_str, options)
496 if self._Capture(['rev-list', '-n', '1', 'HEAD']) == revision: 625 if self._Capture(['rev-list', '-n', '1', 'HEAD']) == refish.local_ref:
497 self.Print('Up-to-date; skipping checkout.') 626 self.Print('Up-to-date; skipping checkout.')
498 else: 627 else:
499 # 'git checkout' may need to overwrite existing untracked files. Allow 628 # 'git checkout' may need to overwrite existing untracked files. Allow
500 # it only when nuclear options are enabled. 629 # it only when nuclear options are enabled.
501 self._Checkout( 630 self._Checkout(
502 options, 631 options,
503 revision, 632 revision,
504 force=(options.force and options.delete_unversioned_trees), 633 force=(options.force and options.delete_unversioned_trees),
505 quiet=True, 634 quiet=True,
506 ) 635 )
507 if not printed_path: 636 if not printed_path:
508 self.Print('_____ %s%s' % (self.relpath, rev_str), timestamp=False) 637 self.Print('_____ %s%s' % (self.relpath, rev_str), timestamp=False)
509 elif current_type == 'hash': 638 elif current_type == 'hash':
510 # case 1 639 # case 1
511 if scm.GIT.IsGitSvn(self.checkout_path) and upstream_branch is not None: 640 if scm.GIT.IsGitSvn(self.checkout_path) and upstream_branch is not None:
512 # Our git-svn branch (upstream_branch) is our upstream 641 # Our git-svn branch (upstream_branch) is our upstream
513 self._AttemptRebase(upstream_branch, files, options, 642 self._AttemptRebase(upstream_branch, files, options,
514 newbase=revision, printed_path=printed_path, 643 newbase=refish.local_ref,
515 merge=options.merge) 644 printed_path=printed_path, merge=options.merge)
516 printed_path = True 645 printed_path = True
517 else: 646 else:
518 # Can't find a merge-base since we don't know our upstream. That makes 647 # Can't find a merge-base since we don't know our upstream. That makes
519 # this command VERY likely to produce a rebase failure. For now we 648 # this command VERY likely to produce a rebase failure. For now we
520 # assume origin is our upstream since that's what the old behavior was. 649 # assume origin is our upstream since that's what the old behavior was.
521 upstream_branch = self.remote 650 upstream_branch = self.remote
522 if options.revision or deps_revision: 651 if options.revision or deps_revision:
523 upstream_branch = revision 652 upstream_branch = refish.local_ref
524 self._AttemptRebase(upstream_branch, files, options, 653 self._AttemptRebase(upstream_branch, files, options,
525 printed_path=printed_path, merge=options.merge) 654 printed_path=printed_path, merge=options.merge)
526 printed_path = True 655 printed_path = True
527 elif rev_type == 'hash': 656 elif not refish.is_branch:
528 # case 2 657 # case 2
529 self._AttemptRebase(upstream_branch, files, options, 658 self._AttemptRebase(upstream_branch, files, options,
530 newbase=revision, printed_path=printed_path, 659 newbase=refish.local_ref, printed_path=printed_path,
531 merge=options.merge) 660 merge=options.merge)
532 printed_path = True 661 printed_path = True
533 elif revision.replace('heads', 'remotes/' + self.remote) != upstream_branch: 662 elif refish.upstream_branch != upstream_branch:
534 # case 4 663 # case 4
535 new_base = revision.replace('heads', 'remotes/' + self.remote) 664 new_base = refish.upstream_branch
536 if not printed_path: 665 if not printed_path:
537 self.Print('_____ %s%s' % (self.relpath, rev_str), timestamp=False) 666 self.Print('_____ %s%s' % (self.relpath, rev_str), timestamp=False)
538 switch_error = ("Switching upstream branch from %s to %s\n" 667 switch_error = ("Switching upstream branch from %s to %s\n"
539 % (upstream_branch, new_base) + 668 % (upstream_branch, new_base) +
540 "Please merge or rebase manually:\n" + 669 "Please merge or rebase manually:\n" +
541 "cd %s; git rebase %s\n" % (self.checkout_path, new_base) + 670 "cd %s; git rebase %s\n" % (self.checkout_path, new_base) +
542 "OR git checkout -b <some new branch> %s" % new_base) 671 "OR git checkout -b <some new branch> %s" % new_base)
543 raise gclient_utils.Error(switch_error) 672 raise gclient_utils.Error(switch_error)
544 else: 673 else:
545 # case 3 - the default case 674 # case 3 - the default case
(...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after
656 return self.update(options, [], file_list) 785 return self.update(options, [], file_list)
657 786
658 default_rev = "refs/heads/master" 787 default_rev = "refs/heads/master"
659 if options.upstream: 788 if options.upstream:
660 if self._GetCurrentBranch(): 789 if self._GetCurrentBranch():
661 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path) 790 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
662 default_rev = upstream_branch or default_rev 791 default_rev = upstream_branch or default_rev
663 _, deps_revision = gclient_utils.SplitUrlRevision(self.url) 792 _, deps_revision = gclient_utils.SplitUrlRevision(self.url)
664 if not deps_revision: 793 if not deps_revision:
665 deps_revision = default_rev 794 deps_revision = default_rev
666 if deps_revision.startswith('refs/heads/'): 795 refish = self.ParseRefish(deps_revision)
667 deps_revision = deps_revision.replace('refs/heads/', self.remote + '/') 796 deps_revision = self.GetUsableRev(refish.remote_refspec, options)
668 deps_revision = self.GetUsableRev(deps_revision, options)
669 797
670 if file_list is not None: 798 if file_list is not None:
671 files = self._Capture(['diff', deps_revision, '--name-only']).split() 799 files = self._Capture(['diff', deps_revision, '--name-only']).split()
672 800
673 self._Run(['reset', '--hard', deps_revision], options) 801 self._Run(['reset', '--hard', deps_revision], options)
674 self._Run(['clean', '-f', '-d'], options) 802 self._Run(['clean', '-f', '-d'], options)
675 803
676 if file_list is not None: 804 if file_list is not None:
677 file_list.extend([os.path.join(self.checkout_path, f) for f in files]) 805 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
678 806
(...skipping 156 matching lines...) Expand 10 before | Expand all | Expand 10 after
835 gclient_utils.safe_rename(os.path.join(tmp_dir, '.git'), 963 gclient_utils.safe_rename(os.path.join(tmp_dir, '.git'),
836 os.path.join(self.checkout_path, '.git')) 964 os.path.join(self.checkout_path, '.git'))
837 except: 965 except:
838 traceback.print_exc(file=self.out_fh) 966 traceback.print_exc(file=self.out_fh)
839 raise 967 raise
840 finally: 968 finally:
841 if os.listdir(tmp_dir): 969 if os.listdir(tmp_dir):
842 self.Print('_____ removing non-empty tmp dir %s' % tmp_dir) 970 self.Print('_____ removing non-empty tmp dir %s' % tmp_dir)
843 gclient_utils.rmtree(tmp_dir) 971 gclient_utils.rmtree(tmp_dir)
844 self._UpdateBranchHeads(options, fetch=True) 972 self._UpdateBranchHeads(options, fetch=True)
845 self._Checkout(options, revision.replace('refs/heads/', ''), quiet=True) 973
974 refish = self.ParseRefish(revision)
975 self._Checkout(options, refish.local_ref, quiet=True)
846 if self._GetCurrentBranch() is None: 976 if self._GetCurrentBranch() is None:
847 # Squelch git's very verbose detached HEAD warning and use our own 977 # Squelch git's very verbose detached HEAD warning and use our own
848 self.Print( 978 self.Print(
849 ('Checked out %s to a detached HEAD. Before making any commits\n' 979 "Checked out %s to a detached HEAD. Before making any commits\n"
850 'in this repo, you should use \'git checkout <branch>\' to switch to\n' 980 "in this repo, you should use 'git checkout <branch>' to switch to\n"
851 'an existing branch or use \'git checkout %s -b <branch>\' to\n' 981 "an existing branch or use 'git checkout %s -b <branch>' to\n"
852 'create a new branch for your work.') % (revision, self.remote)) 982 "create a new branch for your work." % (revision, self.remote))
853 983
854 def _AskForData(self, prompt, options): 984 def _AskForData(self, prompt, options):
855 if options.jobs > 1: 985 if options.jobs > 1:
856 self.Print(prompt) 986 self.Print(prompt)
857 raise gclient_utils.Error("Background task requires input. Rerun " 987 raise gclient_utils.Error("Background task requires input. Rerun "
858 "gclient with --jobs=1 so that\n" 988 "gclient with --jobs=1 so that\n"
859 "interaction is possible.") 989 "interaction is possible.")
860 try: 990 try:
861 return raw_input(prompt) 991 return raw_input(prompt)
862 except KeyboardInterrupt: 992 except KeyboardInterrupt:
(...skipping 676 matching lines...) Expand 10 before | Expand all | Expand 10 after
1539 new_command.append('--force') 1669 new_command.append('--force')
1540 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: 1670 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]:
1541 new_command.extend(('--accept', 'theirs-conflict')) 1671 new_command.extend(('--accept', 'theirs-conflict'))
1542 elif options.manually_grab_svn_rev: 1672 elif options.manually_grab_svn_rev:
1543 new_command.append('--force') 1673 new_command.append('--force')
1544 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: 1674 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]:
1545 new_command.extend(('--accept', 'postpone')) 1675 new_command.extend(('--accept', 'postpone'))
1546 elif command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: 1676 elif command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]:
1547 new_command.extend(('--accept', 'postpone')) 1677 new_command.extend(('--accept', 'postpone'))
1548 return new_command 1678 return new_command
OLDNEW
« no previous file with comments | « gclient.py ('k') | tests/gclient_scm_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698