[fetch] Actually fetch from the provided ref.

Previously there was confusion around ref vs revision; The checkout
function signature expected a ref, but was always being passed a revision.

Now we pass both the ref and the revision when we have them; The fetch
operation will now provide the ref during the fetch which should allow
non-master refs.

R=gbeaty@chromium.org, tandrii@chromium.org

Change-Id: Ie14f7c3f388f90bfeb49ad8a4145c04305dee1b4
Reviewed-on: https://chromium-review.googlesource.com/c/infra/luci/recipes-py/+/1693768
Reviewed-by: Garrett Beaty <gbeaty@chromium.org>
Reviewed-by: Andrii Shyshkalov <tandrii@chromium.org>
Commit-Queue: Robbie Iannucci <iannucci@chromium.org>
(cherry picked from commit b077e6d7b5e202070b28b0e7e1be95396c423eec)
Reviewed-on: https://chromium-review.googlesource.com/c/infra/luci/recipes-py/+/1698495
diff --git a/recipe_engine/internal/autoroll_impl/candidate_algorithm.py b/recipe_engine/internal/autoroll_impl/candidate_algorithm.py
index b68d56c..015965d 100644
--- a/recipe_engine/internal/autoroll_impl/candidate_algorithm.py
+++ b/recipe_engine/internal/autoroll_impl/candidate_algorithm.py
@@ -184,7 +184,7 @@
         # seemed like a worse alternative.
         dep_path = os.path.join(recipe_deps.recipe_deps_path, repo_name)
         backend = GitBackend(dep_path, dep.url)
-        backend.checkout(dep.revision)
+        backend.checkout(dep.branch, dep.revision)
 
         # Add any newly discovered repos to our repos set. We don't replace
         # repos because we want to keep the metadata for all already-rolled
diff --git a/recipe_engine/internal/fetch.py b/recipe_engine/internal/fetch.py
index 577dce3..306df1c 100644
--- a/recipe_engine/internal/fetch.py
+++ b/recipe_engine/internal/fetch.py
@@ -120,12 +120,13 @@
     """
     raise NotImplementedError()
 
-  def checkout(self, refspec):
+  def checkout(self, refspec, revision=None):
     """Checks out given |repo| at |refspec| to |checkout_dir|.
 
     Args:
       refspec (str) - a git refspec which is resolvable on the
         remote git repo, e.g. 'refs/heads/master', 'deadbeef...face', etc.
