zebediah49 zebediah49 - 4 months ago 27
Git Question

Determine relationship between commits

In a linear history, two commits A and B can have one of three states:

  • A and B are the same commit

  • A strictly before B

  • B strictly before A

Git's nonlinear history allows for four additional options

  • A and B share both a parent and child

  • A and B share a parent but not a child

  • A and B share a child but not a parent

  • A and B share nothing

I'm not sure how to achieve the last two without using graft points, but it could happen.

What I'm unsure of how to accomplish is a determination of this relationship in a simple manner--at the moment I can only think of a somewhat odd
and solution where one tests each case individually:

[[ "$a" = "$b" ]] && echo "same"
git rev-list "$b" | grep -q "$a" && echo "a before b"
git rev-list "$a" | grep -q "$b" && echo "b before a"
cat <(git rev-list "$a") <(git rev-list "$b") | sort | uniq -cd | grep -q 2 && echo "A and B share parents"
cat <(git rev-list --children "$a") <(git rev-list --children "$b") | sort | uniq -cd | grep -q 2 && echo "A and B share children"

There has to be a better, gittier way of doing this, so what is it?


Checking for parent-child relation is easy, use git merge-base. If the result is one of your commits, it's a parent, the other is a child.

I suggest ignoring the case when commits don't have a common ancestor. Sure, this can happen, but I'm not aware of any valid use-cases for this. I mean, you are solving a real problem for a real project, I'm sure you can assume that this won't happen. BTW, merge-base will exit with an error, and it's the only situation when a merge-base doesn't exist, so you still can detect this case.

Finding children for a commit is just impossible. Absoultely. You can't do that reliably. A commit has a “parent” reference, but it doesn't have “child” references. rev-list --children does a completely different thing, it has nothing to do with what you want. You’ll have to refine your problem.

What I can suggest instead is using git branch --contains <commit> or git tag --contains <commit> which will only list those branches/tags from which you can reach your commit.

Alternatively you can look at git for-each-ref that will allow you to test if a commit is reachable from any of your branches/tags.