How can I move HEAD back to a previous location? (Detached head) & Undo commits

In Git, I was trying to do a squash commit by merging in another branch and then resetting HEAD to the previous place via:

git reset origin/master

But I need to step out of this. How can I move HEAD back to the previous location?

I have the SHA-1 fragment (23b6772) of the commit that I need to move it to. How can I get back to this commit?


Before answering, let's add some background, explaining what this HEAD is.

First of all what is HEAD?

HEAD is simply a reference to the current commit (latest) on the current branch.
There can only be a single HEAD at any given time (excluding git worktree).

The content of HEAD is stored inside .git/HEAD and it contains the 40 bytes SHA-1 of the current commit.


detached HEAD

If you are not on the latest commit - meaning that HEAD is pointing to a prior commit in history it's called detached HEAD.

Enter image description here

On the command line, it will look like this - SHA-1 instead of the branch name since the HEAD is not pointing to the tip of the current branch:

Enter image description here

Enter image description here


A few options on how to recover from a detached HEAD:


git checkout

git checkout <commit_id>
git checkout -b <new branch> <commit_id>
git checkout HEAD~X // x is the number of commits to go back

This will checkout the new branch pointing to the desired commit.
This command will checkout to a given commit.
At this point, you can create a branch and start to work from this point on.

# Checkout a given commit.
# Doing so will result in a `detached HEAD` which mean that the `HEAD`
# is not pointing to the latest so you will need to checkout branch
# in order to be able to update the code.
git checkout <commit-id>

# Create a new branch forked to the given commit
git checkout -b <branch name>

git reflog

You can always use the reflog as well.
git reflog will display any change which updated the HEAD and checking out the desired reflog entry will set the HEAD back to this commit.

Every time the HEAD is modified there will be a new entry in the reflog

git reflog
git checkout HEAD@{...}

This will get you back to your desired commit

Enter image description here


git reset --hard <commit_id>

"Move" your HEAD back to the desired commit.

# This will destroy any local modifications.
# Don't do it if you have uncommitted work you want to keep.
git reset --hard 0d1d7fc32

# Alternatively, if there's work to keep:
git stash
git reset --hard 0d1d7fc32
git stash pop
# This saves the modifications, then reapplies that patch after resetting.
# You could get merge conflicts if you've modified things which were
# changed since the commit you reset to.
  • Note: (Since Git 2.7) you can also use the git rebase --no-autostash as well.

enter image description here


git revert <sha-1>

"Undo" the given commit or commit range.
The reset command will "undo" any changes made in the given commit.
A new commit with the undo patch will be committed while the original commit will remain in history as well.

# Add a new commit with the undo of the original one.
# The <sha-1> can be any commit(s) or commit range
git revert <sha-1>

This schema illustrates which command does what.
As you can see there, reset && checkout modify the HEAD.

Enter image description here


First reset locally:

git reset 23b6772

To see if you're on the right position, verify with:

git status

You will see something like:

On branch master Your branch is behind 'origin/master' by 17 commits, and can be fast-forwarded.

Then rewrite history on your remote tracking branch to reflect the change:

git push --force-with-lease // a useful command @oktober mentions in comments

Using --force-with-lease instead of --force will raise an error if others have meanwhile committed to the remote branch, in which case you should fetch first. More info in this article.


Quickest possible solution (just 1 step)

Use git checkout -

You will see Switched to branch <branch_name>. Confirm it's the branch you want.


Brief explanation: this command will move HEAD back to its last position. See note on outcomes at the end of this answer.


Mnemonic: this approach is a lot like using cd - to return to your previously visited directory. Syntax and the applicable cases are a pretty good match (e.g. it's useful when you actually want HEAD to return to where it was).


More methodical solution (2-steps, but memorable)

The quick approach solves the OP's question. But what if your situation is slightly different: say you have restarted Bash then found yourself with HEAD detached. In that case, here are 2 simple, easily remembered steps.

1. Pick the branch you need

Use git branch -v

You see a list of existing local branches. Grab the branch name that suits your needs.

2. Move HEAD to it

Use git checkout <branch_name>

You will see Switched to branch <branch_name>. Success!


Outcomes

With either method, you can now continue adding and committing your work as before: your next changes will be tracked on <branch_name>.

Note that both git checkout - and git checkout <branch_name> will give additional instructions if you have committed changes while HEAD was detached.