Finding a branch point with Git?

前端 未结 22 1343
小鲜肉
小鲜肉 2020-11-22 10:11

I have a repository with branches master and A and lots of merge activity between the two. How can I find the commit in my repository when branch A was created based on mast

相关标签:
22条回答
  • 2020-11-22 10:41

    You may be looking for git merge-base:

    git merge-base finds best common ancestor(s) between two commits to use in a three-way merge. One common ancestor is better than another common ancestor if the latter is an ancestor of the former. A common ancestor that does not have any better common ancestor is a best common ancestor, i.e. a merge base. Note that there can be more than one merge base for a pair of commits.

    0 讨论(0)
  • 2020-11-22 10:41

    I've used git rev-list for this sort of thing. For example, (note the 3 dots)

    $ git rev-list --boundary branch-a...master | grep "^-" | cut -c2-
    

    will spit out the branch point. Now, it's not perfect; since you've merged master into branch A a couple of times, that'll split out a couple possible branch points (basically, the original branch point and then each point at which you merged master into branch A). However, it should at least narrow down the possibilities.

    I've added that command to my aliases in ~/.gitconfig as:

    [alias]
        diverges = !sh -c 'git rev-list --boundary $1...$2 | grep "^-" | cut -c2-'
    

    so I can call it as:

    $ git diverges branch-a master
    
    0 讨论(0)
  • 2020-11-22 10:42

    Here's an improved version of my previous answer previous answer. It relies on the commit messages from merges to find where the branch was first created.

    It works on all the repositories mentioned here, and I've even addressed some tricky ones that spawned on the mailing list. I also wrote tests for this.

    find_merge ()
    {
        local selection extra
        test "$2" && extra=" into $2"
        git rev-list --min-parents=2 --grep="Merge branch '$1'$extra" --topo-order ${3:---all} | tail -1
    }
    
    branch_point ()
    {
        local first_merge second_merge merge
        first_merge=$(find_merge $1 "" "$1 $2")
        second_merge=$(find_merge $2 $1 $first_merge)
        merge=${second_merge:-$first_merge}
    
        if [ "$merge" ]; then
            git merge-base $merge^1 $merge^2
        else
            git merge-base $1 $2
        fi
    }
    
    0 讨论(0)
  • 2020-11-22 10:42

    The problem appears to be to find the most recent, single-commit cut between both branches on one side, and the earliest common ancestor on the other (probably the initial commit of the repo). This matches my intuition of what the "branching off" point is.

    That in mind, this is not at all easy to compute with normal git shell commands, since git rev-list -- our most powerful tool -- doesn't let us restrict the path by which a commit is reached. The closest we have is git rev-list --boundary, which can give us a set of all the commits that "blocked our way". (Note: git rev-list --ancestry-path is interesting but I don't how to make it useful here.)

    Here is the script: https://gist.github.com/abortz/d464c88923c520b79e3d. It's relatively simple, but due to a loop it's complicated enough to warrant a gist.

    Note that most other solutions proposed here can't possibly work in all situations for a simple reason: git rev-list --first-parent isn't reliable in linearizing history because there can be merges with either ordering.

    git rev-list --topo-order, on the other hand, is very useful -- for walking commits in topographic order -- but doing diffs is brittle: there are multiple possible topographic orderings for a given graph, so you are depending on a certain stability of the orderings. That said, strongk7's solution probably works damn well most of the time. However it's slower that mine as a result of having to walk the entire history of the repo... twice. :-)

    0 讨论(0)
  • 2020-11-22 10:45

    How about something like

    git log --pretty=oneline master > 1
    git log --pretty=oneline branch_A > 2
    
    git rev-parse `diff 1 2 | tail -1 | cut -c 3-42`^
    
    0 讨论(0)
  • 2020-11-22 10:45

    surely I'm missing something, but IMO, all the problems above are caused because we are always trying to find the branch point going back in the history, and that causes all sort of problems because of the merging combinations available.

    Instead, I've followed a different approach, based in the fact that both branches share a lot of history, exactly all the history before branching is 100% the same, so instead of going back, my proposal is about going forward (from 1st commit), looking for the 1st difference in both branches. The branch point will be, simply, the parent of the first difference found.

    In practice:

    #!/bin/bash
    diff <( git rev-list "${1:-master}" --reverse --topo-order ) \
         <( git rev-list "${2:-HEAD}" --reverse --topo-order) \
    --unified=1 | sed -ne 's/^ //p' | head -1
    

    And it's solving all my usual cases. Sure there are border ones not covered but... ciao :-)

    0 讨论(0)
提交回复
热议问题