Search code examples
gitgit-mergegit-rebase

Merging branches whose last common ancestor was affected by rebase


I have a master branch and a topic branch which diverged from master at some "last common ancestor" commit.

I rewrote history on my master branch using git rebase, but forgot, that this included the commit that was the "last common ancestor" with my topic branch. In other words, if I now git log the master branch, it doesn't list the "last common ancestor" commit with topic anymore.

I assume the "last common ancestor" commit hasn't been garbage collected yet, since it is still referenced by the topic branch. But it isn't part of the master branch anymore.

What happens now, if I now try to merge topic with master?


Solution

  • For this sort of thing, I find it helps to draw the commits. Of course, since you can't find the exact commit, you'll be stuck with drawing some approximation—but maybe that will help you find the right commits.

    I have a master branch and a topic branch which diverged from master at some "last common ancestor" commit.

    So, let's draw that, as a sort of rough sketch:

    ...--c1--c2--c3--c4--m1--...--mn   <-- master
                        \
                         t1--...--tn   <-- topic
    

    where the c commits are common to both branches, the m commits are only on master, and the t commits are only on topic. Commit c4 is the last common one.

    I rewrote history on my master branch using git rebase, but forgot, that this included the commit that was the "last common ancestor" with my topic branch. In other words, if I now git log the master branch, I can't find the commit ID anymore of the "last common ancestor" commit with topic.

    Since rebase works by copying commits, then moving the branch name to point to the last-copied commit, let's draw that now, using ' suffixes to show the copied commits:

            c2'-c3'-c4'-m1'-...--mn'  <-- master
           /
    ...--c1--c2--c3--c4--m1--...--mn   [abandoned]
                       \
                        t1--...--tn   <-- topic
    

    While you can use git log on topic to find commit c4, it's not actually shared with master any more.

    I assume the "last common ancestor" commit hasn't been garbage collected yet, since it is still referenced by the topic branch. But it isn't part of the master branch anymore.

    That's exactly right: while m1-...-mn are "abandoned", c4 isn't. (Note that the hash IDs of these commits are probably in at least two reflogs: the one for master, and the one for HEAD. These reflog entries will keep the commits alive until the reflog entries themselves expire.)

    What happens now, if I now try to merge topic with master?

    The merge operation will find the first actually-common commit, which is now c1, and diff the contents of c1 (its snapshot) with the contents of mn (tip of master) to see what you changed on master, then diff c1 with tn to see what you changed on topic. It will then combine the two changes and, if that's successful, make a merge commit on its own:

            c2'-c3'-c4'-m1'-...--mn'
           /                       \
    ...--c1--c2--c3--c4             M   <-- master (HEAD)
                       \           /
                        t1--...--tn   <-- topic
    

    (The abandoned commits are still in there if they haven't been GC-ed, but they were in the way so I stopped drawing them.) A git log of master will now show the duplicated c4-and-c4' commits, c3-and-c3', and so on.

    To eliminate them, find c4 and c4' by some process—e.g., manual git log and eyeballing, or using git cherry, or using git log --left-right --cherry-mark topic...master, or whatever you like. Once you know where c4 and c4' are, you can use the command Omer suggested:

    git checkout topic
    git rebase --onto master <hash-of-c4>
    

    (or even just put topic on the end of the git rebase command, but I prefer not to do that - it's confusing to start on master and end on topic...).