| Index: gclient_scm.py
|
| diff --git a/gclient_scm.py b/gclient_scm.py
|
| index aef2969640e6e938f6deda6d70713d4d53d3cebd..64adbe3a7956ba39f1f0e04555528f6879692701 100644
|
| --- a/gclient_scm.py
|
| +++ b/gclient_scm.py
|
| @@ -276,16 +276,98 @@ class GitWrapper(SCMWrapper):
|
| cwd=self.checkout_path,
|
| filter_fn=GitDiffFilterer(self.relpath).Filter, print_func=self.Print)
|
|
|
| + def _ParseRevision(self, revision):
|
| + """Analyzes a revision string and determines what the repository-local
|
| + refspec is for that revision.
|
| +
|
| + This function handles:
|
| + - Standard local revision paths (refs/heads, refs/remotes)
|
| + (e.g., refs/heads/master)
|
| + - Remote-relative mirrored revision paths (e.g., origin/master)
|
| + - Non-remote revision paths (anything else begining with 'refs/') are
|
| + assumed to be refs on our remote.
|
| + - Everything else (branches, commits)
|
| +
|
| + Params:
|
| + revision: The revision string, fitting into one of the categories above,
|
| + to resolve
|
| +
|
| + Return (dict): Refspec analysis dictionary including:
|
| + - 'revision' (str): The refspec to use to reference the revision in the
|
| + local repository (after it's been fetched).
|
| + - 'type' (str): The type of revision, one of ('branch', 'hash')
|
| + - 'mapped' (bool): If 'True', the revision is part of the default mapped
|
| + revision set ('refs/heads/*'); if 'False', it needs to be
|
| + explicitly fetched before it can be referenced at 'revision'.
|
| + - 'fetch_refspec' (str): The parameter to use to fetch this revision from
|
| + its origin.
|
| + """
|
| + result = {
|
| + 'revision': revision,
|
| + 'type': 'branch',
|
| + 'mapped': True,
|
| + 'fetch_refspec': revision,
|
| + }
|
| + #
|
| + # If 'revision' begins with some prefixes, chop them off to reference
|
| + # local refs.
|
| + for prefix, replacement in (
|
| + ('refs/heads/', None),
|
| + ('refs/remotes/%s/' % (self.remote), None),
|
| + ('%s/' % (self.remote), 'refs/heads/'),
|
| + ):
|
| + if revision.startswith(prefix):
|
| + local_revision = revision[len(prefix):]
|
| + if replacement is not None:
|
| + revision = revision.replace(prefix, replacement)
|
| +
|
| + # Trim 'prefix' to make 'revision' relative to our standard
|
| + # 'refs/heads/*' fetch mapping.
|
| + result['revision'] = revision
|
| + result['fetch_refspec'] = local_revision
|
| + return result
|
| +
|
| + # If 'revision' begins with 'refs/', treat it as an unmapped remote ref
|
| + if revision.startswith('refs/'):
|
| + result['fetch_refspec'] = '%s:%s' % (revision, revision)
|
| + result['mapped'] = False
|
| + return result
|
| +
|
| + # Assume 'revision' is either a local branch or a commit
|
| + result['type'] = 'hash'
|
| + return result
|
| +
|
| + def _Fetch(self, options, revision=None):
|
| + cfg = gclient_utils.DefaultIndexPackConfig()
|
| + fetch_cmd = cfg + ['fetch', self.remote]
|
| +
|
| + if revision is not None:
|
| + refdict = self._ParseRevision(revision)
|
| + fetch_cmd.append(refdict['fetch_refspec'])
|
| +
|
| + if not options.verbose:
|
| + fetch_cmd.append('--quiet')
|
| + fetch_cmd.append('--prune')
|
| + self._Run(fetch_cmd, options, retry=True)
|
| +
|
| + def _FetchIfRemote(self, options, revision):
|
| + refdict = self._ParseRevision(revision)
|
| + if not refdict['mapped']:
|
| + self._Fetch(options, revision)
|
| +
|
| def _FetchAndReset(self, revision, file_list, options):
|
| """Equivalent to git fetch; git reset."""
|
| + self._UpdateBranchHeads(options)
|
| +
|
| + # Fetch default fetch targets ('master'...)
|
| + self._Fetch(options)
|
| + # Fetch our specific revision from the remote, if it's not in the default
|
| + # fetch set.
|
| + self._FetchIfRemote(options, revision)
|
| +
|
| quiet = []
|
| if not options.verbose:
|
| - quiet = ['--quiet']
|
| - self._UpdateBranchHeads(options, fetch=False)
|
| -
|
| - cfg = gclient_utils.DefaultIndexPackConfig(self.url)
|
| - fetch_cmd = cfg + ['fetch', self.remote, '--prune']
|
| - self._Run(fetch_cmd + quiet, options, retry=True)
|
| + quiet.append('--quiet')
|
| self._Run(['reset', '--hard', revision] + quiet, options)
|
| if file_list is not None:
|
| files = self._Capture(['ls-files']).splitlines()
|
| @@ -339,15 +421,9 @@ class GitWrapper(SCMWrapper):
|
|
|
| url = self._CreateOrUpdateCache(url, options)
|
|
|
| - if revision.startswith('refs/'):
|
| - rev_type = "branch"
|
| - elif revision.startswith(self.remote + '/'):
|
| - # For compatibility with old naming, translate 'origin' to 'refs/heads'
|
| - revision = revision.replace(self.remote + '/', 'refs/heads/')
|
| - rev_type = "branch"
|
| - else:
|
| - # hash is also a tag, only make a distinction at checkout
|
| - rev_type = "hash"
|
| + refdict = self._ParseRevision(revision)
|
| + revision = refdict['revision']
|
| + rev_type = refdict['type']
|
|
|
| if (not os.path.exists(self.checkout_path) or
|
| (os.path.isdir(self.checkout_path) and
|
| @@ -360,7 +436,7 @@ class GitWrapper(SCMWrapper):
|
| if not self.relpath == '.':
|
| self._DeleteOrMove(options.force)
|
| self._Clone(revision, url, options)
|
| - self._UpdateBranchHeads(options, fetch=True)
|
| + self._UpdateBranchHeads(options, fetch_revision=revision)
|
| if file_list is not None:
|
| files = self._Capture(['ls-files']).splitlines()
|
| file_list.extend([os.path.join(self.checkout_path, f) for f in files])
|
| @@ -371,7 +447,7 @@ class GitWrapper(SCMWrapper):
|
| return self._Capture(['rev-parse', '--verify', 'HEAD'])
|
|
|
| if not managed:
|
| - self._UpdateBranchHeads(options, fetch=False)
|
| + self._UpdateBranchHeads(options)
|
| self.Print('________ unmanaged solution; skipping %s' % self.relpath)
|
| return self._Capture(['rev-parse', '--verify', 'HEAD'])
|
|
|
| @@ -444,7 +520,7 @@ class GitWrapper(SCMWrapper):
|
| if verbose:
|
| self.Print(remote_output)
|
|
|
| - self._UpdateBranchHeads(options, fetch=True)
|
| + self._UpdateBranchHeads(options, fetch_revision=revision)
|
|
|
| # This is a big hammer, debatable if it should even be here...
|
| if options.force or options.reset:
|
| @@ -790,9 +866,13 @@ class GitWrapper(SCMWrapper):
|
| traceback.print_exc(file=self.out_fh)
|
| raise
|
| finally:
|
| - if os.listdir(tmp_dir):
|
| + if os.path.exists(tmp_dir) and os.listdir(tmp_dir):
|
| self.Print('_____ removing non-empty tmp dir %s' % tmp_dir)
|
| gclient_utils.rmtree(tmp_dir)
|
| +
|
| + # Fetch our specific revision
|
| + self._FetchIfRemote(options, revision)
|
| +
|
| if revision.startswith('refs/heads/'):
|
| self._Run(
|
| ['checkout', '--quiet', revision.replace('refs/heads/', '')], options)
|
| @@ -969,19 +1049,18 @@ class GitWrapper(SCMWrapper):
|
| env = scm.GIT.ApplyEnvVars(kwargs)
|
| return subprocess2.check_output(['git'] + args, env=env, **kwargs).strip()
|
|
|
| - def _UpdateBranchHeads(self, options, fetch=False):
|
| + def _UpdateBranchHeads(self, options, fetch_revision=None):
|
| """Adds, and optionally fetches, "branch-heads" refspecs if requested."""
|
| if hasattr(options, 'with_branch_heads') and options.with_branch_heads:
|
| config_cmd = ['config', 'remote.%s.fetch' % self.remote,
|
| '+refs/branch-heads/*:refs/remotes/branch-heads/*',
|
| '^\\+refs/branch-heads/\\*:.*$']
|
| self._Run(config_cmd, options)
|
| - if fetch:
|
| - cfg = gclient_utils.DefaultIndexPackConfig(self.url)
|
| - fetch_cmd = cfg + ['fetch', self.remote]
|
| - if options.verbose:
|
| - fetch_cmd.append('--verbose')
|
| - self._Run(fetch_cmd, options, retry=True)
|
| + if fetch_revision is not None:
|
| + self._Fetch(options)
|
| +
|
| + if fetch_revision is not None:
|
| + self._FetchIfRemote(options, fetch_revision)
|
|
|
| def _Run(self, args, options, **kwargs):
|
| cwd = kwargs.setdefault('cwd', self.checkout_path)
|
|
|