How can I tell if one commit is a descendant of another commit?
GitGit Problem Overview
With Git, how can I tell if one commit in my branch is a descendant of another commit?
Git Solutions
Solution 1 - Git
From Git 1.8.0, this is supported as an option to merge-base
:
git merge-base --is-ancestor <maybe-ancestor-commit> <descendant-commit>
From the man page:
> --is-ancestor
>
> Check if the first
For example:
git merge-base --is-ancestor origin/master master; echo $?
Solution 2 - Git
If you want to check this programmatically (e.g. in script), you can check if git merge-base A B
is equal to git rev-parse --verify A
(then A is reachable from B), or if it is git rev-parse --verify B
(then B is reachable from A). git rev-parse
is here needed to convert from commit name to commit SHA-1 / commit id.
Using git rev-list
like in VonC answer is also possibility.
Edit: in modern Git there is explicit support for this query in the form of git merge-base --is-ancestor
.
If one of commits you are asking about is a branch tip, then git branch --contains <commit>
or git branch --merged <commit>
might be better non-programmatic solution.
Solution 3 - Git
This kind of operations relies on the notion of range of revisions detailed in the SO question: "Difference in ‘git log origin/master’ vs ‘git log origin/master..’".
git rev-list
should be able to walk back from a commit, up until another if reachable.
So I would try:
git rev-list --boundary 85e54e2408..0815fcf18a
0815fcf18a19441c1c26fc3495c4047cf59a06b9
8a1658147a460a0230fb1990f0bc61130ab624b2
-85e54e240836e6efb46978e4a1780f0b45516b20
(Boundary commits are prefixed with -
)
If the last commit displayed is the same than the first commit in the git rev-list
command, then it is a commit reachable from the second commit.
If the first commit is not reachable from the second, git rev-list
should return nothing.
git rev-list --boundary A..B
would finish by A
, if A
is reachable from B
.
It is the same as:
git rev-list --boundary B --not A
,with B
a positive reference, and A
a negative reference.
It will starts at B
and walks back through the graph until it encounters a revision that is reachable from A
.
I would argue that if A
is directly reachable from B
, it will encounter (and display, because of the --boundary
option) A
itself.
Solution 4 - Git
Another way would be to use git log
and grep
.
git log --pretty=format:%H abc123 | grep def456
This will produce one line of output if commit def456 is an ancestor of commit abc123, or no output otherwise.
You can usually get away with omitting the --pretty
argument, but it is needed if you want to make sure that you only search through actual commit hashes and not through log comments and so on.
Solution 5 - Git
https://stackoverflow.com/a/13526591/895245 mentions it, now to make it more human friendly:
git-is-ancestor() (
if git merge-base --is-ancestor "$1" "$2"; then
echo 'ancestor'
elif git merge-base --is-ancestor "$2" "$1"; then
echo 'descendant'
else
echo 'unrelated'
fi
)
alias giia='git-is-ancestor'
Solution 6 - Git
git show-branch branch-sha1 commit-sha1
Where:
- branch-sha1: the sha1 in your branch you want to check
- commit-sha1: the sha1 of the commit you want to check against
Solution 7 - Git
If you are using git merge-base --is-ancestor
, make sure to use Git 2.28 (Q3 2020)
With Git 2.28 (Q3 2020), a few fields in "struct commit
" that do not have to always be present have been moved to commit slabs.
See commit c752ad0, commit c49c82a, commit 4844812, commit 6da43d9 (17 Jun 2020) by Abhishek Kumar (abhishekkumar2718
).
(Merged by Junio C Hamano -- gitster
-- in commit d80bea4, 06 Jul 2020)
> ## commit-graph
: introduce commit_graph_data_slab
> Signed-off-by: Abhishek Kumar
> The struct commit is used in many contexts. However, members generation
and graph_pos
are only used for commit-graph related operations and otherwise waste memory.
>
> This wastage would have been more pronounced as we transition to generation number v2, which uses 64-bit generation number instead of current 32-bits.
>
> As they are often accessed together, let's introduce struct commit_graph_data
and move them to a commit_graph_data
slab.
>
> While the overall test suite runs just as fast as master
, (series: 26m48s, master
: 27m34s, faster by 2.87%), certain commands like git merge-base --is-ancestor
were slowed by 40% as discovered by Szeder Gábor.
After minimizing commit-slab access, the slow down persists but is closer to 20%.
>
> Derrick Stolee believes the slow down is attributable to the underlying algorithm rather than the slowness of commit-slab access and we will follow-up in a later series.
Solution 8 - Git
Building up on itub's answer, in case you need to do this for all the tags in the repository:
for i in `git tag` ; do echo -ne $i "\t" ; git log --pretty=format:%H $i | (grep <commit to find> || echo ""); done