Well this is certainly an odd thing to look for, I'd love to know your use case for doing this frequently enough that you're looking for a command for it.
Regardless, the "simplest" way would be to just move backward through the history from D until you find what you want. I'm sure this can be improved, but here's a shell script that does what you want.
#!/bin/sh
startingCommit=$1
target1=$2
target2=$3
#https://stackoverflow.com/a/8574392/3980115
containsElement () {
local e match="$1"
shift
for e; do [[ "$e" == "$match" ]] && return 0; done
return 1
}
currentCommit=$startingCommit
resultCommit=$startingCommit
result=1
while true ; do
echo "Trying $currentCommit"
#https://stackoverflow.com/a/11426834/3980115
list=( $(git rev-list $currentCommit) )
if (containsElement $target1 "${list[@]}" && containsElement $target2 "${list[@]}"); then
resultCommit=$currentCommit
currentCommit=${list[1]}
result=0
continue
else
break
fi
done
if [ "$result" ] ; then
echo "Earliest descendent: $resultCommit"
else
echo "No common commit found"
fi
Invoked from the command line with commit SHAs as:
./getDecesdent.sh D A B
Output:
Earliest descendent: C
I have no idea if this would work in a more complicated branching scenario, but I suspect it may will not get the correct result if any of the commits between D and C are merges - I honestly don't know.
However, this will find the correct commit in the situation described in the question - just very slowly. This script works by using rev-list
to get all available commits from a given revision, and slowly works its way back through the history until it finds a commit that can't reach both targets. The older the target commits are, the slower this will be.