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.
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
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 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 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 using
git switch -c newbranchor a tag using
git tag mytagThat 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