Revert "gclient: Add support for the branch:revision format."

This reverts commit d328b47eb1a09f1872d6fe589d02c3c4822d5a06.

Reason for revert: suspect this caused https://bugs.chromium.org/p/chromium/issues/detail?id=863211

Original change's description:
> gclient: Add support for the branch:revision format.
> 
> Bug: 850812, 853032
> Change-Id: I597acbde2b3c229813b7eba8fcba52d5877130b2
> Reviewed-on: https://chromium-review.googlesource.com/1119235
> Commit-Queue: Edward Lesmes <ehmaldonado@chromium.org>
> Reviewed-by: Aaron Gable <agable@chromium.org>

TBR=agable@chromium.org,tandrii@chromium.org,ehmaldonado@chromium.org

Change-Id: I1e4c00b83a2840cc5a87621469da866dad58a20c
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: 850812, 853032
Reviewed-on: https://chromium-review.googlesource.com/1135893
Reviewed-by: John Budorick <jbudorick@chromium.org>
Commit-Queue: John Budorick <jbudorick@chromium.org>
diff --git a/gclient_scm.py b/gclient_scm.py
index a322b99..e96d8d1 100644
--- a/gclient_scm.py
+++ b/gclient_scm.py
@@ -303,12 +303,12 @@
         except OSError:
           pass
 
-  def _FetchAndReset(self, ref, remote_ref, revision, file_list, options):
+  def _FetchAndReset(self, revision, file_list, options):
     """Equivalent to git fetch; git reset."""
-    self._UpdateBranchHeads(options, ref, remote_ref, fetch=False)
+    self._UpdateBranchHeads(options, fetch=False)
 
     self._Fetch(options, prune=True, quiet=options.verbose)
-    self._Scrub(revision or remote_ref, options)
+    self._Scrub(revision, options)
     if file_list is not None:
       files = self._Capture(
           ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
@@ -384,12 +384,11 @@
 
     self._CheckMinVersion("1.6.6")
 
-    # If a dependency is not pinned, track refs/heads/master by default.
-    default_rev = 'refs/heads/master'
+    # If a dependency is not pinned, track the default remote branch.
+    default_rev = 'refs/remotes/%s/master' % self.remote
     url, deps_revision = gclient_utils.SplitUrlRevision(self.url)
     revision = deps_revision
     managed = True
-
     if options.revision:
       # Override the revision number.
       revision = str(options.revision)
@@ -411,37 +410,20 @@
       verbose = ['--verbose']
       printed_path = True
 
-    ref = remote_ref = None
-    # Support the 'branch:revision' syntax.
-    if ':' in revision:
-      ref, revision = revision.split(':')
-      if not gclient_utils.IsFullGitSha(revision):
-        raise gclient_utils.Error(
-            'Invalid format: %s:%s. revision must be a git hash.' % (
-                remote_ref, revision))
-    elif not gclient_utils.IsFullGitSha(revision):
-      ref = revision
-      revision = None
+    remote_ref = scm.GIT.RefToRemoteRef(revision, self.remote)
+    if remote_ref:
+      # Rewrite remote refs to their local equivalents.
+      revision = ''.join(remote_ref)
+      rev_type = "branch"
+    elif revision.startswith('refs/'):
+      # Local branch? We probably don't want to support, since DEPS should
+      # always specify branches as they are in the upstream repo.
+      rev_type = "branch"
+    else:
+      # hash is also a tag, only make a distinction at checkout
+      rev_type = "hash"
 
-    if ref:
-      if ref.startswith('origin/'):
-        ref = ref[len('origin/'):]
-      if not ref.startswith('refs/'):
-        ref = 'refs/heads/' + ref
-      remote_ref = scm.GIT.RefToRemoteRef(ref, self.remote)
-      if remote_ref:
-        # If there is a corresponding remote ref for |ref|,  RefToRemoteRef
-        # returns a tuple, so we need to join it to get the actual remote ref.
-        # E.g. ('refs/remotes/origin/', 'branch-name')
-        #         -> 'refs/remotes/origin/branch-name
-        remote_ref = ''.join(remote_ref)
-      else:
-        # Otherwise, it returns None, so we use |ref|.
-        remote_ref = ref
-
-    # If we're using a mirror, make sure it contains the ref we are asked to
-    # sync.
-    mirror = self._GetMirror(url, options, ref)
+    mirror = self._GetMirror(url, options)
     if mirror:
       url = mirror.mirror_path
 
@@ -463,12 +445,12 @@
         (os.path.isdir(self.checkout_path) and
          not os.path.exists(os.path.join(self.checkout_path, '.git')))):
       if mirror:
