Git Reflog To The Rescue
I spent a lot of time hearing people talk about git reflog
without actually knowing what it did or how to use it. It sounded advanced, and I didn't understand it well enough to know when it would be useful to me. So hopefully this article helps to explain when it can be useful, and how to use it.
This isn't something that I use often, and when I do use it I refer back to a note which is the basis of this post. So I thought I'd share it in the hope that it helps others in the same scenario.
tl;dr
Use git reflog
to restore dropped commits. When you type git reflog
in a terminal, you'll see a list of entries like this:
735e24f41 HEAD@{5}: commit: My clever commit message
Get the hash of the commit that you want to restore (it's the first column), then cherry-pick that commit:
git cherry-pick 735e24f41
And there you go, your commit is back.
One Particular Example Where I Use git reflog
This is a very specific example, due to using git rebase
and squashing a merged PR into one commit in GitHub.
When I work with git, I use rebasing to keep branches in sync. The reasons why are a separate article, but suffice it to say, I broadly prefer rebasing over merging.
Here's a common scenario that I encounter: we have the main
branch as the source of truth for our team's project. It's got root-commit
as its latest commit. Then I create a feature branch, search-1
, for the first part of the new search feature that I'm working on. Then, before my PR for search-1
gets merged, I have some more work to do, and I branch from search-1
to search-2
.
-> main
- root-commit
-> search-1
- c1
- c2
- c3
-> search-2
- c4
If you've spent much time in bigger teams, with multiple developers on one git repo and a bit of a queue to get PRs merged, this is probably quite familiar.
When I merge search-1
, all of the commits in that branch get squashed down to one single merge commit. (Again, that's what I like, the merits of this approach are another article.)
-> main
- root-commit
- squashed-merge-commit-from-search-1
-> search-2
- c1 (originally in merged branch)
- c2 (originally in merged branch)
- c3 (originally in merged branch)
- c4 (new to this branch)
So search-2
is out of sync. It still has the whole history of search-1
, as well as its own commits.
When I rebase search-2
on top of main
, I want to drop c1
, c2
and c3
, since they're part of main
now, through the merge commit. So I drop them, in an interactive rebase.
And here's where we get to the point!
Sometimes, I mess up, and drop the wrong commits. It's not always as simple as this contrived example, messages aren't always clear. And you don't always have a list of which commits were in search-1
and which are new to search-2
.
I just had this happen to me where I had a few commits in search-2
, and dropped too many because I didn't know where the line was between search-1
and search-2
, and had to look up how to use git reflog
to save myself, yet again.
Restoring A Dropped Commit
So this is the whole point behind this article. I hope the example above was clear to follow, but even if not, it doesn't matter. What matters is:
If you had previously had a commit, and you've somehow deleted that commit, chances are you can still get it back!
git reflog
will show you the history of the actions you've taken. Things like commits, checkouts, pulls and pushes, resets, all sorts of actions. Not just commits, like you'd find in git log
.
So to restore a commit that I didn't mean to drop, I'd look for this line in the git reflog
output:
735e24f35 HEAD@{5}: commit: My clever commit message
That's the commit that I want to restore. So I just cherry pick that out of the ether:
git cherry-pick 735e24f35
And then I get my commit back!
Aside
It took me an embarrassingly long time to realise it was "ref log", not "re flog". I'm not sure what I thought was being flogged again.
Reference
There's a great article on the GitHub blog: "How to undo (almost) anything with Git".
Published on 25 October 2024