welw | About_me Blog WebAssembly

I checked out an older commit and suddenly my latest commit vanished

One of my biggest surprises when I started using Git was that after checking out an older commit, it looked like my latest commit had disappeared.

You can think of a commit as a snapshot of your repository at particular point in time. Each commit receives a unique commit id, for example 3f56bcaa. We'll understand it better through a series of practical exercises.

Exercise: Create 3 commits

Suppose the following scenario. I create a git repository with git init, with 3 commits: commit A, commit B and commit C. The commit history looks like this:

A <- B <- C <- main, HEAD

What happens when I check out commit B using git checkout B? Let's find out. Let's also have a look at hashes of each commit, as they allow to get to any place in git tree.

After checkout, the graph looks like this:

A <- B <- (C <- main = invisible) ^ HEAD

OK, I can see commit B, but what happened to commit C? I should be able to go back, right?

Exercise: Go back to branch main or C.

You can go back by checking out a branch. git checkout main solves the problem. You can also go back using git checkout . Once again we see the familiar tree:

A <- B <- C ← main

Commit C never disappeared, but we could not see it when we went to B. We were able to go back either using git checkout main or git checkout commit-hash.

Question: Why does commit C or branch main disappear, when we go back to commit B?

Commit C never disappears, because the history of your commits forms a direct acyclic graph. It is a graph in which each node points in one direction. The graph does not contain links back. However, Git lets you jump to any commit if you know its hash or have a branch pointing at it.

Exercise: Create a branch, and make commit to it

We will now create a branch and make a commit. In terminal, we write

git branch feature

A new branch feature was created. As we said previously, branch is a pointer to commit hash. It moves forward with each commit: A - B - C <- main, feature, HEAD

We created branch feature, but we are still on branch main. We need to check out the branch if we want to advance it with each new commit:

git checkout feature

We will now make a commit: git commit -m "D". We now see the following graph using git lg. Notice: feature moved, main did not move.

A - B - C - D <- feature, HEAD ^ main

This is the part that surprises many people. They think of a branch as a collection of commits. Branch is a movable label attached to a commit. It is a common misconception that git users associate a branch with specific commit. A branch is a label attached to a commit. It moves forward as you commit.

Exercise: Go back to branch main. Notice that git lg does not show feature.

What happens if we switch to branch main by git checkout main? Answer:

A - B - C <- main

Where is D? Did Git remove our branch completely? From C there is no path that reveals commit D. But Git still knows where D is.

We can go back to branch feature with git checkout feature. The git tree looks familiar again:

A - B - C - D <- feature ^ main

Exercise: Remove feature

What happens if we remove branch feature? Will D get deleted? Not immediately. It becomes an unreachable commit, which you can still checkout if you remember its commit id.

Be careful: Git will eventually garbage-collect unreachable commits. Until Git garbage collects it, you can still recover it using commit hash.

git branch -D feature git checkout <commit-hash>

Remember: