问题
I'm writing a shell script which switches to a specified commit, makes a copy of the index in /tmp
, resets HEAD
to original position, then runs a command in the temporary directory:
orig_head=$(git rev-parse -q HEAD) # "refs/heads/master"
git checkout "$1"
# copy index to /tmp/...
git checkout "$orig_head"
# run command in /tmp/...
However, this script gives me the same "'detached HEAD' state" message as when I run git checkout refs/heads/master
:
Note: checking out 'refs/heads/master'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:
git checkout -b new_branch_name
HEAD is now at c82ad67... Use vector and binary search for dictionary
How can I store and restore the position of HEAD
properly?
I would prefer to avoid using reset HEAD@{1}
, as that seems messy.
回答1:
Most git commands don't really care whether a name is a branch name. The obvious exceptions (there may be more, these are off the top of my head) are git branch
and, of course, git checkout
.
For the non-exceptions, the rules for resolving branch names to SHA-1s are listed in gitrevisions. For git branch
it's easier: some arguments are obviously branch-names, e.g., in git branch new/branch/name
, new/branch/name
is a branch name, even though it presumably doesn't exist yet.
The checkout
command can't do that: given git checkout xyz
, xyz
might be a branch name, or a tag name, or any of the other forms in gitrevisions. Of course, to be one of the funny syntax forms like HEAD~5
it has to have the special ~
character in it, but even an unadorned name might be a branch name, or might not. (If it follows -b
, as in git checkout -b new/branch
then is is definitely a branch-name, but that's not the case here.)
Anyway, the short answer is that git checkout
has its own special rules, different from those listed in gitrevisions: a name is a branch name if adding refs/heads/
in front works to turn it into an existing branch name.
This fails with refs/heads/master
since refs/heads/refs/heads/master
is not an existing branch name.1 Hence you need to strip off the refs/heads/
part yourself.
You could do this after-the-fact, but there's an easier version: git symbolic-ref
has --short
to leave the refs/heads/
part off. Since HEAD
should only be a symbolic reference to a branch (never a symbolic ref to a tag for instance), and you know you're inquiring about HEAD
, just do:
orig_head=$(git symbolic-ref -q --short HEAD)
You do need one more bit, which is to remember whether the system was in detached HEAD state to start with, and if so, what SHA-1 that goes with. So:
sha1=$(git rev-parse HEAD) || exit 1 # should never fail
orig_head=$(git symbolic-ref -q --short HEAD) && symbolic=true || symbolic=false
or something along those lines.
1You can create such a branch. Don't. :-)
来源:https://stackoverflow.com/questions/30006786/why-is-there-a-difference-between-master-and-heads-master