Search code examples
gitrebase

Why does git rebase from remote?


I have a branch A, with several commits I want to squash, and from this branch has been created an other branch (B), with several commits I want also to squash.

I rebased the branch A from develop, and squashed all commits in one commit. OK.

I then try to rebase B from the A (whose SHA1 changed because of previous commit), but the window Git shows me the list of commits since the old branch A, not the squashed one.

Why ?


Solution

  • Here's what's happening, made clearer with some graphs. Initially, you start out with 3 branches, develop, A, and B:

    a-b-c               < develop
         \
          d-e-f         < A
               \
                g-h-i   < B
    

    Now you run git rebase -i develop A (equivalent to git checkout A && git rebase -i develop) and squash all commits:

    a-b-c               < develop
        |\
        | f'            < A
         \
          d-e-f-g-h-i   < B
    

    f' is the squashed commit. Branch B still references the unrelated and untouched commit i. Since every commit keeps an (immutable) reference to its parent commit (or parent commits), the original, unsquashed commit d, e, and f are still reachable from B.

    When you now run git rebase A B (or the equivalent git checkout B && git rebase A), then Git will replay all commits reachable from B that are not reachable from A. Looking at the graph it is clear that these are commits d..i (d, e, f, g, h, i), including the original, unsquashed commits. Consequently, you would end up with a-b-c-f'-d-e-f-g-h-i – but since f' already contains the changes of d, e, and f, they cannot be reapplied and will result in conflicts.

    What you want to do instead is specify the upstream and new base explicitly: git rebase --onto A f B (interactive rebase is supported as well with -i). Running this command results in the following history:

    a-b-c               < develop
         \
          f'            < A
           \
            g'-h'-i'    < B
    

    or a-b-c-f'-i' if you decide to squash g..i into a single commit.