I would like to perform a set of operations that require saving and then restoring HEAD.
Here is a logical representation of what I would like to do:
# Save HEAD
original_head="$(cat .git/HEAD)"
# Perform other operations that muck with HEAD
# ...
# Restore HEAD
echo -n "$original_head" > .git/HEAD
The use of cat
and echo
above are merely illustrative; they are neither git native nor particularly reliable, because they will fail in newly created working trees where .git
is a file and not a directory.
A slightly better approach is to use symbolic-ref
:
# Save
original_head="$(git symbolic-ref HEAD)"
# Restore
git symbolic-ref HEAD "$original_head"
This approach is a little better, but it fails if HEAD was originally detached:
# Detach HEAD
git checkout $(git rev-parse HEAD)
# Try to get HEAD value to save:
git symbolic-ref HEAD
fatal: ref HEAD is not a symbolic ref
Here is what I am currently doing to work around this:
# Save HEAD
symbolic_head=''
detached_head=''
if git symbolic-ref HEAD >/dev/null 2>&1; then
symbolic_head="$(git symbolic-ref HEAD)"
else
detached_head="$(git rev-parse HEAD)"
fi
# Perform other operations that muck with HEAD
# ...
# Restore HEAD
if [[ -n $symbolic_head ]]; then
git symbolic-ref HEAD "$symbolic_head"
else
git update-ref HEAD "$detached_head"
fi
Is there a more direct way to say "get the value of HEAD, regardless of whether it is a symbolic ref or normal ref" and then "set the value of HEAD, regardless of whether it is a symbolic or normal ref"?
If you
# Perform other operations that muck with HEAD
while you've got a branch checked out your restore will do nothing, operations on an attached head affect the branch, that's just how attaching works.
What you're asking for is unusual enough that if it's really what you want then I think what you're doing, just saving the ref contents byte-for-byte and restoring them, is the best and possibly even the only way.
But there's better ways to do everything I've imagined (at least so far) you could be after with this.
edit: ahhh, from comments, also ones I didn't suss out too:
my current operations involve explicitly checking out a specific sha and then to moving
HEAD
back to its original place, so that my working tree and index match a different tree
HEAD
is a convention used for the convenience "porcelain" commands, the more conventional source-control-operatino commands. If you don't need what they do, use the underlying core commands, the "plumbing":
git read-tree -um $thatrev
is the low-level operation git checkout
runs¹; checkout's chief extra step beyond that is to update HEAD
to point at $thatrev
, if you spelled it as the short form of a ref starting refs/heads/
by attaching HEAD
to that (branch tip) ref. That's really all there is to it, the command has acquired a lot of other options to do various other convenient things along the way, but they're all just stringing together core commands to achieve the desired effect.
If your work tree's dirty you'll probably need git read-tree --reset -m
and perhaps some other cleanup, before the read-tree will work, Git doesn't like stepping on uncommitted work without you giving explicit orders.
For other uses, a sideband worktree with git worktree add `mktemp -d`; cd $_
, remove the worktree when done and as soon as you're done with the new branch worktree added to track what you were doing delete that too.
There's lots of options available on that command, if there's some kind of prep that needs doing, see the --no-checkout
option and maybe hardlink any needed files, git ls-files $options|cpio -pdl $thescratchtree
. And so on.
Me, I started with Git before the worktree command existed and still do any major-surgery sandboxing with git clone -ns . `mktemp -d`; cd $_
and have my way with the clone, then any results I want I push back. That still feels cleanest and safest to me, especially for major-surgery attempts.
p.s. and btw:
if you're sticking with the low-level stuff, original_head=$(cat .git/HEAD)
is a more-ungainly read original_head <.git/HEAD
, and you can just echo $original_head >.git/HEAD
to do the restore.
¹ checkout really runs git read-tree -um HEAD $thatrev
, see the docs, that lights up many more checks on what's being changed or abandoned.