-        self._UpdateMirrorIfNotContains(mirror, options, revision)
+        self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
       try:
-        self._Clone(ref, remote_ref, revision, url, options)
+        self._Clone(revision, url, options)
       except subprocess2.CalledProcessError:
         self._DeleteOrMove(options.force)
-        self._Clone(ref, remote_ref, revision, url, options)
+        self._Clone(revision, url, options)
       if file_list is not None:
         files = self._Capture(
           ['-c', 'core.quotePath=false', 'ls-files']).splitlines()
@@ -480,14 +462,14 @@
       return self._Capture(['rev-parse', '--verify', 'HEAD'])
 
     if not managed:
-      self._UpdateBranchHeads(options, ref, remote_ref, fetch=False)
+      self._UpdateBranchHeads(options, fetch=False)
       self.Print('________ unmanaged solution; skipping %s' % self.relpath)
       return self._Capture(['rev-parse', '--verify', 'HEAD'])
 
     self._maybe_break_locks(options)
 
     if mirror:
-      self._UpdateMirrorIfNotContains(mirror, options, revision)
+      self._UpdateMirrorIfNotContains(mirror, options, rev_type, revision)
 
     # See if the url has changed (the unittests use git://foo for the url, let
     # that through).
@@ -514,14 +496,12 @@
             self.checkout_path, '.git', 'objects', 'info', 'alternates'),
             'w') as fh:
           fh.write(os.path.join(url, 'objects'))
-      self._EnsureValidHeadObjectOrCheckout(ref, remote_ref, revision, options,
-                                            url)
-      self._FetchAndReset(ref, remote_ref, revision, file_list, options)
+      self._EnsureValidHeadObjectOrCheckout(revision, options, url)
+      self._FetchAndReset(revision, file_list, options)
 
       return_early = True
     else:
-      self._EnsureValidHeadObjectOrCheckout(ref, remote_ref, revision, options,
-                                            url)
+      self._EnsureValidHeadObjectOrCheckout(revision, options, url)
 
     if return_early:
       return self._Capture(['rev-parse', '--verify', 'HEAD'])
@@ -567,17 +547,16 @@
       else:
         raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch)
 
-    if revision and not scm.GIT.IsValidRevision(
-        self.checkout_path, revision, sha_only=True):
+    if not scm.GIT.IsValidRevision(self.checkout_path, revision, sha_only=True):
       # Update the remotes first so we have all the refs.
       remote_output = scm.GIT.Capture(['remote'] + verbose + ['update'],
               cwd=self.checkout_path)
       if verbose:
         self.Print(remote_output)
 
-    self._UpdateBranchHeads(options, ref, remote_ref, fetch=True)
+    self._UpdateBranchHeads(options, fetch=True)
 
-    revision = self._AutoFetchRevision(options, revision)
+    revision = self._AutoFetchRef(options, revision)
 
     # This is a big hammer, debatable if it should even be here...
     if options.force or options.reset:
@@ -594,8 +573,8 @@
       # to actually "Clean" the checkout; that commit is uncheckoutable on this
       # system. The best we can do is carry forward to the checkout step.
       if not (options.force or options.reset):
-        self._CheckClean(revision or ref)
-      self._CheckDetachedHead(revision or ref, options)
+        self._CheckClean(revision)
+      self._CheckDetachedHead(revision, options)
       if self._Capture(['rev-list', '-n', '1', 'HEAD']) == revision:
         self.Print('Up-to-date; skipping checkout.')
       else:
@@ -603,35 +582,43 @@
         # it only when nuclear options are enabled.
         self._Checkout(
             options,
-            revision or ref,
+            revision,
             force=(options.force and options.delete_unversioned_trees),
             quiet=True,
         )
       if not printed_path:
-        self.Print('_____ %s at %s' % (self.relpath, revision or ref),
-                   timestamp=False)
+        self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
     elif current_type == 'hash':
       # case 1
       # Can't find a merge-base since we don't know our upstream. That makes
       # this command VERY likely to produce a rebase failure. For now we
       # assume origin is our upstream since that's what the old behavior was.
-      upstream_branch = revision or ref or self.remote
+      upstream_branch = self.remote
+      if options.revision or deps_revision:
+        upstream_branch = revision
       self._AttemptRebase(upstream_branch, file_list, options,
                           printed_path=printed_path, merge=options.merge)
       printed_path = True
