Raffi
Raffi

Reputation: 102

git - Find commit from which was branch created

Is it possible to find commit (ideally only the commit hash) from which was branch created, even if the branch was already merged?

For example:

master   A-B-C-D-E
          \   /
feature    F-G

feature branch is already merged in master, but it still exists and can be checkout to. I would like to find, that feature branch started from A.

Upvotes: 1

Views: 1770

Answers (2)

torek
torek

Reputation: 489708

Branches don't really have parent branches. (Related: How to find the nearest parent of a Git branch?) You can find the hash of A pretty easily, but consider this diagram:

H--I--L--N--O--R---T--U   <-- master
 \     \     \    /
  J--K--M--P--Q--S   <-- feature

Which commit(s) would you like to find here? O, L, and H are all viable candidates for "where feature started from". But maybe feature didn't start from any of those; maybe it started from P, which used to have a label main-feature. See the related question for details.

To find commit A from your diagram, which I'll reproduce here a form I consider more accurate (the name master merely points directly to commit E for instance—commits F and G are on branch master too!):

A--B--C--D--E   <-- master
 \      /
  F----G   <-- feature

we can start from commit E (tip of master) and step back to commit D (the merge). We know we have hit D when one of its parents is the tip of feature, i.e., is commit G. We then get both parent hash IDs from commit D:

git rev-parse <hash-of-D>^@

and make sure there are exactly two of them (a three-parent merge makes this hard), and then invoke git merge-base --all on those two parent hash IDs. This immediately produces the hash ID of commit A.

When we use this same procedure on my sample graph, we step back from U to T, discover that it has two parents and the second is S which is the tip of feature, and ask Git to find the merge base of R and S. The output of this git merge-base --all is the hash of commit O.

To do this relatively quickly in shell script, start with:

git rev-list --merges --first-parent master

which produces a list of all merge commit hash IDs reachable by walking first-parent descendants of master. (You may or may not want --first-parent; if you've been using git pull heavily, the first parent linkages of master may be somewhat damaged by "foxtrot merges".) That is, this gives us commit hashes T and D in the two diagrams here (and probably more). If you omit --first-parent, think about what you want from cases where other branches have merged into master, bringing with them commits from the feature branch in question, and decide whether you might want --topo-order in your git rev-list.

Next, walk through these commits, getting their parent hashes:

looking_for=$(git rev-parse feature)  # this is the hash ID we're looking for
found=false
for hash in (output of above git rev-list); do
    set -- $(git rev-parse ${hash}^@)
    case $# in
    2) # two parents, good
        if [ $2 == $looking_for ]; then found=true; break; fi;;
    *) # more than two parents, we might go wrong
        echo "commit $hash has $# parents, help"; exit 1;;
    esac
done
if ! $found; then
    echo "unable to find the commit hash you were looking for"
    exit 1
fi

At this point, you have the desired commit pair in $1 and $2 due to the way set parses arguments, so:

# found the desired commit pair, so now just invoke git merge-base
set -- $(git merge-base --all $1 $2)
case $# in
1) echo "the commit you are looking for is $1"; exit 0;;
*) echo "I found $# candidate commits $@"; exit 1;;
esac

Upvotes: 2

Romain Valeri
Romain Valeri

Reputation: 22057

(Disclaimer : off-topic answer - not adressing already merged branches)


If feature and master are not yet merged, you can just get their "merge-base" (doc) :

git merge-base master feature

It will output your commit A's long-form hash.

Upvotes: 1

Related Questions