Replace a commit in git
I have a bad commit from a long time ago, I want to remove it completely from git history as if it never happened. I know the commit id let’s say 1f020. I have tried git rebase and remove it , but there is so many conflicts when rebasing that it is not possible that way. That commit is a simple 1 line change of code and pushing some files not related to the project.
So I want to write that 1 line code change and commit it, then replace somehow replace this new commit with the one a long time ago.
4 Solutions collect form web for “Replace a commit in git”
If the offending commit is in a private repository, what you want to do is not a big deal. Rewriting published git history is irritating for your collaborators, so be sure removing this line is worth the cost.
git-rebase documentation has a helpful passage.
git rebase [...] [<--onto newbase>] [<upstream>] [<branch>]
A range of commits could also be removed with rebase. If we have the following situation:
then the command
git rebase --onto topicA~5 topicA~3 topicA
would result in the removal of commits F and G:
This is useful if F and G were flawed in some way, or should not be part of
topicA. Note that the argument to
--ontoand the upstream parameter can be any valid commit-ish.
Assuming your history is linear and the offending commit is in your master branch, you can adapt the example above by running
git rebase --onto 1f020~ 1f020 master
For hairier situations, use interactive rebase. You may find it helpful to follow along with an example that merges two commits, but instead of marking the commit with
s for squash, remove the entire line to remove the commit from your history.
This is slightly complex but anyway, here is how it goes:
detach head and move to commit just AFTER that bad commit. use git log to find out the next commit after 1f020.
git checkout <SHA1-for-commit-just-after-bad-commit-1f020>
move HEAD to commit just BEFORE that bad commit, but leave the index and working tree as it is
git reset --soft <SHA1-for-just-previous-to-bad-commit-1f020>
Redo the commit just AFTER that bad commit re-using the commit message, but now on top of commit just BEFORE that bad commit. thus, removing that bad commit
git commit -C <SHA1-for-commit-just-after-bad-commit-1f020>
Re-apply everything from the commit just AFTER that bad commit onwards onto this new place
git rebase --onto HEAD <SHA1-for-commit-just-after-bad-commit-1f020> master
You should never remove commits from the history of git. You will run into problems later.
But if you know what you are doing, you can rewind your history locally, replace that commit with the current one, then replay all the commits on top again – then do a force push on your repository.
But I HIGHLY advise against any of this. Just do a new commit and live that there is a bad commit in history.
So, what you want is to:
Remove the bad commit from history, and
Not have to redo all the merges that came after the deleted commit. I.e. each commit that descends from the deleted commit should be kept as-is, only minimally changed to not include the changes erased from history.
Solutions based on
git rebase --onto and
git rebase -i fail on the 2nd point, because they require redoing all the merges that happened later. However, it is theoretically possible to simply recreate all these other commits as if the offending commit had never happened, provided that the bad commit is small enough that reverting it from its successors itself create conflicts.
As Michael and others pointed out, it is highly inadvisable to do this. It’s also a major undertaking that is almost certainly not worth the effort. But, for education value, here is an outline of a comprehensive solution that accomplishes the goal:
back up the repository.
git rev-listto generate a list of commits that starts with the bad commit and leads up to all the branch heads from which the commit is reachable.
initialize an empty map to map old commits to new ones.
for each commit in the list, do the following:
- check out the commit and revert the changes from the bad commit
- create a tree object out of the current tree using
git commit-treeto create a new commit with the new tree, old commit message, and parents translated from old parents using the above map (if some parents are not in the map, just use the old parents)
- register the new commit in the commit map.
go through tags and tag objects, and recreate them to point to the new commits, consulting the map.
go through branch heads and call
git update-refto point them to the new commits, consulting the map.
If the one-line change (and the unnecessary files) introduced by the commit doesn’t conflict with the changes from later commits, this process could be completely automated, and does not require you to manually redo all the merges and conflicts from the later commits.
The downside is that it still requires a rewrite of all the later commits, invalidating them if they had been shared elsewhere, and invalidating commit references in commit messages, such as those produced by