I add my code change to repo, I can see the commit id by git log -n1
.
Then I combine a new change to pre-commit id using git add -u; git commit --amend; git push -f
.
Then I suddenly forget what's the diff with second change. But the pre-commit id disappers in git log
history.
How to find the missing commit?
TL;DR: use the reflogs.
The git commit --amend
operation has the effect of "shoving the old commit aside". If we draw commits, as found by a branch name, we get this kind of picture:
... <-F <-G <-H <--branch
Each of the uppercase letters here stands in for a Git commit hash ID. The branch name, in this case branch
, holds the hash ID of the latest commit H
, so that:
git rev-parse branch
prints out this hash ID. Git is able to use this stored hash ID to read commit H
from the database of commits.
Commit H
itself stores, in its metadata, the raw hash ID of earlier commit G
. So having read H
from the database, Git can fish out this hash ID, and use that to retrieve commit G
from the database.
Having retrieved G
, Git has the metadata from G
, which gives the hash ID of earlier commit F
, so Git can retrieve commit F
from the database. This of course has metadata, including the hash ID of another still-earlier commit, and so on down the line.
When we add a new commit in the normal way, what Git does is:
I
, using the next letter of the alphabet).I
: our name, our email address, the current date-and-time, and so on. In this metadata, Git includes the hash ID of the current commit H
, as the parent of commit I
.git commit-tree
to write all of this to the objects database as a new commit. The act of writing the commit produces the new hash ID: the one we'll call I
.I
in the current branch name.Step 4 causes branch
to point to new commit I
, instead of to existing commit H
. When we draw the result, we have:
... <-F <-G <-H <-I <--branch
and as you can see, the process of starting at the end and working backwards will now find commit I
, then commit H
, then commit G
, and so on.
What git commit --amend
does is to bypass the current commit by having new commit I
point directly back to the parent(s) of the current commit. In this case, since H
points to G
, Git will make new commit I
point directly to G
:
H
/
... <-F <-G <-I <--branch
Commit H
does still exist, for some time anyway. But there is nowhere to find its hash ID. If it's still on your screen, or in scrollback in a window, you can grab it that way: Git needs the hash ID; a branch name is just a way to provide a hash ID. If you can cut and paste the hash ID, that will do fine.
But here's what the word reflog means above. Whenever Git replaces the value stored in a branch name, or in the special name HEAD
, Git saves the previous value in a log. This log is optional but it defaults to "on" for your repositories (at least if you use conventional Git; there are no guarantees if you are using Eclipse or JGit or some other non-Git program that merely uses the same database formats). So when both git commit
and git commit --amend
overwrite the hash ID stored in the branch name, they save the previous hash ID in the log.
Running git reflog
with no arguments will dump out the reflog for HEAD
. This is the most active reflog with the most "stuff" in it, so it has a lot more information to sift through.
Running git reflog branch
, if you were on a branch named branch
, dumps out the reflog for branch
. (If you were on main
or master
, use git reflog main
or git reflog master
.) This will show the updates to that branch name.
In both cases, you can find the hash ID of the commit from just before the git commit --amend
. This will be the hash ID of the commit that got shoved out of the way. So you can now run:
git diff <hash> HEAD
for instance to compare the two commits.
There are additional ways to use the reflogs. See the documentation for git reflog
and the documentation for writing revision ID expressions.