Reputation: 812
I have a branch and a tag both named test
.
If I have an unambiguous ref like heads/test
or refs/heads/test
, how can I checkout without going into detached state?
git checkout heads/test
I want to save the current HEAD state and later return to it:
# Exit if dirty
# Save HEAD
ref=$(git symbolic-ref -q --short HEAD)
if [ -z "$ref" ] ; then # Detached
ref=$(git rev-parse HEAD)
fi
echo $ref
# Do Stuff that changes HEAD
git checkout master
# Restore HEAD
git checkout $ref
::Edited for clarity::
Upvotes: 1
Views: 1187
Reputation: 489748
Edit: Adamarla notes that git symbolic-ref --short -q HEAD
misbehaves in this ambiguous-reference situation. For instance:
$ mkdir t
$ cd t
$ git init
Initialized empty Git repository in ...
$ echo test symref > README
$ git add README
$ git commit -m initial
[master (root-commit) ee3448a] initial
1 file changed, 1 insertion(+)
create mode 100644 README
$ git branch test
$ git tag test
$ git checkout test
warning: refname 'test' is ambiguous.
Switched to branch 'test'
$ git symbolic-ref -q --short HEAD
heads/test
so my original answer below has an important error. I'd use a slight variant on the OP's alternative:
if headRef=$(git symbolic-ref -q HEAD); then
headRef=${headRef#refs/heads/}
else
headRef=$(git rev-parse HEAD)
fi
Note that:
git symbolic-ref
errors (returns a failed status, as well as not printing the symbolic reference) when HEAD
is not a symbolic reference:
if ! ref=$(git symbolic-ref -q --short HEAD); then
... HEAD is detached ...
(In other words, you do not need a separate zero-length string test.)
HEAD
can only be a symbolic reference to a branch name, not a tag name.
Most Git commands behave as described in the gitrevisions documentation, in that the bare name test
will resolve to the commit pointed-to by refs/tags/test
rather than refs/heads/test
. The git checkout
command, however, behaves differently: git checkout test
will prefer the branch.
Thus, if the git symbolic-ref
succeeds, your short reference is suitable to pass to git checkout
. If it fails, you do need to run git rev-parse HEAD
to get the hash ID to pass to the later git checkout
.
Upvotes: 3
Reputation: 812
I was hoping for a plumbing checkout command that would handle branch checkout from a qualified ref, but have come up with this workaround:
# Save Checkout
refBranch=$(git symbolic-ref -q HEAD)
refDetach=$(git rev-parse HEAD)
echo "refBranch [$refBranch]"
echo "refDetach [$refDetach]"
# Do Stuff
git checkout master --quiet
echo "Now on [$(git symbolic-ref -q --short HEAD)]"
# Restore Checkout
if [[ ${refBranch:0:11} == "refs/heads/" ]] ; then
echo "Checkout Branch [${refBranch:11}]"
git checkout ${refBranch:11} --quiet
else # Detached
echo "Checkout Detach [$refDetach]"
git checkout $refDetach --quiet
fi
Or if we are guaranteed to always get the refs/heads/
prefix:
# Save Checkout
if headRef=$(git symbolic-ref -q HEAD); then
headRef=${headRef:11}
else
headRef=$(git rev-parse HEAD)
fi
echo "headRef [${headRef}]"
# Do Stuff
git checkout master --quiet
echo "Now on [$(git symbolic-ref -q --short HEAD)]"
# Restore Checkout
git checkout $headRef --quiet
Upvotes: 1
Reputation: 45819
From the docs:
Branch to checkout; if it refers to a branch (i.e., a name that, when prepended with "refs/heads/", is a valid ref), then that branch is checked out. Otherwise, if it refers to a valid commit, your HEAD becomes "detached" and you are no longer on any branch (see below for details).
So: If there exist both a branch and a tag with the name test
, then git checkout test
will checkout the branch without detaching. It is true that git gives a warning, but it still will consistently behave this way.
But on the other hand, if you give it anything other than a "branch name" as defined above - which includes explicitly qualifying the branch, as in heads/test
- then it detaches.
So it seems if you have heads/test
and want to check out without detaching, the (porcelain) procedure is to strip the heads/
off and just checkout test
.
If you really don't want to do that... well, I don't know a "safe" alternative that's any better.
You could use git symbolic-ref
git symbolic-ref -m "Checkout test" HEAD refs/heads/test
git reset HEAD -- .
git checkout -- .
but notice you have to fully qualify the ref in this case - so you still can't just use heads/test
. Also if you don't manually provide a "reason" in a -m
argument, you'll muck up the reflog. And you have to then update the index and work tree. You could write a script or alias to do all that, I guess.
Or you could directly manipulate the .git/HEAD
file, but if you mess it up git will no longer recognize the repo until you fix it.
Also, you said:
Checking out tags/test works fine.
Not sure what this means, since (a) your question was how to check out heads/test
without entering detached head, but (b) checking out a tag always enters detached head. And:
I am getting the ref from:
git symbolic-ref -q --short HEAD
Under what circumstances would this return tags/test
? Maybe I misunderstood because you put that on the same line where you were talking about checkout out the tag... do you mean that git symbolic-ref -q --short HEAD
is returning heads/test
, and you're storing that and later trying to check out to it?
In that case, see above; but also in that case, you could leave out the --short
and at least that would be one step closer to making the symbolic-ref
approach work...
Upvotes: 2