gitrebasesquash

Git squash commits in middle of branch with child branches after that


I have the following repo:

A--B--C--D--E--F--G--H--I--J
                   \
                    K--L--M
main -> J
DEV -> M

I want to squash the commits between B and E so the repo looks like this:

A--N--F--G--H--I--J
          \
           K--L--M
main -> J
DEV -> M

I tried to do some interactive squash but the best i got was something like this:

  N--F'-G'-H'-I'-J'
 /  
A--B--C--D--E--F--G--H--I--J
                   \
                    K--L--M
remote/main -> J
main -> J'
DEV -> M

so the DEV branch its not atached where it supossed to be.

What is the way to do that?


Solution

  • You can use an interactive --rebase-merges after having a throw-away merge commit at the top:

    We construct this history:

    A--B--C--D--E--F--G--H--I--J--X   <-- main
                       \         /
                        K--L----M     <-- DEV
    

    using

    git checkout main
    git merge --strategy ours DEV   # constructs X
    

    The merge result (X) is irrelevant as we are going throw it away anyway. Using merge strategy ours guarantees that the merge command succeeds.

    Now you can do an interactive rebase:

    git rebase -i --rebase-merges --update-refs -- A
    

    In the "todo" script, specify the squash of B to E and also delete the merge command in the last line as shown here:

    label onto
    
    # Branch DEV
    reset onto
    pick b844192 B
    squash b844193 C
    squash b844194 D
    squash b844195 E
    pick b844196 F
    pick b844197 G
    label branch-point
    pick bb393a5 K
    pick c213a54 L
    pick b844198 M
    update-ref refs/heads/DEV
    
    label DEV
    
    reset branch-point # G
    pick b918c51 H
    pick b918c52 I
    pick b918c53 J
    # merge -C 9e9d655 DEV # Merge branch 'DEV' into main
    

    --updated-refs ensures that branch DEV is reseated on top of the rewritten side branch. You need Git version 2.38 for this. If yours is older, leave away the option and reseat the branch label manually.

    BTW, the script that git rebase prepares rebuilds commits B to G on the "side" branch DEV. This must not surprise you: the commits are on that branch.