gitgit-rebasesquashgit-interactive-rebase

Squashing multiple commits that contain merge with main commits


Suppose we have the following scenario:

In the feature branch the commit history looks like this (main branch updates during the development process of feature branch and we want to keep feature branch up to date with main):

Now we want to squash all of these commits into a single commit. When I tried to use git rebase -i HEAD~7 => a list of 9 lines which contains the new commits from feature branch (A, B, C, D, E) and also the commits that were merged from main (not the merge commits the actual commits).

When I tried to use git rebase -i main => a list of 5 commits that does not contain the merge commits or the commits taken from main as in the above example

I don't understand why is this happening. I would expect the following list of commits:

git rebase -i main git rebase -i HEAD~7


Solution

  • The default behavior of git rebase is to ignore merges (as in: completely drop them from the list of rebased commits, be it with or without the --interactive flag).

    This behavior is pretty surprising when you are first confronted to it :)


    If you want to handle merges in your rebase history, you can use the explicit -r|--rebase-merges option :

    -r, --rebase-merges[=(rebase-cousins|no-rebase-cousins)]

    By default, a rebase will simply drop merge commits from the todo list, and put the rebased commits into a single, linear branch. With --rebase-merges, the rebase will instead try to preserve the branching structure within the commits that are to be rebased, by recreating the merge commits. Any resolved merge conflicts or manual amendments in these merge commits will have to be resolved/re-applied manually.

    [...]


    Another option could be to try to squash all the feature commits into 1, then add the merge result on top of it.

    If the merges from master didn't result in a lot of conflicts, then this should be doable :

    1. write down the current hash of your E commit,
    2. run git rebase -i HEAD~7, remove the commits coming from master from that list, mark commits B .. E to squash,
    3. save & exit, have git rebase do its job

    -> if you don't have too many conflicts, you should have the "content of feature" condensed in one single commit

    1. create a merge commit with the original content of E (the one you wrote down at step 1.)
    git merge -n master
    # if you have conflicts, ignore them:
    git reset .
    # restore the content from original E:
    git restore -SW -s <original hash of commit E>
    git commit