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

Side by Side Diff: gclient_scm.py

Issue 2448303002: Rewrite git gclient sync/update to be simpler and more reliable (Closed)
Patch Set: Created 4 years, 1 month 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
« 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 # 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 errno 9 import errno
10 import logging 10 import logging
(...skipping 287 matching lines...) Expand 10 before | Expand all | Expand 10 after
298 gclient_utils.CheckCallAndFilter( 298 gclient_utils.CheckCallAndFilter(
299 ['git', 'diff'] + merge_base, 299 ['git', 'diff'] + merge_base,
300 cwd=self.checkout_path, 300 cwd=self.checkout_path,
301 filter_fn=GitDiffFilterer(self.relpath, print_func=self.Print).Filter) 301 filter_fn=GitDiffFilterer(self.relpath, print_func=self.Print).Filter)
302 302
303 def _FetchAndReset(self, revision, file_list, options): 303 def _FetchAndReset(self, revision, file_list, options):
304 """Equivalent to git fetch; git reset.""" 304 """Equivalent to git fetch; git reset."""
305 quiet = [] 305 quiet = []
306 if not options.verbose: 306 if not options.verbose:
307 quiet = ['--quiet'] 307 quiet = ['--quiet']
308 self._UpdateBranchHeads(options, fetch=False) 308 self._ConfigBranchHeads(options)
309 309
310 self._Fetch(options, prune=True, quiet=options.verbose) 310 self._Fetch(options, prune=True, quiet=options.verbose)
311 self._Run(['reset', '--hard', revision] + quiet, options) 311 self._Run(['reset', '--hard', revision] + quiet, options)
312 if file_list is not None: 312 if file_list is not None:
313 files = self._Capture(['ls-files']).splitlines() 313 files = self._Capture(['ls-files']).splitlines()
314 file_list.extend([os.path.join(self.checkout_path, f) for f in files]) 314 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
315 315
316 def _DisableHooks(self): 316 def _DisableHooks(self):
317 hook_dir = os.path.join(self.checkout_path, '.git', 'hooks') 317 hook_dir = os.path.join(self.checkout_path, '.git', 'hooks')
318 if not os.path.isdir(hook_dir): 318 if not os.path.isdir(hook_dir):
319 return 319 return
320 for f in os.listdir(hook_dir): 320 for f in os.listdir(hook_dir):
321 if not f.endswith('.sample') and not f.endswith('.disabled'): 321 if not f.endswith('.sample') and not f.endswith('.disabled'):
322 disabled_hook_path = os.path.join(hook_dir, f + '.disabled') 322 disabled_hook_path = os.path.join(hook_dir, f + '.disabled')
323 if os.path.exists(disabled_hook_path): 323 if os.path.exists(disabled_hook_path):
324 os.remove(disabled_hook_path) 324 os.remove(disabled_hook_path)
325 os.rename(os.path.join(hook_dir, f), disabled_hook_path) 325 os.rename(os.path.join(hook_dir, f), disabled_hook_path)
326 326
327 def _maybe_break_locks(self, options): 327 def _MaybeBreakLocks(self, options):
328 """This removes all .lock files from this repo's .git directory, if the 328 """This removes all .lock files from this repo's .git directory, if the
329 user passed the --break_repo_locks command line flag. 329 user passed the --break_repo_locks command line flag.
330 330
331 In particular, this will cleanup index.lock files, as well as ref lock 331 In particular, this will cleanup index.lock files, as well as ref lock
332 files. 332 files.
333 """ 333 """
334 if options.break_repo_locks: 334 if options.break_repo_locks:
335 git_dir = os.path.join(self.checkout_path, '.git') 335 git_dir = os.path.join(self.checkout_path, '.git')
336 for path, _, filenames in os.walk(git_dir): 336 for path, _, filenames in os.walk(git_dir):
337 for filename in filenames: 337 for filename in filenames:
338 if filename.endswith('.lock'): 338 if filename.endswith('.lock'):
339 to_break = os.path.join(path, filename) 339 to_break = os.path.join(path, filename)
340 self.Print('breaking lock: %s' % (to_break,)) 340 self.Print('breaking lock: %s' % (to_break,))
341 try: 341 try:
342 os.remove(to_break) 342 os.remove(to_break)
343 except OSError as ex: 343 except OSError as ex:
344 self.Print('FAILED to break lock: %s: %s' % (to_break, ex)) 344 self.Print('FAILED to break lock: %s: %s' % (to_break, ex))
345 raise 345 raise
346 346
347 347
348 def update(self, options, args, file_list): 348 def update(self, options, args, file_list):
349 """Runs git to update or transparently checkout the working copy. 349 """Runs git to update or transparently checkout the working copy.
350 350
351 All updated files will be appended to file_list. 351 All updated files will be appended to file_list.
352 352
353 Raises: 353 Raises:
354 Error: if can't get URL for relative path. 354 Error: if can't get URL for relative path.
355 """ 355 """
356 ### Section 0: Sanity checks.
356 if args: 357 if args:
357 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args)) 358 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args))
358 359
359 self._CheckMinVersion("1.6.6") 360 self._CheckMinVersion("1.6.6")
360 361
361 # If a dependency is not pinned, track the default remote branch. 362 ### Section 1: Handling basic options and parsing revision.
362 default_rev = 'refs/remotes/%s/master' % self.remote 363 # Command line options trump DEPS-file pins. If neither is provided,
363 url, deps_revision = gclient_utils.SplitUrlRevision(self.url) 364 # fall back to the default branch (master).
364 revision = deps_revision 365 # Throughout this function, 'revision' is assumed to be available on the
365 managed = True 366 # remote; i.e. it will never be "refs/remotes/foo/bar". When it is necessary
367 # to translate the desired revision into a locally-available refish, that
368 # is handled later.
369 default_ref = 'refs/heads/master'
370 url, revision = gclient_utils.SplitUrlRevision(self.url)
366 if options.revision: 371 if options.revision:
367 # Override the revision number.
368 revision = str(options.revision) 372 revision = str(options.revision)
369 if revision == 'unmanaged':
370 # Check again for a revision in case an initial ref was specified
371 # in the url, for example bla.git@refs/heads/custombranch
372 revision = deps_revision
373 managed = False
374 if not revision: 373 if not revision:
375 revision = default_rev 374 revision = default_ref
376 375
376 managed = (revision == 'unmanaged')
377 if managed: 377 if managed:
378 self._DisableHooks() 378 self._DisableHooks()
379 379
380 printed_path = False 380 printed_path = False
381 verbose = [] 381 verbose = []
382 if options.verbose: 382 if options.verbose:
383 verbose = ['--verbose']
383 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False) 384 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
384 verbose = ['--verbose']
385 printed_path = True 385 printed_path = True
386 386
387 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
388 if remote_ref:
389 # Rewrite remote refs to their local equivalents.
390 revision = ''.join(remote_ref)
391 rev_type = "branch"
392 elif revision.startswith('refs/'):
393 # Local branch? We probably don't want to support, since DEPS should
394 # always specify branches as they are in the upstream repo.
395 rev_type = "branch"
396 else:
397 # hash is also a tag, only make a distinction at checkout
398 rev_type = "hash"
399
400 mirror = self._GetMirror(url, options) 387 mirror = self._GetMirror(url, options)
401 if mirror: 388 if mirror:
402 url = mirror.mirror_path 389 url = mirror.mirror_path
403 390
404 # If we are going to introduce a new project, there is a possibility that 391 ### Section 2: Ensuring a checkout exists at all.
405 # we are syncing back to a state where the project was originally a 392 # If our checkout path isn't a git repository, or if the repository which
406 # sub-project rolled by DEPS (realistic case: crossing the Blink merge point 393 # is there is corrupt, we need to bootstrap it.
407 # syncing backwards, when Blink was a DEPS entry and not part of src.git). 394 needs_bootstrap = False
408 # In such case, we might have a backup of the former .git folder, which can 395 if not os.path.exists(self.checkout_path):
409 # be used to avoid re-fetching the entire repo again (useful for bisects). 396 needs_boostrap = True
410 backup_dir = self.GetGitBackupDirPath() 397 elif not os.path.exists(os.path.join(self.checkout_path, '.git')):
411 target_dir = os.path.join(self.checkout_path, '.git') 398 needs_bootstrap = True
412 if os.path.exists(backup_dir) and not os.path.exists(target_dir): 399 else:
413 gclient_utils.safe_makedirs(self.checkout_path) 400 try:
414 os.rename(backup_dir, target_dir) 401 self._Capture(['rev-list', '-n', '1', 'HEAD'])
402 except subprocess2.CalledProcessError as e:
403 if 'fatal: bad object HEAD' in e.stderr:
404 needs_bootstrap = True
405 # If a cache_dir is configured, that's likely why the repo is
406 # corrupt, so provide a helpful message.
407 if self.cache_dir and self.cache_dir in url:
408 self.Print(
409 'Likely due to DEPS change with git cache_dir, '
410 'the current commit points to no longer existing object.\n'
411 '%s' % e)
412
413 if needs_bootstrap:
414 # Get anything that might be there out of the way, just in case.
415 self._DeleteOrMove(options.force)
416
417 # If we are going to introduce a new project, there is a possibility that
418 # we are syncing back to a state where the project was originally
419 # a sub-project rolled by DEPS (realistic case: crossing the Blink merge
420 # point syncing backwards, when Blink was a DEPS entry and not part of
421 # src.git). In such case, we might have a backup of the former .git
422 # folder, which can be used to avoid re-fetching the entire repo again.
423 backup_dir = self.GetGitBackupDirPath()
424 if os.path.exists(backup_dir):
425 gclient_utils.safe_makedirs(self.checkout_path)
426 gclient_utils.safe_rename(backup_dir, target_dir)
427 else:
428 if mirror:
429 self._UpdateMirror(mirror, options)
430 self._Clone(revision, url, options)
431
415 # Reset to a clean state 432 # Reset to a clean state
416 self._Run(['reset', '--hard', 'HEAD'], options) 433 self._Run(['reset', '--hard', 'HEAD'], options)
417 434
418 if (not os.path.exists(self.checkout_path) or
419 (os.path.isdir(self.checkout_path) and
420 not os.path.exists(os.path.join(self.checkout_path, '.git')))):
421 if mirror:
422 self._UpdateMirror(mirror, options)
423 try:
424 self._Clone(revision, url, options)
425 except subprocess2.CalledProcessError:
426 self._DeleteOrMove(options.force)
427 self._Clone(revision, url, options)
428 if file_list is not None: 435 if file_list is not None:
429 files = self._Capture(['ls-files']).splitlines() 436 files = self._Capture(['ls-files']).splitlines()
430 file_list.extend([os.path.join(self.checkout_path, f) for f in files]) 437 file_list.extend([os.path.join(self.checkout_path, f) for f in files])
431 if not verbose:
432 # Make the output a little prettier. It's nice to have some whitespace
433 # between projects when cloning.
434 self.Print('')
435 return self._Capture(['rev-parse', '--verify', 'HEAD'])
436 438
439 ### Section 3: Getting the checkout ready to be updated.
437 if not managed: 440 if not managed:
438 self._UpdateBranchHeads(options, fetch=False)
439 self.Print('________ unmanaged solution; skipping %s' % self.relpath) 441 self.Print('________ unmanaged solution; skipping %s' % self.relpath)
440 return self._Capture(['rev-parse', '--verify', 'HEAD']) 442 return self._Capture(['rev-parse', '--verify', 'HEAD'])
441 443
442 self._maybe_break_locks(options) 444 self._MaybeBreakLocks(options)
443 445
444 if mirror: 446 # TODO(agable): The logic here is pretty crazy. Clean it up.
445 self._UpdateMirror(mirror, options)
446
447 # See if the url has changed (the unittests use git://foo for the url, let 447 # See if the url has changed (the unittests use git://foo for the url, let
448 # that through). 448 # that through).
449 current_url = self._Capture(['config', 'remote.%s.url' % self.remote]) 449 current_url = self._Capture(['config', 'remote.%s.url' % self.remote])
450 return_early = False
451 # TODO(maruel): Delete url != 'git://foo' since it's just to make the 450 # TODO(maruel): Delete url != 'git://foo' since it's just to make the
452 # unit test pass. (and update the comment above) 451 # unit test pass. (and update the comment above)
453 # Skip url auto-correction if remote.origin.gclient-auto-fix-url is set. 452 # Skip url auto-correction if remote.origin.gclient-auto-fix-url is set.
454 # This allows devs to use experimental repos which have a different url 453 # This allows devs to use experimental repos which have a different url
455 # but whose branch(s) are the same as official repos. 454 # but whose branch(s) are the same as official repos.
456 if (current_url.rstrip('/') != url.rstrip('/') and 455 if (current_url.rstrip('/') != url.rstrip('/') and
457 url != 'git://foo' and 456 url != 'git://foo' and
458 subprocess2.capture( 457 subprocess2.capture(
459 ['git', 'config', 'remote.%s.gclient-auto-fix-url' % self.remote], 458 ['git', 'config', 'remote.%s.gclient-auto-fix-url' % self.remote],
460 cwd=self.checkout_path).strip() != 'False'): 459 cwd=self.checkout_path).strip() != 'False'):
461 self.Print('_____ switching %s to a new upstream' % self.relpath) 460 self.Print('_____ switching %s to a new upstream' % self.relpath)
462 if not (options.force or options.reset): 461 if not (options.force or options.reset):
463 # Make sure it's clean 462 # Make sure it's clean
464 self._CheckClean(revision) 463 self._CheckClean(revision)
465 # Switch over to the new upstream 464 # Switch over to the new upstream
466 self._Run(['remote', 'set-url', self.remote, url], options) 465 self._Run(['remote', 'set-url', self.remote, url], options)
467 if mirror: 466 if mirror:
468 with open(os.path.join( 467 with open(os.path.join(
469 self.checkout_path, '.git', 'objects', 'info', 'alternates'), 468 self.checkout_path, '.git', 'objects', 'info', 'alternates'),
470 'w') as fh: 469 'w') as fh:
471 fh.write(os.path.join(url, 'objects')) 470 fh.write(os.path.join(url, 'objects'))
472 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
473 self._FetchAndReset(revision, file_list, options)
474 471
475 return_early = True 472 # If the user wants to set up refs/branch-heads/* and refs/tags/*, oblige.
476 else: 473 self._ConfigBranchHeads(options)
477 self._EnsureValidHeadObjectOrCheckout(revision, options, url)
478 474
479 if return_early: 475 try:
476 self._CheckClean(revision)
477 except gclient_utils.Error:
478 if options.reset:
479 self._Run(['reset', '--hard', 'HEAD'], options)
480 else:
481 raise
482
483 ### Section 4: Ensuring the desired revision is available.
484 # If the desired revision is already checked out, we have no work to do.
485 try:
486 revision_hash = self._Capture(['rev-parse', revision])
487 current_hash = self._Capture(['rev-parse', 'HEAD'])
488 if revision_hash == current_hash:
489 return self._Capture(['rev-parse', '--verify', 'HEAD'])
490 except subprocess2.CalledProcessError:
491 # We know HEAD is reachable, because if it wasn't we did the bootstrap.
492 # But if the desired revision isn't reachable, it's clearly not checked
493 # out, so continue to the rest of the routine.
494 pass
495
496 # Perform a basic fetch to see if the desired revision shows up.
497 if mirror:
498 self._UpdateMirror(mirror, options)
499 self._Fetch(options)
500 revision_hash = self._Capture(['rev-parse', revision])
501 if not gclient_utils.IsGitSha(revision_hash):
502 # If it didn't exist, fetch just that ref and save the FETCH_HEAD.
503 try:
504 revision_hash = self._Fetch(options, refspec=[revision])
505 except subprocess2.CalledProcessError as e:
506 raise gclient_utils.Error(
507 '\n____ %s at %s\n\n'
508 'The given revision is neither reachable from a normal clone,\n'
509 'nor directly fetchable from the remote. Aborting.\n'
510 % (self.relpath, revision))
511
512 ### Section 5: Performing the checkout.
513 cur_branch = self._GetCurrentBranch()
514
515 if cur_branch is None:
516 # We're in a detached HEAD state. Ensure the current commit is reachable
517 # (by creating a new branch if necessary), and then check out the new one.
518 self._CheckDetachedHead(revision, options)
519 # 'git checkout' may need to overwrite existing untracked files. Allow
520 # it only when nuclear options are enabled.
521 self._Checkout(
522 options,
523 revision,
524 force=(options.force and options.delete_unversioned_trees),
525 quiet=True,
526 )
527 if not printed_path:
528 self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
480 return self._Capture(['rev-parse', '--verify', 'HEAD']) 529 return self._Capture(['rev-parse', '--verify', 'HEAD'])
481 530
482 cur_branch = self._GetCurrentBranch() 531 # TODO(agable): Drastically simplify this. I think it can be reduced to
483 532 # * Only rebase if --auto_rebase is passed
533 # * If rebase fails, and --force is passed, just switch
534 # * If rebase fails, and --force is not passed, abort
535 # * If not told to rebase, just switch
536 # But not reducing it yet, because I'm scared of breaking too much at once.
484 # Cases: 537 # Cases:
485 # 0) HEAD is detached. Probably from our initial clone. 538 # 0) HEAD is detached. Probably from our initial clone.
486 # - make sure HEAD is contained by a named ref, then update. 539 # - make sure HEAD is contained by a named ref, then update.
487 # Cases 1-4. HEAD is a branch. 540 # Cases 1-4. HEAD is a branch.
488 # 1) current branch is not tracking a remote branch 541 # 1) current branch is not tracking a remote branch
489 # - try to rebase onto the new hash or branch 542 # - try to rebase onto the new hash or branch
490 # 2) current branch is tracking a remote branch with local committed 543 # 2) current branch is tracking a remote branch with local committed
491 # changes, but the DEPS file switched to point to a hash 544 # changes, but the DEPS file switched to point to a hash
492 # - rebase those changes on top of the hash 545 # - rebase those changes on top of the hash
493 # 3) current branch is tracking a remote branch w/or w/out changes, and 546 # 3) current branch is tracking a remote branch w/or w/out changes, and
494 # no DEPS switch 547 # no DEPS switch
495 # - see if we can FF, if not, prompt the user for rebase, merge, or stop 548 # - see if we can FF, if not, prompt the user for rebase, merge, or stop
496 # 4) current branch is tracking a remote branch, but DEPS switches to a 549 # 4) current branch is tracking a remote branch, but DEPS switches to a
497 # different remote branch, and 550 # different remote branch, and
498 # a) current branch has no local changes, and --force: 551 # a) current branch has no local changes, and --force:
499 # - checkout new branch 552 # - checkout new branch
500 # b) current branch has local changes, and --force and --reset: 553 # b) current branch has local changes, and --force and --reset:
501 # - checkout new branch 554 # - checkout new branch
502 # c) otherwise exit 555 # c) otherwise exit
503 556
504 # GetUpstreamBranch returns something like 'refs/remotes/origin/master' for 557 # GetUpstreamBranch returns something like 'refs/remotes/origin/master'
505 # a tracking branch 558 # for a tracking branch
506 # or 'master' if not a tracking branch (it's based on a specific rev/hash) 559 # or 'master' if not a tracking branch (it's based on a specific rev/hash)
507 # or it returns None if it couldn't find an upstream 560 # or it returns None if it couldn't find an upstream
508 if cur_branch is None: 561 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
509 upstream_branch = None 562 if not upstream_branch or not upstream_branch.startswith('refs/remotes'):
510 current_type = "detached" 563 current_type = "hash"
511 logging.debug("Detached HEAD") 564 logging.debug("Current branch is not tracking an upstream (remote)"
565 " branch.")
566 elif upstream_branch.startswith('refs/remotes'):
567 current_type = "branch"
512 else: 568 else:
513 upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path) 569 raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch)
514 if not upstream_branch or not upstream_branch.startswith('refs/remotes'):
515 current_type = "hash"
516 logging.debug("Current branch is not tracking an upstream (remote)"
517 " branch.")
518 elif upstream_branch.startswith('refs/remotes'):
519 current_type = "branch"
520 else:
521 raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch)
522
523 if not scm.GIT.IsValidRevision(self.checkout_path, revision, sha_only=True):
524 # Update the remotes first so we have all the refs.
525 remote_output = scm.GIT.Capture(['remote'] + verbose + ['update'],
526 cwd=self.checkout_path)
527 if verbose:
528 self.Print(remote_output)
529
530 self._UpdateBranchHeads(options, fetch=True)
531 570
532 # This is a big hammer, debatable if it should even be here... 571 # This is a big hammer, debatable if it should even be here...
533 if options.force or options.reset: 572 if options.force or options.reset:
534 target = 'HEAD' 573 target = 'HEAD'
535 if options.upstream and upstream_branch: 574 if options.upstream and upstream_branch:
536 target = upstream_branch 575 target = upstream_branch
537 self._Run(['reset', '--hard', target], options) 576 self._Run(['reset', '--hard', target], options)
538 577
539 if current_type == 'detached': 578 if current_type == 'detached':
540 # case 0 579 # case 0
(...skipping 130 matching lines...) Expand 10 before | Expand all | Expand 10 after
671 self.Print(merge_output.strip()) 710 self.Print(merge_output.strip())
672 if not verbose: 711 if not verbose:
673 # Make the output a little prettier. It's nice to have some 712 # Make the output a little prettier. It's nice to have some
674 # whitespace between projects when syncing. 713 # whitespace between projects when syncing.
675 self.Print('') 714 self.Print('')
676 715
677 if file_list is not None: 716 if file_list is not None:
678 file_list.extend( 717 file_list.extend(
679 [os.path.join(self.checkout_path, f) for f in rebase_files]) 718 [os.path.join(self.checkout_path, f) for f in rebase_files])
680 719
681 # If the rebase generated a conflict, abort and ask user to fix 720 ### Section 6: Final cleanup.
721 # If the rebase generated a conflict, abort and ask user to fix.
682 if self._IsRebasing(): 722 if self._IsRebasing():
683 raise gclient_utils.Error('\n____ %s at %s\n' 723 raise gclient_utils.Error('\n____ %s at %s\n'
684 '\nConflict while rebasing this branch.\n' 724 '\nConflict while rebasing this branch.\n'
685 'Fix the conflict and run gclient again.\n' 725 'Fix the conflict and run gclient again.\n'
686 'See man git-rebase for details.\n' 726 'See man git-rebase for details.\n'
687 % (self.relpath, revision)) 727 % (self.relpath, revision))
688 728
689 if verbose: 729 if verbose:
690 self.Print('Checked out revision %s' % self.revinfo(options, (), None), 730 self.Print('Checked out revision %s' % self.revinfo(options, (), None),
691 timestamp=False) 731 timestamp=False)
(...skipping 148 matching lines...) Expand 10 before | Expand all | Expand 10 after
840 depth = 10000 880 depth = 10000
841 else: 881 else:
842 depth = None 882 depth = None
843 mirror.populate(verbose=options.verbose, 883 mirror.populate(verbose=options.verbose,
844 bootstrap=not getattr(options, 'no_bootstrap', False), 884 bootstrap=not getattr(options, 'no_bootstrap', False),
845 depth=depth, 885 depth=depth,
846 ignore_lock=getattr(options, 'ignore_locks', False), 886 ignore_lock=getattr(options, 'ignore_locks', False),
847 lock_timeout=getattr(options, 'lock_timeout', 0)) 887 lock_timeout=getattr(options, 'lock_timeout', 0))
848 mirror.unlock() 888 mirror.unlock()
849 889
850 def _Clone(self, revision, url, options): 890 def _Clone(self, url, options):
851 """Clone a git repository from the given URL. 891 """Perform a bare clone a git repository from the given URL.
852 892
853 Once we've cloned the repo, we checkout a working branch if the specified 893 Does not perform any checkout. Only fetches the default set of refs that
854 revision is a branch head. If it is a tag or a specific commit, then we 894 a normal clone would get, and does not set up any custom fetch specs.
855 leave HEAD detached as it makes future updates simpler -- in this case the 895 """
856 user should first create a new branch or switch to an existing branch before
857 making changes in the repo."""
858 if not options.verbose: 896 if not options.verbose:
859 # git clone doesn't seem to insert a newline properly before printing 897 # git clone doesn't seem to insert a newline properly before printing
860 # to stdout 898 # to stdout
861 self.Print('') 899 self.Print('')
900
862 cfg = gclient_utils.DefaultIndexPackConfig(url) 901 cfg = gclient_utils.DefaultIndexPackConfig(url)
863 clone_cmd = cfg + ['clone', '--no-checkout', '--progress'] 902 clone_cmd = cfg + ['clone', '--no-checkout', '--progress']
864 if self.cache_dir: 903 if self.cache_dir:
865 clone_cmd.append('--shared') 904 clone_cmd.append('--shared')
866 if options.verbose: 905 if options.verbose:
867 clone_cmd.append('--verbose') 906 clone_cmd.append('--verbose')
868 clone_cmd.append(url) 907 clone_cmd.append(url)
869 # If the parent directory does not exist, Git clone on Windows will not 908 # If the parent directory does not exist, Git clone on Windows will not
870 # create it, so we need to do it manually. 909 # create it, so we need to do it manually.
871 parent_dir = os.path.dirname(self.checkout_path) 910 parent_dir = os.path.dirname(self.checkout_path)
(...skipping 13 matching lines...) Expand all
885 with open(os.path.join(template_dir, 'shallow'), 'w') as template_file: 924 with open(os.path.join(template_dir, 'shallow'), 'w') as template_file:
886 template_file.write(revision) 925 template_file.write(revision)
887 clone_cmd.append('--template=' + template_dir) 926 clone_cmd.append('--template=' + template_dir)
888 else: 927 else:
889 # Otherwise, we're just interested in the HEAD. Just use --depth. 928 # Otherwise, we're just interested in the HEAD. Just use --depth.
890 clone_cmd.append('--depth=1') 929 clone_cmd.append('--depth=1')
891 930
892 tmp_dir = tempfile.mkdtemp( 931 tmp_dir = tempfile.mkdtemp(
893 prefix='_gclient_%s_' % os.path.basename(self.checkout_path), 932 prefix='_gclient_%s_' % os.path.basename(self.checkout_path),
894 dir=parent_dir) 933 dir=parent_dir)
934
895 try: 935 try:
896 clone_cmd.append(tmp_dir) 936 clone_cmd.append(tmp_dir)
897 self._Run(clone_cmd, options, cwd=self._root_dir, retry=True) 937 self._Run(clone_cmd, options, cwd=self._root_dir, retry=True)
898 gclient_utils.safe_makedirs(self.checkout_path) 938 gclient_utils.safe_makedirs(self.checkout_path)
899 gclient_utils.safe_rename(os.path.join(tmp_dir, '.git'), 939 gclient_utils.safe_rename(os.path.join(tmp_dir, '.git'),
900 os.path.join(self.checkout_path, '.git')) 940 os.path.join(self.checkout_path, '.git'))
901 except: 941 except:
902 traceback.print_exc(file=self.out_fh) 942 traceback.print_exc(file=self.out_fh)
903 raise 943 raise
904 finally: 944 finally:
905 if os.listdir(tmp_dir): 945 if os.listdir(tmp_dir):
906 self.Print('_____ removing non-empty tmp dir %s' % tmp_dir) 946 self.Print('_____ removing non-empty tmp dir %s' % tmp_dir)
907 gclient_utils.rmtree(tmp_dir) 947 gclient_utils.rmtree(tmp_dir)
908 if template_dir: 948 if template_dir:
909 gclient_utils.rmtree(template_dir) 949 gclient_utils.rmtree(template_dir)
910 self._UpdateBranchHeads(options, fetch=True) 950
911 remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote) 951 if not verbose:
912 self._Checkout(options, ''.join(remote_ref or revision), quiet=True) 952 # Make the output a little prettier. It's nice to have some whitespace
913 if self._GetCurrentBranch() is None: 953 # between projects when cloning.
914 # Squelch git's very verbose detached HEAD warning and use our own 954 self.Print('')
915 self.Print(
916 ('Checked out %s to a detached HEAD. Before making any commits\n'
917 'in this repo, you should use \'git checkout <branch>\' to switch to\n'
918 'an existing branch or use \'git checkout %s -b <branch>\' to\n'
919 'create a new branch for your work.') % (revision, self.remote))
920 955
921 def _AskForData(self, prompt, options): 956 def _AskForData(self, prompt, options):
922 if options.jobs > 1: 957 if options.jobs > 1:
923 self.Print(prompt) 958 self.Print(prompt)
924 raise gclient_utils.Error("Background task requires input. Rerun " 959 raise gclient_utils.Error("Background task requires input. Rerun "
925 "gclient with --jobs=1 so that\n" 960 "gclient with --jobs=1 so that\n"
926 "interaction is possible.") 961 "interaction is possible.")
927 try: 962 try:
928 return raw_input(prompt) 963 return raw_input(prompt)
929 except KeyboardInterrupt: 964 except KeyboardInterrupt:
930 # Hide the exception. 965 # Hide the exception.
931 sys.exit(1) 966 sys.exit(1)
932 967
933
934 def _AttemptRebase(self, upstream, files, options, newbase=None, 968 def _AttemptRebase(self, upstream, files, options, newbase=None,
935 branch=None, printed_path=False, merge=False): 969 branch=None, printed_path=False, merge=False):
936 """Attempt to rebase onto either upstream or, if specified, newbase.""" 970 """Attempt to rebase onto either upstream or, if specified, newbase."""
937 if files is not None: 971 if files is not None:
938 files.extend(self._Capture(['diff', upstream, '--name-only']).split()) 972 files.extend(self._Capture(['diff', upstream, '--name-only']).split())
939 revision = upstream 973 revision = upstream
940 if newbase: 974 if newbase:
941 revision = newbase 975 revision = newbase
942 action = 'merge' if merge else 'rebase' 976 action = 'merge' if merge else 'rebase'
943 if not printed_path: 977 if not printed_path:
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after
1009 # whitespace between projects when syncing. 1043 # whitespace between projects when syncing.
1010 self.Print('') 1044 self.Print('')
1011 1045
1012 @staticmethod 1046 @staticmethod
1013 def _CheckMinVersion(min_version): 1047 def _CheckMinVersion(min_version):
1014 (ok, current_version) = scm.GIT.AssertVersion(min_version) 1048 (ok, current_version) = scm.GIT.AssertVersion(min_version)
1015 if not ok: 1049 if not ok:
1016 raise gclient_utils.Error('git version %s < minimum required %s' % 1050 raise gclient_utils.Error('git version %s < minimum required %s' %
1017 (current_version, min_version)) 1051 (current_version, min_version))
1018 1052
1019 def _EnsureValidHeadObjectOrCheckout(self, revision, options, url):
1020 # Special case handling if all 3 conditions are met:
1021 # * the mirros have recently changed, but deps destination remains same,
1022 # * the git histories of mirrors are conflicting.
1023 # * git cache is used
1024 # This manifests itself in current checkout having invalid HEAD commit on
1025 # most git operations. Since git cache is used, just deleted the .git
1026 # folder, and re-create it by cloning.
1027 try:
1028 self._Capture(['rev-list', '-n', '1', 'HEAD'])
1029 except subprocess2.CalledProcessError as e:
1030 if ('fatal: bad object HEAD' in e.stderr
1031 and self.cache_dir and self.cache_dir in url):
1032 self.Print((
1033 'Likely due to DEPS change with git cache_dir, '
1034 'the current commit points to no longer existing object.\n'
1035 '%s' % e)
1036 )
1037 self._DeleteOrMove(options.force)
1038 self._Clone(revision, url, options)
1039 else:
1040 raise
1041
1042 def _IsRebasing(self): 1053 def _IsRebasing(self):
1043 # Check for any of REBASE-i/REBASE-m/REBASE/AM. Unfortunately git doesn't 1054 # Check for any of REBASE-i/REBASE-m/REBASE/AM. Unfortunately git doesn't
1044 # have a plumbing command to determine whether a rebase is in progress, so 1055 # have a plumbing command to determine whether a rebase is in progress, so
1045 # for now emualate (more-or-less) git-rebase.sh / git-completion.bash 1056 # for now emualate (more-or-less) git-rebase.sh / git-completion.bash
1046 g = os.path.join(self.checkout_path, '.git') 1057 g = os.path.join(self.checkout_path, '.git')
1047 return ( 1058 return (
1048 os.path.isdir(os.path.join(g, "rebase-merge")) or 1059 os.path.isdir(os.path.join(g, "rebase-merge")) or
1049 os.path.isdir(os.path.join(g, "rebase-apply"))) 1060 os.path.isdir(os.path.join(g, "rebase-apply")))
1050 1061
1051 def _CheckClean(self, revision): 1062 def _CheckClean(self, revision):
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after
1126 if quiet is None: 1137 if quiet is None:
1127 quiet = (not options.verbose) 1138 quiet = (not options.verbose)
1128 checkout_args = ['checkout'] 1139 checkout_args = ['checkout']
1129 if force: 1140 if force:
1130 checkout_args.append('--force') 1141 checkout_args.append('--force')
1131 if quiet: 1142 if quiet:
1132 checkout_args.append('--quiet') 1143 checkout_args.append('--quiet')
1133 checkout_args.append(ref) 1144 checkout_args.append(ref)
1134 return self._Capture(checkout_args) 1145 return self._Capture(checkout_args)
1135 1146
1136 def _Fetch(self, options, remote=None, prune=False, quiet=False): 1147 def _Fetch(
1148 self, options, remote=None, refspec=None, prune=False, quiet=False):
1137 cfg = gclient_utils.DefaultIndexPackConfig(self.url) 1149 cfg = gclient_utils.DefaultIndexPackConfig(self.url)
1150 remote = remote or self.remote
1151 refspec = refspec or []
1138 fetch_cmd = cfg + [ 1152 fetch_cmd = cfg + [
1139 'fetch', 1153 'fetch',
1140 remote or self.remote, 1154 remote,
1141 ] 1155 ] + refspec
1142 1156
1143 if prune: 1157 if prune:
1144 fetch_cmd.append('--prune') 1158 fetch_cmd.append('--prune')
1145 if options.verbose: 1159 if options.verbose:
1146 fetch_cmd.append('--verbose') 1160 fetch_cmd.append('--verbose')
1147 elif quiet: 1161 elif quiet:
1148 fetch_cmd.append('--quiet') 1162 fetch_cmd.append('--quiet')
1149 self._Run(fetch_cmd, options, show_header=options.verbose, retry=True) 1163 self._Run(fetch_cmd, options, show_header=options.verbose, retry=True)
1150 1164
1151 # Return the revision that was fetched; this will be stored in 'FETCH_HEAD' 1165 # Return the revision that was fetched; this will be stored in 'FETCH_HEAD'
1152 return self._Capture(['rev-parse', '--verify', 'FETCH_HEAD']) 1166 return self._Capture(['rev-parse', '--verify', 'FETCH_HEAD'])
1153 1167
1154 def _UpdateBranchHeads(self, options, fetch=False): 1168 def _ConfigBranchHeads(self, options):
1155 """Adds, and optionally fetches, "branch-heads" and "tags" refspecs 1169 """Adds "branch-heads" and "tags" refspecs if requested."""
1156 if requested."""
1157 need_fetch = fetch
1158 if hasattr(options, 'with_branch_heads') and options.with_branch_heads: 1170 if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
1159 config_cmd = ['config', 'remote.%s.fetch' % self.remote, 1171 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
1160 '+refs/branch-heads/*:refs/remotes/branch-heads/*', 1172 '+refs/branch-heads/*:refs/remotes/branch-heads/*',
1161 '^\\+refs/branch-heads/\\*:.*$'] 1173 '^\\+refs/branch-heads/\\*:.*$']
1162 self._Run(config_cmd, options) 1174 self._Run(config_cmd, options)
1163 need_fetch = True
1164 if hasattr(options, 'with_tags') and options.with_tags: 1175 if hasattr(options, 'with_tags') and options.with_tags:
1165 config_cmd = ['config', 'remote.%s.fetch' % self.remote, 1176 config_cmd = ['config', 'remote.%s.fetch' % self.remote,
1166 '+refs/tags/*:refs/tags/*', 1177 '+refs/tags/*:refs/tags/*',
1167 '^\\+refs/tags/\\*:.*$'] 1178 '^\\+refs/tags/\\*:.*$']
1168 self._Run(config_cmd, options) 1179 self._Run(config_cmd, options)
1169 need_fetch = True
1170 if fetch and need_fetch:
1171 self._Fetch(options, prune=options.force)
1172 1180
1173 def _Run(self, args, options, show_header=True, **kwargs): 1181 def _Run(self, args, options, show_header=True, **kwargs):
1174 # Disable 'unused options' warning | pylint: disable=W0613 1182 # Disable 'unused options' warning | pylint: disable=W0613
1175 kwargs.setdefault('cwd', self.checkout_path) 1183 kwargs.setdefault('cwd', self.checkout_path)
1176 kwargs.setdefault('stdout', self.out_fh) 1184 kwargs.setdefault('stdout', self.out_fh)
1177 kwargs['filter_fn'] = self.filter 1185 kwargs['filter_fn'] = self.filter
1178 kwargs.setdefault('print_stdout', False) 1186 kwargs.setdefault('print_stdout', False)
1179 env = scm.GIT.ApplyEnvVars(kwargs) 1187 env = scm.GIT.ApplyEnvVars(kwargs)
1180 cmd = ['git'] + args 1188 cmd = ['git'] + args
1181 if show_header: 1189 if show_header:
1182 gclient_utils.CheckCallAndFilterAndHeader(cmd, env=env, **kwargs) 1190 gclient_utils.CheckCallAndFilterAndHeader(cmd, env=env, **kwargs)
1183 else: 1191 else:
1184 gclient_utils.CheckCallAndFilter(cmd, env=env, **kwargs) 1192 gclient_utils.CheckCallAndFilter(cmd, env=env, **kwargs)
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