Git Reflog
In a previous article, I talked about orphaned commits. First, let’s talk about why they are bad. They are bad because they will eventually be removed by git’s garbage collection algorithm, losing the data forever. The reflog is designed to prevent that (for a time). In my previous post, I mentioned that: Any commit that is not reachable from a ref (tags, branches, or HEAD) will be removed. There is actually one other thing that references commits that will also cause them to not be garbage collected and that is the reflog.
As you go about using git you are bound to occasionally accumulate some orphaned items. We’ve talked about orphaned commits, but you can also orphan other things, such as files. If you add a file, but instead of immediately commiting it you unstage it, that unstaged file will be orphaned. If you want to manually search your repository and find what is orphaned you should start with git help fsck
.
Garbage Collection
You can manually run the garbage collection routine git gc
. You may want to do this after you intentionally remove a commit with a git reset
or a git rebase
or use something like git filter-branch
to remove some files. These are just a few cases where you may want to run garbage collection manually.
git gc
is automatically run at several key points, most notably fetch and push operations. At those points, git looks at a whole series of configuration parameters to determine what if anything gets pruned (deleted). To see them all type in git help config
and search for “gc”.
Reflog to the Rescue
Now this may sound dire. It may sound like it is easy to lose data if you accidentally orphan something important. If you do orphan something that you care about, you do want to act quickly. Time is not your friend (although you typically have at least 30 days from when it was orphaned as that is the default reflog expiration). Luckily there is this thing called the reflog which helps to keep references to orphaned commits alive (for a time) so that they can be recovered if needed. To view the reflog, just type git reflog
.
The reflog records the new commit id every time the HEAD moves. As long as a commit is reachable from a commit referenced in the reflog, it won’t be garbage collected. Now as you can imagine if we did nothing this list would eventually grow to infinity. There are some config settings that control when data is pruned from the reflog. See git help config
for more info (in particular search for gc.reflogExpire, gc.reflogExpireUnreachable and gc.pruneexpire). Remember you can also use git fsck
to find orphaned commits and other orphaned objects.
Recovery
Recovery is rather simple once you know the commit id. You simply checkout the commit using git checkout commitID
. Then you can examine it and determine if it is worth saving. If so, then you can either tag it with git tag tagname
or create a branch there using git switch -c branchname
. NOTE: if you have a chain of commits you want to save (an abandoned branch), you only need to create a tag or branch for the most recent commit. Also just by checking out the commit you have added it to the top of the reflog, so that buys you some more time to make a decision.
Tips for Preventing Orphans
It is possible to retrieve orphaned commits, but you have to notice that you’ve orphaned it and then go through git reflog
or git fsck
to find the commit id. It is a much better idea to avoid orphaning commits in the first place.
Here are a few tips to prevent creating orphaned commits:
- Before you
git reset
, create a branch usinggit switch -c newbranch
or a tag usinggit tag mytag
That way those commits will still be reachable. - Don’t check in on a detached head. if you need to do that create a branch using git
switch -c newbranch
- Don’t delete a branch until its content has been merged into another branch. That way those commits will still be reachable. If you do want to delete a branch because you don’t intend on pursuing it any further, consider tagging it before deleting it so it is still reachable.
NOTE: if you are using tags to avoid orphaned commits, just know that tags don’t automatically get pushed unless you specify git push --tags