Search code examples
gitgit-reset

Are there any git commands that will explicitly destroy a commit?


Does Git ever truly destroy commits or just orphan them? I am aware that the git garbage collector will remove orphaned commits when run with git gc on whatever interval is set in git config. Are there any other cases that will delete a commit?


Solution

  • Git may truly destroy some object, including an unreferenced commit, eventually; but it may not. Read through to the end for why this is so.

    The most direct control is available through git prune, git repack, and git prune-packed. Running git gc runs all of these, and more. The main problem with assuming that git prune, or even git gc --prune=now, will do the trick is that objects that are now unreachable may have been packed.

    In general, you should expire reflogs first, since removing expired reflog entries may cause objects to become unreachable. Using git gc gets all of this done in the right order, in a relatively safe manner: expire reflogs first, then prune (carefully), then repack, then prune-packed. The prune step tosses loose objects that are unreferenced and themselves expired.1 After that, the other steps repack older objects that are in use, and delete objects that have just become packed.

    This sequence of events is why we need git repack -d, or even -ad, followed by git prune-packed. However, if some packs are marked with a .keep file, those packs will remain, and if they contain otherwise-unreferenced objects, those objects will remain as well.


    1Since multiple Git commands may be run in parallel, it's wise to provide a grace period during which unreferenced objects live on, in case they are about to become referenced. Note that it is git gc that inserts the default 14 day grace period; git prune defaults to --expire=now rather than --expire=2.weeks.ago. This gives Git commands 336 hours to get their work done and cement references: e.g., git commit runs git write-tree followed by git commit-tree, and this tree and commit are unreferenced until git commit also updates the current branch name. Normally, this whole process takes just a few milliseconds, so 336 hours should be plenty of time.