1

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.

Raffi
  • 102
  • 8

2 Answers2

2

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
torek
  • 448,244
  • 59
  • 642
  • 775
  • That should be "=" not "==" in the shell script on line 7, but Stack Overflow won't let me make such a small edit. – rptb1 Jan 17 '23 at 15:56
1

(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.

Romain Valeri
  • 19,645
  • 3
  • 36
  • 61
  • 2
    This works when the branch is not merged yet. But for branch, that has been already merged, it shows hash of last commit in branch (`G` hash). – Raffi Nov 21 '19 at 15:27
  • You're right, this is not for merged branches, I have to search further since what you want is indeed more tricky to get. I've edited in the meantime to avoid misleading people. – Romain Valeri Nov 21 '19 at 16:39