+      revesion (str|None) - A pre-resolved revision to checkout.
     """
     # TODO(iannucci): Alter the contract for this method so that it only checks
     # out the files referred to according to the rules that the bundle
@@ -295,8 +296,9 @@
     LOGGER.info('fetching %s', self.repo_url)
     self._git(*args)
 
-  def checkout(self, refspec):
-    revision = self.resolve_refspec(refspec)
+  def checkout(self, refspec, revision=None):
+    if not revision:
+      revision = self.resolve_refspec(refspec)
 
     LOGGER.info('Checking out %r in %s (%s)',
                 revision, self.checkout_dir, self.repo_url)
@@ -333,7 +335,7 @@
       if bool(rev)
     ]
 
-  def _resolve_refspec_impl(self, revision):
+  def _resolve_refspec_impl(self, refspec):
     self._ensure_local_repo_exists()
     # Can return e.g.
     #
@@ -346,10 +348,10 @@
       ref: csum
       for csum, ref in (
         l.split()
-        for l in self._git('ls-remote', source, revision).splitlines()
+        for l in self._git('ls-remote', source, refspec).splitlines()
       )
     }
-    rslt = mapping[revision]
+    rslt = mapping[refspec]
     assert self.is_resolved_revision(rslt), repr(rslt)
     return rslt
 
diff --git a/recipe_engine/internal/recipe_deps.py b/recipe_engine/internal/recipe_deps.py
index 54ae911..e365499 100644
--- a/recipe_engine/internal/recipe_deps.py
+++ b/recipe_engine/internal/recipe_deps.py
@@ -164,7 +164,7 @@
 
       dep_path = os.path.join(recipe_deps_path, repo_name)
       backend = fetch.GitBackend(dep_path, dep.url)
-      backend.checkout(dep.revision)
+      backend.checkout(dep.branch, dep.revision)
       repos[repo_name] = RecipeRepo.create(ret, dep_path, backend=backend)
 
     # This makes `repos` unmodifiable. object.__setattr__ is needed to get
diff --git a/unittests/fetch_test.py b/unittests/fetch_test.py
index 2cd37aa..065c6f4 100755
--- a/unittests/fetch_test.py
+++ b/unittests/fetch_test.py
@@ -99,8 +99,8 @@
     ]
 
   def g_ls_remote(self):
-    return self.g(['-C', 'dir', 'ls-remote', 'repo', 'revision'],
-                  'a'*40 + '\trevision')
+    return self.g(['-C', 'dir', 'ls-remote', 'repo', 'ref'],
+                  'a'*40 + '\tref')
 
   @mock.patch('os.path.isdir')
   @mock.patch(fetch.__name__+'.GitBackend._execute')
@@ -108,13 +108,12 @@
     isdir.return_value = False
     git.side_effect = multi(*([
       self.g(['init', 'dir']),
-      self.g_ls_remote(),
     ] + self.g_metadata_calls() + [
       self.g(['-C', 'dir', 'diff', '--quiet', 'a'*40], CPE('', 1)),
       self.g(['-C', 'dir', 'reset', '-q', '--hard', 'a'*40])
     ]))
 
-    fetch.GitBackend('dir', 'repo').checkout('revision')
+    fetch.GitBackend('dir', 'repo').checkout('ref', 'a'*40)
 
     self.assertMultiDone(git)
 
@@ -129,7 +128,7 @@
       self.g(['-C', 'dir', 'reset', '-q', '--hard', 'a'*40])
     ]))
 
-    fetch.GitBackend('dir', 'repo').checkout('revision')
+    fetch.GitBackend('dir', 'repo').checkout('ref')
 
     self.assertMultiDone(git)
     isdir.assert_has_calls([
@@ -140,13 +139,12 @@
   @mock.patch(fetch.__name__+'.GitBackend._execute')
   def test_existing_checkout_same_revision(self, git, isdir):
     isdir.return_value = True
-    git.side_effect = multi(*([
-      self.g_ls_remote(),
-    ] + self.g_metadata_calls() + [
+    git.side_effect = multi(*(
+      self.g_metadata_calls() + [
       self.g(['-C', 'dir', 'diff', '--quiet', 'a'*40]),
     ]))
 
-    fetch.GitBackend('dir', 'repo').checkout('revision')
+    fetch.GitBackend('dir', 'repo').checkout('ref', 'a'*40)
 
     self.assertMultiDone(git)
     isdir.assert_has_calls([
@@ -162,7 +160,7 @@
     git.side_effect = _mock_execute
 
     with self.assertRaises(exceptions.GitFetchError):
-      fetch.GitBackend('dir', 'repo').checkout('revision')
+      fetch.GitBackend('dir', 'repo').checkout('ref', 'a'*40)
 
     git.assert_called_once_with('GIT', 'init', 'dir')
 
@@ -171,18 +169,16 @@
   def test_rev_parse_fail(self, git, isdir):
     isdir.return_value = True
     git.side_effect = multi(*(
-      self.g_ls_remote(),
-
       self.g(
         ['-C', 'dir', 'show', '-s', '--format=%aE%n%ct%n%B', 'a'*40],
         CPE(1, 'nope')),
 
-      self.g(['-C', 'dir', 'fetch', 'repo', 'revision']),
+      self.g(['-C', 'dir', 'fetch', 'repo', 'ref']),
       self.g(['-C', 'dir', 'diff', '--quiet', 'a'*40], CPE('', 1)),
       self.g(['-C', 'dir', 'reset', '-q', '--hard', 'a'*40]),
     ))
 
-    fetch.GitBackend('dir', 'repo').checkout('revision')
+    fetch.GitBackend('dir', 'repo').checkout('ref', 'a'*40)
 
     self.assertMultiDone(git)
 
@@ -195,7 +191,7 @@
       self.g_ls_remote(),
     ] + self.g_metadata_calls()))
 
-    result = fetch.GitBackend('dir', 'repo').commit_metadata('revision')
+    result = fetch.GitBackend('dir', 'repo').commit_metadata('ref')
     self.assertEqual(result, fetch.CommitMetadata(
       revision = 'a'*40,
       author_email = 'foo@example.com',
@@ -219,7 +215,7 @@
       self.g_ls_remote(),
     ] + self.g_metadata_calls(config=spec)))
 
-    result = fetch.GitBackend('dir', 'repo').commit_metadata('revision')
+    result = fetch.GitBackend('dir', 'repo').commit_metadata('ref')
     self.assertEqual(result, fetch.CommitMetadata(
       revision = 'a'*40,
       author_email = 'foo@example.com',
@@ -242,7 +238,7 @@
       self.g_ls_remote(),
     ] + self.g_metadata_calls(config=spec, diff=tuple([IRC]))))
 
-    result = fetch.GitBackend('dir', 'repo').commit_metadata('revision')
+    result = fetch.GitBackend('dir', 'repo').commit_metadata('ref')
     self.assertEqual(result, fetch.CommitMetadata(
       revision = 'a'*40,
       author_email = 'foo@example.com',
@@ -264,7 +260,7 @@
       self.g_ls_remote()
     ] + self.g_metadata_calls(config=spec, diff=tuple(['recipes/foo']))))
 
-    result = fetch.GitBackend('dir', 'repo').commit_metadata('revision')
+    result = fetch.GitBackend('dir', 'repo').commit_metadata('ref')
     self.assertEqual(result, fetch.CommitMetadata(
       revision = 'a'*40,
       author_email = 'foo@example.com',
@@ -288,7 +284,7 @@
       self.g_ls_remote()
     ] + self.g_metadata_calls(config=spec)))
 
-    result = fetch.GitBackend('dir', 'repo').commit_metadata('revision')
+    result = fetch.GitBackend('dir', 'repo').commit_metadata('ref')
     self.assertEqual(result, fetch.CommitMetadata(
       revision = 'a'*40,
       author_email = 'foo@example.com',
@@ -311,7 +307,7 @@
       self.g_ls_remote()
     ] + self.g_metadata_calls(config=spec, diff=['.gitattributes'])))
 
-    result = fetch.GitBackend('dir', 'repo').commit_metadata('revision')
+    result = fetch.GitBackend('dir', 'repo').commit_metadata('ref')
     self.assertEqual(result, fetch.CommitMetadata(
       revision = 'a'*40,
       author_email = 'foo@example.com',
@@ -333,7 +329,7 @@
       self.g_ls_remote()
     ] + self.g_metadata_calls(config=spec, diff=['subdir/.gitattributes'])))
 
-    result = fetch.GitBackend('dir', 'repo').commit_metadata('revision')
+    result = fetch.GitBackend('dir', 'repo').commit_metadata('ref')
     self.assertEqual(result, fetch.CommitMetadata(
       revision = 'a'*40,
       author_email = 'foo@example.com',