| Index: gclient_scm.py
|
| diff --git a/gclient_scm.py b/gclient_scm.py
|
| index e51a0eecb254bb47d937bc64347db6519350b340..057c82a30ec636d36533b045ac412b0756f0e8cb 100644
|
| --- a/gclient_scm.py
|
| +++ b/gclient_scm.py
|
| @@ -199,7 +199,7 @@ class GitWrapper(SCMWrapper):
|
| rev_type = "hash"
|
|
|
| if not os.path.exists(self.checkout_path):
|
| - self._Clone(rev_type, revision, url, options.verbose)
|
| + self._Clone(revision, url, options.verbose)
|
| files = self._Run(['ls-files']).split()
|
| file_list.extend([os.path.join(self.checkout_path, f) for f in files])
|
| if not verbose:
|
| @@ -218,39 +218,40 @@ class GitWrapper(SCMWrapper):
|
|
|
| cur_branch = self._GetCurrentBranch()
|
|
|
| - # Check if we are in a rebase conflict
|
| - if cur_branch is None:
|
| - raise gclient_utils.Error('\n____ %s%s\n'
|
| - '\tAlready in a conflict, i.e. (no branch).\n'
|
| - '\tFix the conflict and run gclient again.\n'
|
| - '\tOr to abort run:\n\t\tgit-rebase --abort\n'
|
| - '\tSee man git-rebase for details.\n'
|
| - % (self.relpath, rev_str))
|
| -
|
| # Cases:
|
| - # 1) current branch based on a hash (could be git-svn)
|
| - # - try to rebase onto the new upstream (hash or branch)
|
| - # 2) current branch based on a remote branch with local committed changes,
|
| - # but the DEPS file switched to point to a hash
|
| + # 0) HEAD is detached. Probably from our initial clone.
|
| + # - make sure HEAD is contained by a named ref, then update.
|
| + # Cases 1-4. HEAD is a branch.
|
| + # 1) current branch is not tracking a remote branch (could be git-svn)
|
| + # - try to rebase onto the new hash or branch
|
| + # 2) current branch is tracking a remote branch with local committed
|
| + # changes, but the DEPS file switched to point to a hash
|
| # - rebase those changes on top of the hash
|
| - # 3) current branch based on a remote with or without changes, no switch
|
| + # 3) current branch is tracking a remote branch w/or w/out changes,
|
| + # no switch
|
| # - see if we can FF, if not, prompt the user for rebase, merge, or stop
|
| - # 4) current branch based on a remote, switches to a new remote
|
| + # 4) current branch is tracking a remote branch, switches to a different
|
| + # remote branch
|
| # - exit
|
|
|
| # GetUpstreamBranch returns something like 'refs/remotes/origin/master' for
|
| # a tracking branch
|
| # or 'master' if not a tracking branch (it's based on a specific rev/hash)
|
| # or it returns None if it couldn't find an upstream
|
| - upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
|
| - if not upstream_branch or not upstream_branch.startswith('refs/remotes'):
|
| - current_type = "hash"
|
| - logging.debug("Current branch is based off a specific rev and is not "
|
| - "tracking an upstream.")
|
| - elif upstream_branch.startswith('refs/remotes'):
|
| - current_type = "branch"
|
| + if cur_branch is None:
|
| + upstream_branch = None
|
| + current_type = "detached"
|
| + logging.debug("Detached HEAD")
|
| else:
|
| - raise gclient_utils.Error('Invalid Upstream')
|
| + upstream_branch = scm.GIT.GetUpstreamBranch(self.checkout_path)
|
| + if not upstream_branch or not upstream_branch.startswith('refs/remotes'):
|
| + current_type = "hash"
|
| + logging.debug("Current branch is not tracking an upstream (remote)"
|
| + " branch.")
|
| + elif upstream_branch.startswith('refs/remotes'):
|
| + current_type = "branch"
|
| + else:
|
| + raise gclient_utils.Error('Invalid Upstream: %s' % upstream_branch)
|
|
|
| # Update the remotes first so we have all the refs.
|
| for _ in range(10):
|
| @@ -279,7 +280,14 @@ class GitWrapper(SCMWrapper):
|
| if options.force or options.reset:
|
| self._Run(['reset', '--hard', 'HEAD'], redirect_stdout=False)
|
|
|
| - if current_type == 'hash':
|
| + if current_type == 'detached':
|
| + # case 0
|
| + self._CheckClean(rev_str)
|
| + self._CheckDetachedHead(rev_str)
|
| + self._Run(['checkout', '--quiet', '%s^0' % revision])
|
| + if not printed_path:
|
| + print("\n_____ %s%s" % (self.relpath, rev_str))
|
| + elif current_type == 'hash':
|
| # case 1
|
| if scm.GIT.IsGitSvn(self.checkout_path) and upstream_branch is not None:
|
| # Our git-svn branch (upstream_branch) is our upstream
|
| @@ -380,7 +388,7 @@ class GitWrapper(SCMWrapper):
|
| file_list.extend([os.path.join(self.checkout_path, f) for f in files])
|
|
|
| # If the rebase generated a conflict, abort and ask user to fix
|
| - if self._GetCurrentBranch() is None:
|
| + if self._IsRebasing():
|
| raise gclient_utils.Error('\n____ %s%s\n'
|
| '\nConflict while rebasing this branch.\n'
|
| 'Fix the conflict and run gclient again.\n'
|
| @@ -441,17 +449,26 @@ class GitWrapper(SCMWrapper):
|
| base_url = self.url
|
| return base_url[:base_url.rfind('/')] + url
|
|
|
| - def _Clone(self, rev_type, revision, url, verbose=False):
|
| + def _Clone(self, revision, url, verbose=False):
|
| """Clone a git repository from the given URL.
|
|
|
| - Once we've cloned the repo, we checkout a working branch based off the
|
| - specified revision."""
|
| + Once we've cloned the repo, we checkout a working branch if the specified
|
| + revision is a branch head. If it is a tag or a specific commit, then we
|
| + leave HEAD detached as it makes future updates simpler -- in this case the
|
| + user should first create a new branch or switch to an existing branch before
|
| + making changes in the repo."""
|
| if not verbose:
|
| # git clone doesn't seem to insert a newline properly before printing
|
| # to stdout
|
| print ""
|
|
|
| clone_cmd = ['clone']
|
| + if revision.startswith('refs/heads/'):
|
| + clone_cmd.extend(['-b', revision.replace('refs/heads/', '')])
|
| + detach_head = False
|
| + else:
|
| + clone_cmd.append('--no-checkout')
|
| + detach_head = True
|
| if verbose:
|
| clone_cmd.append('--verbose')
|
| clone_cmd.extend([url, self.checkout_path])
|
| @@ -473,21 +490,14 @@ class GitWrapper(SCMWrapper):
|
| continue
|
| raise e
|
|
|
| - if rev_type == "branch":
|
| - short_rev = revision.replace('refs/heads/', '')
|
| - new_branch = revision.replace('heads', 'remotes/origin')
|
| - elif revision.startswith('refs/tags/'):
|
| - short_rev = revision.replace('refs/tags/', '')
|
| - new_branch = revision
|
| - else:
|
| - # revision is a specific sha1 hash
|
| - short_rev = revision
|
| - new_branch = revision
|
| -
|
| - cur_branch = self._GetCurrentBranch()
|
| - if cur_branch != short_rev:
|
| - self._Run(['checkout', '-b', short_rev, new_branch],
|
| - redirect_stdout=False)
|
| + if detach_head:
|
| + # Squelch git's very verbose detached HEAD warning and use our own
|
| + self._Run(['checkout', '--quiet', '%s^0' % revision])
|
| + 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 origin -b <branch>' to\n" \
|
| + "create a new branch for your work." % revision
|
|
|
| def _AttemptRebase(self, upstream, files, verbose=False, newbase=None,
|
| branch=None, printed_path=False):
|
| @@ -570,12 +580,63 @@ class GitWrapper(SCMWrapper):
|
| raise gclient_utils.Error('git version %s < minimum required %s' %
|
| (current_version, min_version))
|
|
|
| + def _IsRebasing(self):
|
| + # Check for any of REBASE-i/REBASE-m/REBASE/AM. Unfortunately git doesn't
|
| + # have a plumbing command to determine whether a rebase is in progress, so
|
| + # for now emualate (more-or-less) git-rebase.sh / git-completion.bash
|
| + g = os.path.join(self.checkout_path, '.git')
|
| + return (
|
| + os.path.isdir(os.path.join(g, "rebase-merge")) or
|
| + os.path.isdir(os.path.join(g, "rebase-apply")))
|
| +
|
| + def _CheckClean(self, rev_str):
|
| + # Make sure the tree is clean; see git-rebase.sh for reference
|
| + try:
|
| + scm.GIT.Capture(['update-index', '--ignore-submodules', '--refresh'],
|
| + self.checkout_path, print_error=False)
|
| + except gclient_utils.CheckCallError, e:
|
| + raise gclient_utils.Error('\n____ %s%s\n'
|
| + '\tYou have unstaged changes.\n'
|
| + '\tPlease commit, stash, or reset.\n'
|
| + % (self.relpath, rev_str))
|
| + try:
|
| + scm.GIT.Capture(['diff-index', '--cached', '--name-status', '-r',
|
| + '--ignore-submodules', 'HEAD', '--'], self.checkout_path,
|
| + print_error=False)
|
| + except gclient_utils.CheckCallError, e:
|
| + raise gclient_utils.Error('\n____ %s%s\n'
|
| + '\tYour index contains uncommitted changes\n'
|
| + '\tPlease commit, stash, or reset.\n'
|
| + % (self.relpath, rev_str))
|
| +
|
| + def _CheckDetachedHead(self, rev_str):
|
| + # HEAD is detached. Make sure it is safe to move away from (i.e., it is
|
| + # reference by a commit). If not, error out -- most likely a rebase is
|
| + # in progress, try to detect so we can give a better error.
|
| + try:
|
| + out, err = scm.GIT.Capture(
|
| + ['name-rev', '--no-undefined', 'HEAD'],
|
| + self.checkout_path,
|
| + print_error=False)
|
| + except gclient_utils.CheckCallError, e:
|
| + # Commit is not contained by any rev. See if the user is rebasing:
|
| + if self._IsRebasing():
|
| + # Punt to the user
|
| + raise gclient_utils.Error('\n____ %s%s\n'
|
| + '\tAlready in a conflict, i.e. (no branch).\n'
|
| + '\tFix the conflict and run gclient again.\n'
|
| + '\tOr to abort run:\n\t\tgit-rebase --abort\n'
|
| + '\tSee man git-rebase for details.\n'
|
| + % (self.relpath, rev_str))
|
| + # Let's just save off the commit so we can proceed.
|
| + name = "saved-by-gclient-" + self._Run(["rev-parse", "--short", "HEAD"])
|
| + self._Run(["branch", name])
|
| + print ("\n_____ found an unreferenced commit and saved it as '%s'" % name)
|
| +
|
| def _GetCurrentBranch(self):
|
| - # Returns name of current branch
|
| - # Returns None if inside a (no branch)
|
| - tokens = self._Run(['branch']).split()
|
| - branch = tokens[tokens.index('*') + 1]
|
| - if branch == '(no':
|
| + # Returns name of current branch or None for detached HEAD
|
| + branch = self._Run(['rev-parse', '--abbrev-ref=strict', 'HEAD'])
|
| + if branch == 'HEAD':
|
| return None
|
| return branch
|
|
|
|
|