86

Git is a DAG of snapshots, with each node on the graph representing a commit. Each commit can have 'n' parent commits.

Given any two commits, is there a single, concise way to discern the "order" of these two in the DAG. git rev-list seems to be the most promising, but I can't seem to find the right incantation.

Ideally, I'd have something like the following

$ git related hash1 hash2
hash1 is ancestor of hash2

OR

hash2 is ancestor of hash1

OR

hash1 unrelated to hash2

OR

hash1 is equal to hash2
Anthony Geoghegan
  • 11,533
  • 5
  • 49
  • 56
Chris Cleeland
  • 4,760
  • 3
  • 26
  • 28
  • 5
    Same question asked the opposite: http://stackoverflow.com/questions/3005392/how-can-i-tell-if-one-commit-is-a-descendant-of-another-commit?rq=1 – Chris Cleeland Dec 18 '14 at 16:07
  • Possible duplicate of [How can I tell if one commit is a descendant of another commit?](https://stackoverflow.com/questions/3005392/how-can-i-tell-if-one-commit-is-a-descendant-of-another-commit) – Drew Noakes Sep 05 '18 at 17:36
  • The answers are mostly the same, but the questions are the opposite. Indeed, when I search the question for evaluation of being an ancestor, the "descendant" question comes up very far down in search results--that's probably why I ended up asking the question anew. If the goal is to join the two question/answers, the result should be edited so that it's found for both "ancestor" or "descendant" questions. – Chris Cleeland Sep 06 '18 at 15:40

5 Answers5

113

Use git merge-base --is-ancestor <commit1> <commit2>

There is more than one way to find the answer to this. The simplest is to use

git merge-base --is-ancestor <possible-ancestor-commit> <commit>

From the documentation for git merge-base:

--is-ancestor

Check if the first <commit> is an ancestor of the second <commit>, and exit with status 0 if true, or with status 1 if not. Errors are signaled by a non-zero status that is not 1.

Other options

git log with triple dot ... notation

Another option is to use git log and use triple dot notation ... to tell Git to output the set union of the child commits, minus the set intersection. Basically, it tells you how a set of commits have diverged from each other:

$ git log --oneline --graph --left-right \
--first-parent --decorate <commit1>...<commit2>

The above command will show you commits that are reachable from commit1 or commit2, but not both, i.e. C1 UNION C2 - C1 INTERSECTION C2, in terms of set operations.

If neither commit is a parent of the other, you'll see the child commits of both, but if one is an ancestor of the other, you'll only see the output for the descendant commit, since the ancestor is contained in the path of the descendant, and is thus excluded from the output.

You can read more about git log and triple dot notation from the following resources:

  1. git-log(1).
  2. gitrevisions(1): Specifying Ranges.
  3. Revision Selection.

git branch --contains option

git-rev-list(1) seems like it could be used to answer this. Another way is to simply attach temporary branch labels to the commits you want to test, and then use the --contains option for git branch:

git branch --contains <commit-to-test>

The output will be all branches that contain the commit somewhere in their commit tree, so by using a temporary branch on the other commit, you can see if the commit you're testing is an ancestor.

From the documentation:

--contains [<commit>]

Only list branches which contain the specified commit (HEAD if not specified).

legends2k
  • 31,634
  • 25
  • 118
  • 222
  • 2
    Just to note that `--is-ancestor` was introduced in git v1.8. Your `git branch` example worked great on a machine with git v1.7.x. – eoinoc Oct 15 '13 at 11:01
  • 3
    Thanks for the --is-ancestor. However I would love to see a `git log --contains ` (that shows all commits that contains a specific commit) similar to `git tag --contains` and `git branch --contains` – Jarl Feb 20 '14 at 07:38
  • @Jarl How about `git log parent..descendant`? This lists all commits from `parent` to `descendant`. You can find the `parent` using `git branch --contains descendant`. – legends2k Jan 19 '21 at 10:49
11

The following shell script might do the trick:

if git rev-list $SHA1 | grep -q $SHA2 ; then echo "$SHA2 is ancestor of $SHA1"
elif git rev-list $SHA2 | grep -q $SHA1 ; then echo "$SHA1 is ancestor of $SHA2"
else echo "$SHA1 unrelated to $SHA2" ; fi

Or, to neatly wrap it up into a git alias:

git config --global alias.related '!function git_related() { if git rev-list $1 | grep -q $2 ; then echo "$2 is ancestor of $1" ; elif git rev-list $2 | grep -q $1 ; then echo "$1 is ancestor of $2" ; else echo "$1 unrelated to $2" ; fi } ; git_related $1 $2'
helmbert
  • 35,797
  • 13
  • 82
  • 95
  • This one is better because it works in older git as well. – Tim has moved to Codidact Oct 26 '15 at 17:16
  • Also nice even in newer Git versions because its output is exactly what's needed. – RichVel Sep 14 '16 at 13:07
  • Here's a version of the alias (very useful) which also accepts branch names (or HEAD etc) instead of just commit ids: `git config --global alias.related '!function git_related() { commit1=\`git log -n 1 --format="%h" $1\` ; commit2=\`git log -n 1 --format="%h" $2\` ; if git rev-list $commit1 | grep -q $commit2 ; then echo "$2 is ancestor of $1" ; elif git rev-list $commit2 | grep -q $commit1 ; then echo "$1 is ancestor of $2" ; else echo "$1 unrelated to $2" ; fi } ; git_related $1 $2'` – Peter Reay Oct 12 '17 at 15:50
  • Like it, love it. – Charles Jul 03 '19 at 11:32
2
if   (( $(git rev-list $1..$2|wc -l) == 0 )); then echo "$2 is ancestor of $1"
elif (( $(git rev-list $2..$1|wc -l) == 0 )); then echo "$1 is ancestor of $2"
else echo "$1 and $2 are unrelated"
fi
michas
  • 25,361
  • 15
  • 76
  • 121
1
git log  --oneline -1  OLD_SHA..NEW_SHA

Iff this gives you some log, then OLD_SHA is parent of NEW_SHA.

jin
  • 1,542
  • 1
  • 9
  • 10
  • Tried it, but unfortunately it gave me the same output even if I switched the order of the SHAs. So I don't think this works. – EluciusFTW Dec 28 '20 at 11:52
0

To build on @helmbert's excellent git related alias, here's a version which also accepts branch names (or HEAD etc) as arguments, rather than just commit ids:

git config --global alias.related '!function git_related() { commit1=`git log -n 1 --format="%h" $1` ; commit2=`git log -n 1 --format="%h" $2` ; if git rev-list $commit1 | grep -q $commit2 ; then echo "$2 is ancestor of $1" ; elif git rev-list $commit2 | grep -q $commit1 ; then echo "$1 is ancestor of $2" ; else echo "$1 unrelated to $2" ; fi } ; git_related $1 $2'
Peter Reay
  • 231
  • 5
  • 13