Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replace a commit in git

Tags:

git

github

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.

like image 873
sonnyhe2002 Avatar asked Nov 09 '12 22:11

sonnyhe2002


People also ask

How do I overwrite a git commit?

There are many ways to rewrite history with git. Use git commit --amend to change your latest log message. Use git commit --amend to make modifications to the most recent commit. Use git rebase to combine commits and modify history of a branch.

How do I replace in git?

The replace command lets you specify an object in Git and say "every time you refer to this object, pretend it's a different object". This is most commonly useful for replacing one commit in your history with another one without having to rebuild the entire history with, say, git filter-branch .

Can you amend an old commit?

The git commit –amend command lets you modify your last commit. You can change your log message and the files that appear in the commit. The old commit is replaced with a new commit which means that when you amend your old commit it will no longer be visible in the project history.

How do I change a commit after push?

Changing the latest Git commit message If the message to be changed is for the latest commit to the repository, then the following commands are to be executed: git commit --amend -m "New message" git push --force repository-name branch-name.


3 Answers

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.

The 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:

E---F---G---H---I---J  topicA

then the command

git rebase --onto topicA~5 topicA~3 topicA

would result in the removal of commits F and G:

E---H'---I'---J'  topicA

This is useful if F and G were flawed in some way, or should not be part of topicA. Note that the argument to --onto and 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.

like image 93
Greg Bacon Avatar answered Sep 26 '22 03:09

Greg Bacon


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
like image 28
CuriousMind Avatar answered Sep 24 '22 03:09

CuriousMind


So, what you want is to:

  1. Remove the bad commit from history, and

  2. 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.

  • use git rev-list to 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 write-tree
    • use git commit-tree to 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-ref to 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 git revert.

like image 31
user4815162342 Avatar answered Sep 24 '22 03:09

user4815162342