Skip to content

Rewriting Git History

ahmadia edited this page Nov 19, 2014 · 3 revisions

@cekees notes in a PR that he'd like to amend the commit in its predecessor to avoid changes in three of the modified files. If he had made the commit but nobody else was using it, this would be a simple procedure:

Method 1: Amending the commit

# check out the commit directly
$ git checkout 902178ed1bf6068b638d11c769dcc51fab6b2188
# now revert the changes by checking out the previous version of the modified files
$ git checkout HEAD~1 -- src/LinearSolvers.py src/Transport.py test/poisson_3d_c0p1_n.py
# now amend this commit
$ git commit --amend
# voila!
$ git log -1 --stat
commit ef769dadd584d8725d2800d2645b3790f8677c79
Author: cekees <cekees@gmail.com>
Date:   Fri Nov 14 13:15:21 2014 -0500

    added some point-wise convenience functions for FemTools

    - onElement test for ReferencElement objects
    - pointwise getInverseMap for ElementMaps objects
    - fixed some bugs in MultilevelProjections

 src/FemTools.py | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++
 src/mesh.h      | 14 ++++++++++++++
 2 files changed, 65 insertions(+)

Now he can do a hard reset of his branch to this commit and force-push it to the open PR:

git checkout fem_tools_extensions
git reset --hard ef769dadd584d8725d2800d2645b3790f8677c79
# the '+' here indicates that we're intentionally resetting the remote branch
git push origin +fem_tools_extensions

This is the preferred technique for fixing a commit that has no descendents, which is usually a commit that you have just made, but not shared or used yourself in a subsequent commit.

Method 2: Writing a "fix" commit

Unfortunately, I had already started in another PR using this commit, so Chris needs some way to communicate this to me. Since he's amended a commit that I've now pulled into my repository, a future merge would end up re-introducing the changes he was trying to remove. The safest thing to do in this situation is to revert the changes into a new commit:

# check out the commit directly
$ git checkout 902178ed1bf6068b638d11c769dcc51fab6b2188
# now revert the changes by checking out the previous version of the modified files
$ git checkout HEAD~1 -- src/LinearSolvers.py src/Transport.py test/poisson_3d_c0p1_n.py
# now create a *new* commit instead of amending
$ git commit -m "Revert incomplete changes"
# voila!
$ git log -1 --stat

The new commit can be pushed to his PR, but now there's no need to force-push, since the old commit is still in the branch history. The advantage to this approach is that a future merge will take this "fix" into account, and won't reintroduce the old changes. This is the preferred method for undoing changes that have been merged into master or otherwise gone into use.

Method 3: Interactive Rebase

A final technique that an integrator (that is, the person responsible for merging proposed changes in to the master branch) can use is to perform an interactive rebase. An interactive rebase is a powerful technique that allows you to rewrite an entire set of historical changes in a Git repository, including in this case amending the commit in question and "replaying" all of the subsequent commits onto the modified commit. I won't fully introducing interactive rebasing here, but this is the series of Git commands I used to execute the rebase:

gco ahmadia/point_gauges
git rebase -i 902178ed1bf6068b638d11c769dcc51fab6b2188~1
git checkout HEAD~1 -- src/LinearSolvers.py src/Transport.py test/poisson_3d_c0p1_n.py
git commit --amend
git rebase --continue

Use of this technique is only recommended for advanced Git users, especially since the first two methods are capable of resolving the majority of situations where such a need arises.

More reading on rewriting history: Atlassian Pro Git