Although the now deleted answer by RyPeck RyPeck linked to a good way of doing this using git filter-branch
in the comments, I solved it differently, so I thought I'd share how I did it - if for no other purposes, then as a reference for future me (who will inevitably have to ask the Internet this question again, probably soon...):
First, I did an interactive rebase using git rebase -i master
. That opened up my text editor (I happen to have git configured to open Notepad++) with a file similar to this:
pick First commit message
pick Second commit message
pick This is where I touched the files I didn't want to touch
pick Another commit
pick This goes on for a while
pick I think you get the point
pick I'll stop now
followed by some comments about how to edit the file. What I did, was to change the line with the commit I wanted to alter to
...
edit This is where I touched the files I didn't want to touch
...
and then save and close the editor. What happens then is that git applies the three first commits, then stops and says
Stopped at <hash>... This is where I touched the files I didn't want to touch
You can amend the commit now, with
git commit --amend
Once you are satisfied with your changes, run
git rebase --continue
Then, I reset the files to the state they were prior to this commit, using something similar to
git reset HEAD^ src/C.java # undoes the changes to the file
git add src/C.java # stages the undoing of the changes
git commit --amend # amends the undoing to the last commit
After that, the commit has changed the file twice - first the change I made, and then back again - and git has squashed these two sets of changes into one, in which they cancel out and do nothing. I now run
git rebase --continue
to finish the job. If I now look at the history of src/C.java
, there's nothing about this set of commits.
This technique is extremely flexible - using the edit
mode of a commit, you can do any change you want to it.