I've got a Git repository with plenty of commits that are under no particular branch, I can git show
them, but when I try to list branches that contain them, it reports back nothing.
I thought this is the dangling commits/tree issue (as a result of -D branch), so I pruned the repo, but I still see the same behavior after that:
$ git fetch origin
$ git fsck --unreachable
$ git fsck
No output, nothing dangling (right?). But the commit exists
$ git show 793db7f272ba4bbdd1e32f14410a52a412667042
commit 793db7f272ba4bbdd1e32f14410a52a412667042
Author: ...
and it is not reachable through any branch as
$ git branch --contains 793db7f272ba4bbdd1e32f14410a52a412667042
gives no output.
What exactly is the state of that commit? How can I list all commits in a similar state? How can I delete commits like those?
I accidentally hit the same situation and found my stashes contain reference to the unreachable commit, and thus the presumed unreachable commit was reachable from stashes.
These were what I did to make it truly unreachable.
To remove all dangling commits and those reachable from the reflogs do this:
But be certain that this is what you want. I recommend you read the man pages but here is the gist:
git gc
removes unreachable objects (commits, trees, blobs (files)). An object is unreachable if it isn't part of the history of some branch. Actually it is a bit more complicated:git gc
does some other things but they are not relevant here and not dangerous.Unreachable objects that are younger than two weeks are not removed so we use
--prune=now
which means "remove unreachable objects that were created before now".Objects can also be reached through the reflog. While branches record the history of some project, reflogs record the history of these branches. If you amend, reset etc. commits are removed from the branch history but git keeps them around in case you realize that you made a mistake. Reflogs are a convenient way to find out what destructive (and other) operations were performed on a branch (or HEAD), making it easier to undo a destructive operation.
So we also have to remove the reflogs to actually remove everything not reachable from a branch. We do so by expiring
--all
reflogs. Again git keeps a bit of the reflogs to protect users so we again have to tell it not to do so:--expire-unreachable=now
.Since I mainly use the reflog to recover from destructive operations I usually use
--expire=now
instead, which zaps the reflogs completely.Note that commits referred to from your reflog are considered reachable.
Pass
--no-reflogs
to convincegit fsck
to show them to you.Once your reflog entries are expired, those objects will then also be cleaned up by
git gc
.Expiry is regulated by the
gc.pruneexpire
,gc.reflogexpire
, andgc.reflogexpireunreachable
settings. Cf.git help config
.The defaults are all quite reasonable.
probably just needs to be
to also report on branches from remotes
I had the same issue, still after following all the advice in this thread:
If it's not a reflog and not a branch, ...it must be a tag!
I removed the tags with
git tag -d <tagname>
and redid the cleanup, and the old commits were gone.git gc --prune=<date>
defaults to prune objects older than two weeks ago. You could set a more recent date. But, git commands that create loose objects generally will run git gc --auto (which prunes loose objects if their number exceeds the value of configuration variable gc.auto).Are you sure that you want to delete these commits? gc.auto's default setting will ensure that the loose objects do not take up an unreasonable amount of memory, and storing loose objects for some amount of time is generally a good idea. That way, if you realize tomorrow that your deleted branch contained a commit you needed, you can recover it.