diff --git a/gclient_scm.py b/gclient_scm.py index bb1b1a211..cc03d8e6d 100644 --- a/gclient_scm.py +++ b/gclient_scm.py @@ -543,6 +543,8 @@ class GitWrapper(SCMWrapper): self._UpdateBranchHeads(options, fetch=True) + revision = self._AutoFetchRef(options, revision) + # This is a big hammer, debatable if it should even be here... if options.force or options.reset: target = 'HEAD' @@ -919,6 +921,7 @@ class GitWrapper(SCMWrapper): if template_dir: gclient_utils.rmtree(template_dir) self._UpdateBranchHeads(options, fetch=True) + revision = self._AutoFetchRef(options, revision) remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote) self._Checkout(options, ''.join(remote_ref or revision), quiet=True) if self._GetCurrentBranch() is None: @@ -1148,12 +1151,15 @@ class GitWrapper(SCMWrapper): checkout_args.append(ref) return self._Capture(checkout_args) - def _Fetch(self, options, remote=None, prune=False, quiet=False): + def _Fetch(self, options, remote=None, prune=False, quiet=False, + refspec=None): cfg = gclient_utils.DefaultIndexPackConfig(self.url) fetch_cmd = cfg + [ 'fetch', remote or self.remote, ] + if refspec: + fetch_cmd.append(refspec) if prune: fetch_cmd.append('--prune') @@ -1185,6 +1191,17 @@ class GitWrapper(SCMWrapper): if fetch and need_fetch: self._Fetch(options, prune=options.force) + def _AutoFetchRef(self, options, revision): + """Attempts to fetch |revision| if not available in local repo. + + Returns possibly updated revision.""" + try: + self._Capture(['rev-parse', revision]) + except subprocess2.CalledProcessError: + self._Fetch(options, refspec=revision) + revision = self._Capture(['rev-parse', 'FETCH_HEAD']) + return revision + def _Run(self, args, options, show_header=True, **kwargs): # Disable 'unused options' warning | pylint: disable=unused-argument kwargs.setdefault('cwd', self.checkout_path) diff --git a/testing_support/fake_repos.py b/testing_support/fake_repos.py index 06d181247..80dbaba15 100755 --- a/testing_support/fake_repos.py +++ b/testing_support/fake_repos.py @@ -281,6 +281,12 @@ class FakeReposBase(object): new_tree = tree.copy() self.git_hashes[repo].append((commit_hash, new_tree)) + def _fast_import_git(self, repo, data): + repo_root = join(self.git_root, repo) + logging.debug('%s: fast-import %s', repo, data) + subprocess2.check_call( + ['git', 'fast-import', '--quiet'], cwd=repo_root, stdin=data) + def check_port_is_free(self, port): sock = socket.socket() try: @@ -298,7 +304,7 @@ class FakeReposBase(object): class FakeRepos(FakeReposBase): """Implements populateGit().""" - NB_GIT_REPOS = 12 + NB_GIT_REPOS = 13 def populateGit(self): # Testing: @@ -620,6 +626,43 @@ deps = { 'origin': 'git/repo_12@1\n', }) + self._fast_import_git('repo_12', """blob +mark :1 +data 6 +Hello + +blob +mark :2 +data 4 +Bye + +reset refs/changes/1212 +commit refs/changes/1212 +mark :3 +author Bob 1253744361 -0700 +committer Bob 1253744361 -0700 +data 8 +A and B +M 100644 :1 a +M 100644 :2 b +""") + + self._commit_git('repo_13', { + 'DEPS': """ +deps = { + 'src/repo12': '/repo_12', +}""", + 'origin': 'git/repo_13@1\n', + }) + + self._commit_git('repo_13', { + 'DEPS': """ +deps = { + 'src/repo12': '/repo_12@refs/changes/1212', +}""", + 'origin': 'git/repo_13@2\n', + }) + class FakeRepoSkiaDEPS(FakeReposBase): """Simulates the Skia DEPS transition in Chrome.""" diff --git a/tests/gclient_scm_test.py b/tests/gclient_scm_test.py index e270d1449..60266a61d 100755 --- a/tests/gclient_scm_test.py +++ b/tests/gclient_scm_test.py @@ -792,8 +792,11 @@ class UnmanagedGitWrapperTestCase(BaseGitWrapperTestCase): self.assertEquals(file_list, expected_file_list) self.assertEquals(scm.revinfo(options, (), None), '9a51244740b25fa2ded5252ca00a3178d3f665a9') - self.assertEquals(self.getCurrentBranch(), 'feature') - self.checkNotInStdout('Checked out feature to a detached HEAD') + # indicates detached HEAD + self.assertEquals(self.getCurrentBranch(), None) + self.checkInStdout( + 'Checked out 9a51244740b25fa2ded5252ca00a3178d3f665a9 ' + 'to a detached HEAD') rmtree(origin_root_dir) diff --git a/tests/gclient_smoketest.py b/tests/gclient_smoketest.py index ffcbd19fe..f7ac4dee3 100755 --- a/tests/gclient_smoketest.py +++ b/tests/gclient_smoketest.py @@ -439,6 +439,36 @@ class GClientSmokeGIT(GClientSmokeBase): ]) self.assertTree(tree) + def testSyncFetch(self): + if not self.enabled: + return + self.gclient(['config', self.git_base + 'repo_13', '--name', 'src']) + _out, _err, rc = self.gclient(['sync', '-v', '-v', '-v']) + self.assertEquals(0, rc) + + def testSyncFetchUpdate(self): + if not self.enabled: + return + self.gclient(['config', self.git_base + 'repo_13', '--name', 'src']) + + # Sync to an earlier revision first, one that doesn't refer to + # non-standard refs. + _out, _err, rc = self.gclient( + ['sync', '-v', '-v', '-v', '--revision', self.githash('repo_13', 1)]) + self.assertEquals(0, rc) + + # Make sure update that pulls a non-standard ref works. + _out, _err, rc = self.gclient(['sync', '-v', '-v', '-v']) + self.assertEquals(0, rc) + + def testSyncDirect(self): + if not self.enabled: + return + self.gclient(['config', self.git_base + 'repo_12', '--name', 'src']) + _out, _err, rc = self.gclient( + ['sync', '-v', '-v', '-v', '--revision', 'refs/changes/1212']) + self.assertEquals(0, rc) + def testRunHooks(self): if not self.enabled: return