-    elif remote_ref and remote_ref != upstream_branch:
+    elif rev_type == 'hash':
+      # case 2
+      self._AttemptRebase(upstream_branch, file_list, options,
+                          newbase=revision, printed_path=printed_path,
+                          merge=options.merge)
+      printed_path = True
+    elif remote_ref and ''.join(remote_ref) != upstream_branch:
       # case 4
+      new_base = ''.join(remote_ref)
       if not printed_path:
-        self.Print('_____ %s at %s' % (self.relpath, ref), timestamp=False)
+        self.Print('_____ %s at %s' % (self.relpath, revision), timestamp=False)
       switch_error = ("Could not switch upstream branch from %s to %s\n"
-                     % (upstream_branch, ref) +
+                     % (upstream_branch, new_base) +
                      "Please use --force or merge or rebase manually:\n" +
-                     "cd %s; git rebase %s\n" % (self.checkout_path, ref) +
-                     "OR git checkout -b <some new branch> %s" % ref)
+                     "cd %s; git rebase %s\n" % (self.checkout_path, new_base) +
+                     "OR git checkout -b <some new branch> %s" % new_base)
       force_switch = False
       if options.force:
         try:
-          self._CheckClean(ref)
+          self._CheckClean(revision)
           # case 4a
           force_switch = True
         except gclient_utils.Error as e:
@@ -642,25 +629,15 @@
             switch_error = '%s\n%s' % (e.message, switch_error)
       if force_switch:
         self.Print("Switching upstream branch from %s to %s" %
-                   (upstream_branch, ref))
-        switch_branch = 'gclient_' + re.sub('[^A-Za-z0-9]', '_', ref)
-        self._Capture(['branch', '-f', switch_branch, ref])
+                   (upstream_branch, new_base))
+        switch_branch = 'gclient_' + remote_ref[1]
+        self._Capture(['branch', '-f', switch_branch, new_base])
         self._Checkout(options, switch_branch, force=True, quiet=True)
-        if revision:
-          self._Scrub(revision, options)
       else:
         # case 4c
         raise gclient_utils.Error(switch_error)
-    elif revision:
-      # case 2
-      self._AttemptRebase(upstream_branch, file_list, options,
-                          newbase=revision, printed_path=printed_path,
-                          merge=options.merge)
-      printed_path = True
     else:
       # case 3 - the default case
-      # The same ref as |upstream_branch| was specified, and no revision was
-      # used.
       rebase_files = self._GetDiffFilenames(upstream_branch)
       if verbose:
         self.Print('Trying fast-forward merge to branch : %s' % upstream_branch)
@@ -676,7 +653,7 @@
         rebase_files = []
         if re.match('fatal: Not possible to fast-forward, aborting.', e.stderr):
           if not printed_path:
-            self.Print('_____ %s at %s' % (self.relpath, ref),
+            self.Print('_____ %s at %s' % (self.relpath, revision),
                        timestamp=False)
             printed_path = True
           while True:
@@ -709,7 +686,7 @@
                       "changes or stash them before you can merge.\n",
                       e.stderr):
           if not printed_path:
-            self.Print('_____ %s at %s' % (self.relpath, ref),
+            self.Print('_____ %s at %s' % (self.relpath, revision),
                        timestamp=False)
             printed_path = True
           raise gclient_utils.Error(e.stderr)
@@ -858,16 +835,13 @@
     return os.path.join(self._root_dir,
                         'old_' + self.relpath.replace(os.sep, '_')) + '.git'
 
-  def _GetMirror(self, url, options, ref=None):
+  def _GetMirror(self, url, options):
     """Get a git_cache.Mirror object for the argument url."""
     if not self.cache_dir:
       return None
-    # Don't try to fetch local refs in the mirror.
-    if ref and ref.startswith('refs/remotes'):
-      ref = None
     mirror_kwargs = {
         'print_func': self.filter,
-        'refs': [ref] if ref else [],
+        'refs': []
     }
     if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
       mirror_kwargs['refs'].append('refs/branch-heads/*')
@@ -875,11 +849,11 @@
       mirror_kwargs['refs'].append('refs/tags/*')
     return git_cache.Mirror(url, **mirror_kwargs)
 
-  def _UpdateMirrorIfNotContains(self, mirror, options, revision):
+  def _UpdateMirrorIfNotContains(self, mirror, options, rev_type, revision):
     """Update a git mirror by fetching the latest commits from the remote,
     unless mirror already contains revision whose type is sha1 hash.
     """
-    if revision and mirror.contains_revision(revision):
+    if rev_type == 'hash' and mirror.contains_revision(revision):
       if options.verbose:
         self.Print('skipping mirror update, it has rev=%s already' % revision,
                    timestamp=False)
@@ -900,7 +874,7 @@
                     lock_timeout=getattr(options, 'lock_timeout', 0))
     mirror.unlock()
 
