Why does 'Git rebase' squash commits into a previous commit, instead of the following commit?
Asked Answered
C

3

6

When I squash commits in a branch (using git rebase -i), I'm always annoyed that the squashed commit is combined with the older commit instead of the newer commit.

I don't understand why it was designed this way. When I commit a work in progress (WIP), it represents code that doesn't compile or isn't finished. When I finally commit the "it finally works!" commit and squash before merging, it makes far more sense for these WIP commits to be combined into the "it finally works!" commit, instead of being combined with the previous commit. Squashing a WIP essentially 'breaks' the previous commit with code that I know doesn't compile.

To get around this, my workflow is to squash the commits from "it works!" all the way back to one before the first WIP commit. But isn't this silly? What are other people doing, that makes squashing a WIP to a previous commit make sense?

Concertgoer answered 24/8, 2013 at 16:3 Comment(0)
T
3

With current Git versions (included from version 2.32.0 onwards; introduced in commit 9e3cebd97cbd47909e683e617d5ffa2781f0adaa), you can specify fixup -C in the todo list of an interactive rebase to select the commit message to be used when squashing/fixing up, e.g.

pick 456789 Finished topic XYZ
pick abcdef WIP your incomplete commit
fixup 012345 WIP resolving compiler errors
fixup 6789ab WIP adding unit tests
fixup cdef01 WIP refactoring
fixup -C 234567 Implemented feature ABC
pick 89abcd Another feature

Will end up with 2 commits: "Finished topic XYZ" and "Implemented feature ABC", without having to adapt any of the commit messages manually (so removing all those "WIP" lines is avoided).

If you still want to edit the final commit message, use -c instead of -C:

pick 456789 Finished topic XYZ
pick abcdef WIP your incomplete commit
fixup cdef01 WIP refactoring
fixup -c 234567 Implemented feature ABC
pick 89abcd Another feature

Any other additional steps can be performed with an exec line:

pick 456789 Finished topic XYZ
pick abcdef WIP your incomplete commit
fixup cdef01 WIP refactoring
fixup -C 234567 Implemented feature ABC
exec git commit --amend --reset-author -CHEAD
pick 89abcd Another feature

Before Git 2.32.0 you could work around this limitation with the following todo list:

pick 456789 Finished topic XYZ
pick abcdef WIP your incomplete commit
fixup cdef01 WIP refactoring
fixup 234567 Implemented feature ABC
exec git commit --amend -C 234567 # commit id from line above
pick 89abcd Another feature
Tiu answered 18/10, 2022 at 7:58 Comment(6)
Good point, upvoted (more up-to-date than my 2013 answer). What do you think of the Git 2.38 git merge -i --update-refs option?Schizopod
@Schizopod git merge -i? I assume you mean git rebase -i? :) I agree that --update-refs is good and useful, but does not apply to this question. How I understood this question is that it is about the commit messages, not about any other branches/refs.Tiu
Yes, I meant rebase, sorry. I agree it does not apply here, I just wanted your feedback on this new feature.Schizopod
@Schizopod rebase --update-refs is a very welcome and useful addition to Git. Something that has been missing to support my workflows ever since :)Tiu
AAAAAAAH this does it! Thank you for letting me know. 9 years later, I have an answer. :) It's also worth noting that -c (lowercase) will let you amend the commit message as well.Concertgoer
@Concertgoer good things come to those who wait :) right, totally forgot about -c. Updated my answer!Tiu
S
0

Then why not use git reset --soft, to reset HEAD (and only HEAD) back to the first WIP, and commit from there (with the index and working tree of the most recent and finally working WIP).

That way, you quickly squash those commits together.

See more at "How can I squash my last X commits together using git?"

That mirrors the conclusion I had on "Practical uses of git reset --soft?":

Each time:

  • you are satisfied with what you end up with (in term of working tree and index)
  • you are not satisfied with all the commits that took you to get there:

git reset --soft is the answer.


Note: with Git 2.42 (Q3 2023), when the user edits "rebase -i" todo file so that it starts with a "fixup", which would make it invalid, the command truncated the rest of the file before giving an error and returning the control back to the user.
Stop truncating to make it easier to correct such a malformed todo file.

See commit 9645a08 (22 Jul 2023) by Alex Henrie (alexhenrie).
(Merged by Junio C Hamano -- gitster -- in commit 8bfb359, 02 Aug 2023)

sequencer: finish parsing the todo list despite an invalid first line

Signed-off-by: Alex Henrie

Before the todo list is edited it is rewritten to shorten the OIDs of the commits being picked and to append advice about editing the list.
The exact advice depends on whether the todo list is being edited for the first time or not.
After the todo list has been edited it is rewritten to lengthen the OIDs of the commits being picked and to remove the advice.
If the edited list cannot be parsed then this last step is skipped.

Prior to ddb81e5 ("rebase-interactive: use todo_list_write_to_file() in edit_todo_list()", 2019-03-05, Git v2.22.0-rc0 -- merge) if the existing todo list could not be parsed then the initial rewrite was skipped as well.
This had the unfortunate consequence that if the list could not be parsed after the initial edit the advice given to the user was wrong when they re-edited the list.
This change relied on todo_list_parse_insn_buffer() returning the whole todo list even when it cannot be parsed.
Unfortunately if the list starts with a "fixup" command then it will be truncated and the remaining lines are lost.
Fix this by continuing to parse after an initial "fixup" commit as we do when we see any other invalid line.

Schizopod answered 24/8, 2013 at 17:35 Comment(1)
Because in a single branch, I often have several milestones. I like to squash the WIP (or fixup commits) into the following milestone commit after the branch is ready to merge. It lets me preserve some history at the end of the feature completion. It wouldn't be good to force me to squash early. What if the milestone is bad and I want to reset? I'd lose my history leading up to the milestone.Concertgoer
H
-1

Squashing your latest commit into older ones should keep the diffs from both. Just because you're 'picking' the older commit, doesn't mean your newer work that fixes the bugs in the work-in-progress commits is left out.

Try using git rebase -i HEAD~4, but replace 4 with the number of commits you want to combine, starting with the HEAD, which is presumably your working state.

There's a nice guide on using the interactive rebase at http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html.

Hod answered 8/1, 2014 at 17:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.