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