I just migrated a project from Mercurial to Git. Mercurial adds empty commits when you add tags, so I ended up with empty commits in Git that I would like to remove.
How do I remove empty commits (commits that do not have any files in them) from Git?
Thanks.
To remove the last commit from git, you can simply run git reset --hard HEAD^ If you are removing multiple commits from the top, you can run git reset --hard HEAD~2 to remove the last two commits. You can increase the number to remove even more commits.
If your excess commits are only visible to you, you can just do git reset --hard origin/<branch_name> to move back to where the origin is. This will reset the state of the repository to the previous commit, and it will discard all local changes.
Git makes this process of pushing an empty commit super simple. It's like pushing a regular commit, except that you add the --allow-empty flag. git commit --allow-empty -m "Empty-Commit" You will now need to push this to the main directory. To do this, you can use this command: git push origin main.
One simple (but slow) way to do this is with git filter-branch
and --prune-empty
. With no other filters, no other commits will be altered, but any empty ones will be discarded (which will cause all subsequent commits to have new parent-IDs and is therefore still "rewrites history": not a big deal if this is your initial import from hg to git, but is a big deal if others are using this repository already).
Note all the usual caveats with filter-branch. (Also, as a side note, an "empty commit" is really one that has the same tree as the previous commit: it's not that it has no files at all, it's that it has all the same files, with the same modes, and the same contents, as its parent commit. This is because git stores complete snapshots for each commit, not differences from one commit to the next.)
Here is a tiny example that hides a lot of places you can do fancier things:
$ ... create repository ... $ cd some-tmp-dir; git clone --mirror file://path-to-original
(This first clone step is for safety: filter-branch
can be quite destructive so it's always good to start it on a new clone rather than the original. Using --mirror
means all its branches and tags are mirrored in this copy, too.)
$ git filter-branch --prune-empty --tag-name-filter cat -- --all
(Now wait a potentially very long time; see documentation on methods to speed this up a bit.)
$ git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
(This step is copied out of the documentation: it discards all the original branches from before running the filter. In other words, it makes the filtering permanent. Since this is a clone, that's reasonably safe to do even if the filter went wrong.)
$ cd third-tmp-dir; git clone --mirror file://path-to-filtered-tmp
(This makes a clean, nicely-compressed copy of the filtered copy, with no leftover objects from the filtering steps.)
$ git log # and other commands to inspect
Once you're sure the clone of the filtered clone is good, you don't need the filtered clone, and can use the last clone in place of the first. (That is, you can replace the "true original" with the final clone.)
Using --commit-filter should be faster than using --prune-empty
$ git filter-branch --tag-name-filter cat --commit-filter 'git_commit_non_empty_tree "$@"' -- --all
Then clean the backup refs as in torek's answer
$ git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
Taken from here: Git - remove commits with empty changeset using filter-branch
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With