Actually deleting a commit is fairly difficult in git, by design. Many commands that people think delete commits (like rebase, or reset), actually just make those commits "unreachable" - causing the default output of various commands and tools to exclude them.
It's relatively rare that the reason to delete a commit warrants the cost. Sometimes a commit contains sensitive information (though, in that case, it's almost always best to consider the information compromised, whether or not you take efforts to scrub it from the repo). Maybe a commit contains excessively large binary files that are not present in any other commit, bloating the repo. If it just boils down to wanting to "hide" a "mistake" so the repo looks perfect, I wouldn't waste time on it.
But if you do want to remove the commit, here's what you need to know:
First, you have to remove all knowledge of the commit. Your reset
commands have made it "unreachable" (by parent pointers) from the branch on which you ddi the reset
s. If there are other branches that can reach the commits, they need to be reset
or rebased
away from the commit (or deleted). If there are tags on the removed commits, they need to be moved or deleted. There are special cases when other refs could point to the commits, but I'll assume they don't apply. (It would be things like replacements, or backup refs from filter-branch
... Basically if you can find the SHA for either commit in the .git/packed-refs
file or in any file under refs
, then some action is needed to remedy that.)
Once all refs are removed, the commit is "dangling"; but it still may be reachable via the reflog. You can try to expiire the reflogs
git reflog expire --expire=all --all
I've never had much luck with that (which probably just means I never remember the right arguments); I always end up doing something like
rm -r .git/logs
The downside in any event is that you lose all of your reflog information. You can be more selective about which reflogs you expire. (You probably need HEAD
and any branch from which the commits are (or were) reachable.) You could even use delete
instead of expire
to hunt down individual reflog entries. Again it all depends how much effort you're wanting to put into this.
So once there are no refs and no reflogs that can reach the commit, gc
can be used to physically delete the commit from the local repo.
git gc --aggressive --prune=now
But now there's still an issue: If the commits were ever pushed, the remote still has them; and pushing now won't delete them from the remote. (Pushing updates remote refs and, as needed, adds objects to fill in history; but it doesn't delete objects from the remote.)
If the remote is just a repo on a file share (or web server you control, or whatever): you can log into the server and clean it up the same way you cleaned up your local. (If you've pushed refs, then that part's already done; but you may have to clean up reflogs and you will have to run gc
.)
If the remote is hosted (github, gitlab, TFS, bitbucket...) then it depends on what access to gc
is provided by the host. In TFS (at least versions I've used) you're up a tree; at best you could delete and recreate the repo. Other host servers may provide the ability to trigger gc
, or may even run gc
automatically after certain events; you'd have to consult the docs for the hosting service/software.