gitgit-mergeleast-common-ancestor

How do criss-cross merges arise in Git?


I have been going through the, "git merge-base", man page and I can't understand how multiple merge bases develop. Specifically, I'm hung up on the following illustration in the man page:

When the history involves criss-cross merges, there can be more than one best common
ancestor for two commits. For example, with this topology:
  ---1---o---A
      \ /
       X
      / \
  ---2---o---o---B
both 1 and 2 are merge-bases of A and B. Neither one is better than the other (both
are best merge bases). When the --all option is not given, it is unspecified which
best one is output.

I just can't understand how such a situation could be created. I have attempted to recreate this criss-cross merge situation between branches using a test repository but I cannot replicate it. In all cases, I always wind up with 1 merge commit to which both A and B point to (instead of A and B pointing to independent merge commits as the diagram illustrates).

Can anyone illustrate how this situation can arise? Is this a common situation or an error situation?


Solution

  • I just can't understand how such a situation could be created. I have attempted to recreate this criss-cross merge situation between branches using a test repository but I cannot replicate it. In all cases, I always wind up with 1 merge commit to which both A and B point to (instead of A and B pointing to independent merge commits as the diagram illustrates).

    Can anyone illustrate how this situation can arise?

    Criss-cross merges can arise in different ways. On example that springs to mind is when you have two branch references pointing to the same merge commit (one of those branches being checked out) and you run git commit --amend. The following toy example does just that:

    # set things up
    cd ~/Desktop
    mkdir crisscross
    cd crisscross
    git init
    
    # make an initial commit
    printf "bar\n" > README.md
    git add README.md
    git commit -m "add README"
    
    # add a line at the top of the file and commit
    sed -i '' '1s/^/foo\n/' README.md
    git commit -am "add foo line"
    
    # create and checkout a branch pointing at the initial commit
    git checkout -b other master^
    
    # add a line at the bottom of the file and commit
    printf "baz\n" >> README.md
    git commit -am "add baz line"
    
    # do a merge and make both branches point to this merge commit
    git merge master
    git checkout master
    git merge other
    

    At this stage, the output of git log --graph --oneline --decorate --all is

    *   30b9175 (HEAD, master, other) Merge branch 'master' into other
    |\  
    | * f318ead add foo line
    * | 31875d9 add baz line
    |/  
    * 8b313a0 add README
    

    Now amend the last commit (see How does git commit --amend work, exactly? for more details):

    git commit --amend
    

    After that, the output of git log --graph --oneline --decorate --all will be

    *   69bbcbe (HEAD, master) Amended merge commit
    |\  
    | | *   30b9175 (other) Merge branch 'master' into other
    | | |\  
    | |/ /  
    |/| /   
    | |/    
    | * f318ead add foo line
    * | 31875d9 add baz line
    |/  
    * 8b313a0 add README
    

    There you go: the history now contains criss-cross merges.

    Is this a common situation or an error situation?

    As illustrated above, the situation can arise. It may be undesirable, but it shouldn't be considered an "error state".