-  def _Clone(self, ref, remote_ref, revision, url, options):
+  def _Clone(self, revision, url, options):
     """Clone a git repository from the given URL.
 
     Once we've cloned the repo, we checkout a working branch if the specified
@@ -926,7 +900,7 @@
 
     template_dir = None
     if hasattr(options, 'no_history') and options.no_history:
-      if revision and gclient_utils.IsGitSha(revision):
+      if gclient_utils.IsGitSha(revision):
         # In the case of a subproject, the pinned sha is not necessarily the
         # head of the remote branch (so we can't just use --depth=N). Instead,
         # we tell git to fetch all the remote objects from SHA..HEAD by means of
@@ -967,18 +941,17 @@
       gclient_utils.rmtree(tmp_dir)
       if template_dir:
         gclient_utils.rmtree(template_dir)
-    self._UpdateBranchHeads(options, ref, remote_ref, fetch=True)
-    if revision:
-      revision = self._AutoFetchRevision(options, revision)
-    self._Checkout(options, revision or remote_ref, quiet=True)
+    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:
       # Squelch git's very verbose detached HEAD warning and use our own
       self.Print(
         ('Checked out %s to a detached HEAD. Before making any commits\n'
          'in this repo, you should use \'git checkout <branch>\' to switch to\n'
          'an existing branch or use \'git checkout %s -b <branch>\' to\n'
-         'create a new branch for your work.') % (
-             revision or remote_ref, self.remote))
+         'create a new branch for your work.') % (revision, self.remote))
 
   def _AskForData(self, prompt, options):
     if options.jobs > 1:
@@ -1078,8 +1051,7 @@
       raise gclient_utils.Error('git version %s < minimum required %s' %
                                 (current_version, min_version))
 
-  def _EnsureValidHeadObjectOrCheckout(self, ref, remote_ref, revision, options,
-                                       url):
+  def _EnsureValidHeadObjectOrCheckout(self, revision, options, url):
     # Special case handling if all 3 conditions are met:
     #   * the mirros have recently changed, but deps destination remains same,
     #   * the git histories of mirrors are conflicting.
@@ -1098,7 +1070,7 @@
           '%s' % e)
         )
         self._DeleteOrMove(options.force)
-        self._Clone(ref, remote_ref, revision, url, options)
+        self._Clone(revision, url, options)
       else:
         raise
 
@@ -1198,7 +1170,6 @@
     if quiet:
       checkout_args.append('--quiet')
     checkout_args.append(ref)
-    checkout_args.append('--')
     return self._Capture(checkout_args)
 
   def _Fetch(self, options, remote=None, prune=False, quiet=False,
@@ -1233,7 +1204,7 @@
     # Return the revision that was fetched; this will be stored in 'FETCH_HEAD'
     return self._Capture(['rev-parse', '--verify', 'FETCH_HEAD'])
 
-  def _UpdateBranchHeads(self, options, ref, remote_ref, fetch=False):
+  def _UpdateBranchHeads(self, options, fetch=False):
     """Adds, and optionally fetches, "branch-heads" and "tags" refspecs
     if requested."""
     need_fetch = fetch
@@ -1249,20 +1220,16 @@
                     '^\\+refs/tags/\\*:.*$']
       self._Run(config_cmd, options)
       need_fetch = True
-    # Make sure we fetch the ref we're asked to sync, if any.
-    if ref and not ref.startswith(('refs/remotes',)):
-      config_cmd = ['config', 'remote.%s.fetch' % self.remote,
-                    '+%s:%s' % (ref, remote_ref), '--add']
-      self._Run(config_cmd, options)
-      need_fetch = True
     if fetch and need_fetch:
       self._Fetch(options, prune=options.force)
 
-  def _AutoFetchRevision(self, options, revision):
+  def _AutoFetchRef(self, options, revision):
     """Attempts to fetch |revision| if not available in local repo.
 
     Returns possibly updated revision."""
-    if revision and not scm.GIT.IsValidRevision(self.checkout_path, revision):
+    try:
+      self._Capture(['rev-parse', revision])
+    except subprocess2.CalledProcessError:
       self._Fetch(options, refspec=revision)
       revision = self._Capture(['rev-parse', 'FETCH_HEAD'])
     return revision