Reputation: 102
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
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
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