Rewriting History in Git

Combining several commits into one

If you've set up remote tracking, this is as simple as:

$ git rebase -i

Select to "squash" (s) commit(s) (up) into the previous commit:

pick 908147acbe25f One commit
s    34568147acbee Another commit, combine it with the previous one
s    23568147gdfae Yet another commit, combine it with the previous ones

Once you quit the interactive rebase screen (which is vim in my case), you'll be prompted for how to concatinate (or just rewrite) the combined commit message for the now three combined (squashed) commits.

Removing files from a commit

First, find the SHA of the commit to which you want to rewrite the history. In this example, acbe25f is the first commit I'm not interested in rewriting:

12e36ee       This commit combines a lot of different changes
acbe25f       Everything is safe and sound

$ git rebase -i acbe25f

Select to edit the commit you're interested in:

edit 12e36ee

Then, remove the files from the given commit:

$ git reset HEAD~1 file1 file2

Re-commit the commit using the existing commit message, excluding the two files:

$ git commit --amend

Then, create a seperate commit for file1 and file2:

    $ git commit file1 file2 -m "Split file1 & 2 out into their own commit."

Finish the rebasing:

$ git rebase --continue

Finally, force push the changes to rewrite the changes on the (remote) server too:

$ git push --force

The --force is only needed if you're rewriting already pushed commits. Naturaylly, git push --force is potentially dangerous and heed must be taken.

Removing some lines from a file in a commit

The first steps are just like above:

$ git rebase -i acbe25f
edit 12e36ee
$ git reset HEAD~1 pom.xml
$ git commit --amend

At this point, now do an interactive git add:

$ git add -i
staged     unstaged path
1:        +8/-8        +8/-8 pom.xml

*** Commands ***
1: status         2: update     3: revert       4: add untracked
5: patch          6: diff       7: quit         8: help

Select patch p,

Git will then ask you:

Stage this hunk [y,n,q,a,d,/,j,J,g,s,e,?]?

For the commits you want to include in the next commt, select y, for the others you want to include in a different commit, select n.

Once you've answered yes and no to all the hunks, the hunks that you've said "yes" to, are already staged for commit, so it's important that you now just do a regular commit with -a or file name:

$ git commit -m "Committed a couple of hunks in pom.xml"

If you now do a git status or git diff, you'll see that there're still changes to be committed, i.e. the hunks you said n to in the interactive add:

$ git diff

I find that normally, at this point I can just commit the remaining hunks in one commit (but you could of course repeat the interactive add to split the file commt into even finer grained commits):

$ git commit -m "Committed the remaining hunks in pom.xml"

Finally, force push your changes to the remote(s) server:

$ git push --force

Again, this is will rewrite the server history, so heed must be taken.

Licensed under CC BY Creative Commons License ~ ✉ torstein.k.johansen @ gmail ~ 🐘 ~ 🐦 @torsteinkrause