In Git I can use an interactive rebase to re-write history, this is great because in my feature branch I made a ton of commits with partially working code as I explored different refactors and ways of getting it done.
I'd like to squash a lot of the commits together before rebasing or merging the branch onto master.
Some made up commits in order from first (top) to bottom (last)
1. Initial commit on feature branch "Automatic coffee maker UI"
2. Add hot chocolate as product
3. Add tea as product. Products are now generic
4. Create in memory data store for adapter tests
5. Cry because I can't get entity framework to create a composite key. Integration tests broken.
6. Implemented composite key!!
7. All tests green and feature done!
Lets say I want to keep commits 3, 4 and 7.
Using rebase I want to "squash" commits
Ideally in the interactive rebase I would do
1. squash
2. squash
3. pick (contains the work of 1 & 2)
4. pick
5. squash
6. squash
7. pick (contains the work of 5 & 6)
But that is backwards because squash merges a commit with its previous commit. I can't figure out how to make it squash forward.
Am I being difficult, and should I accept that won't work (I'd rather get it working), or is there a way to accomplish this?
I'm invoking this command with
git checkout My-feature-branch
git rebase master -i
Then I'm editing the list of commits that come up, and trying to finish it by saving the file and editing the editor, which typically works for me.
In case you are using the Tower Git client, using Interactive Rebase to squash some commits is very simple: just select the commits you want to combine, right-click any of them, and select the "Squash Revisions..." option from the contextual menu.
Squashing a commitIn the list of branches, select the branch that has the commits that you want to squash. Click History. Select the commits to squash and drop them on the commit you want to combine them with. You can select one commit or select multiple commits using Command or Shift .
You can run rebase interactively by adding the -i option to git rebase . You must indicate how far back you want to rewrite commits by telling the command which commit to rebase onto. Remember again that this is a rebasing command — every commit in the range HEAD~3..
The git commit --amend command is a convenient way to modify the most recent commit. It lets you combine staged changes with the previous commit instead of creating an entirely new commit.
It is indeed possible to squash a commit into the following commit during interactive rebase, and fully preserve the identity of the second of those commits (including author, date etc.).
The method is somewhat involved however, so native support by git rebase -i
would still be appreciated nevertheless.
I'll demonstrate with just three commits aaaaaaa A
, bbbbbbb B
and ccccccc C
, where we want to fold A
into B
and preserve B
's identity: (The method easily generalises to more commits)
git rebase -i aaaaaaa^
B
and exit the editor:
pick aaaaaaa A
edit bbbbbbb B
pick ccccccc C
B
, revert the latest commit twice, then continue:
git revert HEAD
git revert HEAD
git rebase --continue
Because reverting the revert restores the previous state, all following commits will apply cleanly.
The sequence of B
and Revert "B"
together has no effect, but the first of those commits carries the full identity of commit B (in fact, at this stage it still is commit B
). B
and Revert "B"
could be squashed together to form a no-op commit carrying the identity of commit B forward with a separate rebase -i --keep-empty aaaaaaa^
. This is very useful and recommended to prevent bogus merge conflicts, especially in more complex cases. However we'll skip that step here and just keep the commits together. Revert "Revert "B""
which will carry all the changes originally made by B
.rebase -i aaaaaaa^
again to move A
past both B
and Revert "B"
, while squashing B
into both Revert "B"
and A
:
pick bbbbbbb B
squash b111111 Revert "B"
squash a222222 A
squash b333333 Revert "Revert "B""
pick c444444 C
Because the sequence of B
and Revert "B"
has no effect, you can move any commits past it while creating only trivially resolvable merge conflicts.git mergetool
with a tool that can automatically resolve trivial conflicts, and let the tools do just that.git rebase --continue
and you are done.You either need to also reorder the commits so the to-be-kept commit comes before the to-be-squashed commits if this is feasible.
If this is not feasible, because you then would get conflicts you don't want to resolve, just make it
1. pick
2. squash
3. squash
4. pick
5. pick
6. squash
7. squash
When the squashes are done, you can edit the commit message to contain the message you like the final commits to have. Easy as pie. :-)
You might even be able to do
1. pick
2. fixup
3. squash
4. pick
5. pick
6. fixup
7. squash
Then I think there should only once the commit message editor being fired up, as with fixup the previous commit message is simply taken without launching the editor.
On squash when the commit message editor fires, you also get both commit messages, the one from the to-be-squashed-into and the to-be-squashed commit, so you then can simply delete the commit message you don't want to keep.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With