I was working on some changes for TRT, back and forth. On completing the changes and submitting a pull request, I realized that I'd accidentally pulled commits from 'dev' into my feature branch (totally my bad, still learning the workflows here!)
I had about 20 commits on my feature branch, and there were about 30 commits from dev in the mix, most from before I'd branched from master. It may have been safe to merge those into master, but I don't personally feel comfortable making that call, and I was at the end of my day -- I just wanted to get my PR submitted.
I considered trying to cherry pick commits onto a clean branch -- but with 20 or so commits to work with and a few peppered in, that seemed tedious. Instead I opted to use rebase to play the new commits back cleanly onto master. Rebase offers an 'interactive' mode that makes it really easy to choose which commits to include and which to drop.
You may be familiar with the view offered by git rebase -i ...
.
pick 26278bd New commit 1
pick 577398a New commit 2
pick 1ba508d New commit 3
pick 2b15141 New commit 4
pick 65df0de New commit 5
# Rebase 3b16333..65df0de onto 3b16333 (5 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
This works nicely, but in my case I wanted to see the committer's name next to each commit so I could more easily see which commits to include. That's easy to do by modifying your ~/.gitconfig like so:
[rebase]
instructionFormat = %s [%an]
Or equivalently in your terminal
git config --global rebase.instructionFormat '%s [%an]'
Now...
pick 26278bd New commit 1 [Aaron Hipple]
pick 577398a New commit 2 [Aaron Hipple]
pick 1ba508d New commit 3 [Aaron Hipple]
pick 2b15141 New commit 4 [Aaron Hipple]
pick 65df0de New commit 5 [Aaron Hipple]
# Rebase 3b16333..65df0de onto 3b16333 (5 commands)
# ...
Now to actually run our rebase. The idea is to take a range of commits and replay them
onto the master branch, using the interactive option to allow us to drop certain commits
along the way. What I did was determine the last commit hash from my branch using git log
and then
git rebase -i <clean starting commit> <the last commit from our branch>
git rebase -i master 65c10a7
This results in an interactive rebase screen like this:
pick d7c5437 New commit 1 [Aaron Hipple]
pick 681a602 New commit 2 [Aaron Hipple]
pick d8d4b6e New commit 3 [Aaron Hipple]
pick bea7792 New commit 4 [Aaron Hipple]
pick be55ec2 New commit 5 [Aaron Hipple]
pick 9eebf2d Dirty commit [Aaron Hipple]
pick ca0f456 New commit 6 [Aaron Hipple]
pick b46558e New commit 7 [Aaron Hipple]
pick 409657c New commit 8 [Aaron Hipple]
pick bc3cf17 New commit 9 [Aaron Hipple]
pick e41d34b New commit 10 [Aaron Hipple]
# Rebase 24f4a73..e41d34b onto 24f4a73 (11 commands)
# ...
On this screen we can then just remove our dirty commit (or instruct it to 'drop' instead of 'pick') and then save our changes. This lands us in a detached head state, from which we can quickly save a new, clean branch, and push that. Easy peasy.