Another useful feature of revision control is to be able to view the working directory as it was at a specific point in history, by checking out a commit created at that point.
Suppose you added a new feature to a software product, and while testing it, you noticed that another feature added two commits ago doesn’t handle a certain edge case correctly. Now you’re wondering: did the new feature break the old one, or was it already broken? Can you go back to the moment you committed the old feature and test it in isolation, and come back to the present after you found the answer? With Git, you can.
To view the working directory at a specific point in history, you can check out a commit created at that point.
When you check out a commit, Git:
- Updates your working directory to match the snapshot in that commit, overwriting current files as needed.
- Moves the
HEADref to that commit, marking it as the current state you’re viewing.
→
[check out commit C2...]
Checking out a specific commit puts you in a "detached HEAD" state: i.e., the HEAD no longer points to a branch, but directly to a commit (see the above diagram for an example). This isn't a problem by itself, but any commits you make in this state can be lost, unless certain follow-up actions are taken. It is perfectly fine to be in a detached state if you are only examining the state of the working directory at that commit.
To get out of a "detached HEAD" state, you can simply check out a branch, which "re-attaches" HEAD to the branch you checked out.
→
[check out main...]
Target Checkout a few commits in a local repo, while examining the working directory to verify that it matches the state when you created the corresponding commit
Preparation
1 Examine the revision tree, to get your bearing first.
git log --oneline --decorate
Reminder: You can use aliases to reduce typing Git commands.
6304a59 (HEAD -> main) shapes.txt: Add some shapes
9f68246 (tag: 1.0) colours.txt: Add some colours
2ef8852 Update fruits list
8ca5cc6 (tag: 0.9) Add colours.txt, shapes.txt
542668f Add elderberries and figs into fruits.txt
ec49b17 Add fruits.txt
2 Use the checkout <commit-identifier> command to check out a commit other than the one currently pointed to by HEAD. You can use any of the following methods:
git checkout v1.0: checks out the commit taggedv1.0git checkout 0023cdd: checks out the commit with the hash0023cddgit checkout HEAD~2: checks out the commit 2 commits behind the most recent commit.
git checkout HEAD~2
Note: switching to 'HEAD~2'.
You are in 'detached HEAD' state.
# rest of the warning about the detached HEAD ...
HEAD is now at 2ef8852 Update fruits list
3 Verify HEAD and the working directory have updated as expected.
HEADshould now be pointing at the target commit- The working directory should match the state it was in at that commit (i.e., changes done after that commit should not be in the folder).
git log --oneline --decorate
2ef8852 (HEAD) Update fruits list
8ca5cc6 (tag: 0.9) Add colours.txt, shapes.txt
542668f Add elderberries and figs into fruits.txt
ec49b17 Add fruits.txt
HEAD is indeed pointing at the target commit.
But note how the output does not show commits you added after the checked-out commit.
You can use the following command to verify that the missing commits still exist in the repo.
git log --oneline --decorate --all --date-order
2a6daee (main) shapes.txt: Add some shapes
b9a03c0 (tag: 1.0) colours.txt: Add some colours
b57ad18 (HEAD) Update fruits list
aff416b (tag: 0.9) Add colours.txt, shapes.txt
8998bfb Add elderberries and figs into fruits.txt
9d3b329 Add fruits.txt
The --all switch tells git log to show commits from all refs, not just those reachable from the current HEAD. This includes commits from other branches, tags, and remotes.
The --date-order switch tells git log to order commits primarily by commit date, but never show a child commit before its parent. This way, the output stays chronologically and topologically sensible
even when multiple commits have identical timestamps (a situation that can happen when commits are generated programmatically by a tool/script e.g., Git-Mastery).
4 Go back to the latest commit by checking out the main branch again.
git checkout main
In the revision graph, double-click the commit you want to check out, or right-click on that commit and choose Checkout....
Click OK to the warning about ‘detached HEAD’ (similar to below).
The specified commit is now loaded onto the working folder, as indicated by the HEAD label.
To go back to the latest commit on the main branch, double-click the main branch.
If you check out a commit that comes before the commit in which you added a certain file (e.g., temp.txt) to the .gitignore file, and if the .gitignore file is version controlled as well, Git will now show it under ‘unstaged modifications’ because at Git hasn’t been told to ignore that file yet.
done!
If there are uncommitted changes in the working directory or staging area, Git proceeds with a checkout only if doing so would not overwrite or remove those changes. Here are some examples to illustrate this behavior:
- Example 1: There is a new uncommitted file in the working directory.
→ If the new file is untracked: Git proceeds with the checkout and keeps the file, provided the target commit does not contain a tracked file at the same path.
→ If the new file is staged: Git proceeds with the checkout only if the checkout would not overwrite or remove the staged version of the file. - Example 2: There is an uncommitted change to a file that would be overwritten by the version in the commit you want to check out.
→ Git aborts the checkout.
If the above examples are a bit too abstract for you to grasp at this point, the important thing to remember is that Git aims to prevent your uncommitted changes from being irrecoverably lost due to a checkout operation.
The Git stash feature temporarily sets aside uncommitted changes you’ve made (in your working directory and staging area), without committing them. This is useful when you’re in the middle of some work, but need to switch to another state (e.g., checkout a previous commit), and your current changes are not yet ready to be committed or discarded. You can later reapply the stashed changes when you’re ready to resume that work.