Home > Software engineering >  git checkout removes git log history
git checkout removes git log history

Time:11-06

Ive got two commits in my git log

commit a38056f9da4dcf48c188e79fe632b3624e6ffef4 (HEAD, main)
Author: user
    commit 2

commit 801608941f024901799208e328bb0d3908c2ba7a
Author: user

    commit 1

, I wanna go back to commit 1, which I can do with git checkout 801608941f024901799208e328bb0d3908c2ba7a. Problem is, when I do this, my git log turns to

commit 801608941f024901799208e328bb0d3908c2ba7a
Author: user

    commit 1

And I can't find the SHA for commit 2 anymore. If I find commit 2's SHA by scrolling up to my first git log then git checkout commit 2's SHA, I go back to commit 2's snapshot as expected, but it's annoying that the git log doesn't show commit 2's SHA Is this normal? If so, how am I supposed to find the commit history for commit 2 now?

CodePudding user response:

TL;DR

Run:

git switch main

or:

git checkout main

Long-ish

Git hasn't removed anything. You just can't see it. Here's why: The git log command works by going backwards.

History, in a Git repository, is nothing more or less than the commits that are in that repository. Git finds commits by their big ugly hash IDs:

  • a38056f9da4dcf48c188e79fe632b3624e6ffef4
  • 801608941f024901799208e328bb0d3908c2ba7a

Git desperately needs these hash IDs in order to find the commits. But these hash IDs are very bad for humans (quick, is 801608941f024901799208e328bb0d3908c2ba7a the same as 801608941f024901797208e328bb0d3908c2ba7a?). So we don't normally use the hash IDs. Git does use the hash IDs, but Git provides us branch names and tag names and many other kinds of names. Those are what we normally use.

Each commit stores two things:

  • Directly, each commit stores some metadata, giving information such as the name and email address of the person who made the commit.
  • Indirectly, each commit stores a full snapshot of every file.

All of this stuff, once inside a commit, is completely read-only: it can never be changed, not even by Git itself, as long as you retrieve that commit by its big ugly hash ID. That hash ID is the "true name" of that commit.

Inside each commit's metadata, Git stores a list of previous commits, and this is how git log actually works: it starts at some particular commit, which has some particular big ugly hash ID, and git log shows you that commit. Then git log uses that commit's metadata to find the previous commit. The git log command now shows that commit, and then uses its metadata to step backwards once again.

The end result of this is that you see all the commits, one at a time, backwards, from wherever you start (or is it "end"?), following the internal chains that Git forges as you work:

... <-F <-G <-H

Here the uppercase letters stand in for those big ugly hash IDs, so H is short for some Hash. If you give Git the hash ID of H, it shows you H, then it uses H to find the hash ID for G. We say that commit H points to earlier commit G. Git then shows G, and then uses G's metadata to find the hash ID for F, and so on.

But: if you run git log without giving it a hash ID, how does it know where to start? The answer is that Git has the notion of a current commit, which Git finds using the special, magic name HEAD.

HEAD usually contains a branch name

Because hash IDs are bad for humans, we tend not to use them. Git offers us the option of creating any number of branch names. Each branch name stores exactly one hash ID, and whatever hash ID is "inside" the branch name, that's the last commit that's "on" that branch:

... <-F <-G <-H   <--main

Here the name main points to H, just as H points to G and G points to F and so on. So git log main will start at H and work backwards.

We can have as many branch names as we like, all pointing directly to commit H:

...--G--H   <-- main, develop, feature1, feature2

To remember which name is the current name, Git will attach the special name HEAD to exactly one of these branch names:

...--G--H   <-- main, develop, feature1 (HEAD), feature2

Here we are "on" branch feature1—running git status will say on branch feature1—and git log, without any starting point, will use the name feature1 to find commit H, and show that commit, then G and F and whatever.

Detached HEAD mode

If, however, we run:

git checkout <hash-id>

for some big ugly hash ID, Git stores that hash ID directly in the special name HEAD, and now we have:

...--F   <-- HEAD
      \
       G--H   <-- main, develop, ...

Running git log now starts at commit F and works backwards. What happened to commits G and H? Nothing at all: they're still in there. You just need to start git log at commit H, to see them. To do that, you can run:

git log main

since main points to H; or you can git switch main or git checkout main to re-attach HEAD to the branch name, so that git log starts there.

Note that commits are often on many branches

Given:

...--G--H   <-- main, develop, feature1 (HEAD), feature2

which branch(es) are the commits on?

The answer is: all of them!

If we make a new commit now, though, here's what happens:

...--G--H   <-- main, develop, feature2
         \
          I   <-- feature1 (HEAD)

Git will not only write out the new commit, giving it a new unique hash ID (the big ugly hash IDs are unique), it will set up the new commit so that it points back to commit H—the commit we were using as the current commit just a moment ago—and then write the new commit's hash ID into the current branch name.

This is how branches normally grow, one commit at a time.

There is a whole lot more, but these are essential things to know, to use Git.

  •  Tags:  
  • git
  • Related