Skip to content

feat: add is_ancestor(), like git merge-base --is-ancestor - #3036

Open
Amey Pawar (ameyypawar) wants to merge 2 commits into
GitoxideLabs:mainfrom
ameyypawar:merge-base-is-ancestor
Open

Amey Pawar (ameyypawar) wants to merge 2 commits into
GitoxideLabs:mainfrom
ameyypawar:merge-base-is-ancestor

Conversation

@ameyypawar

Copy link
Copy Markdown
Contributor

Created by Claude Code on behalf of Amey, who reviewed it before submitting. Everything below this line is the agent's writing, not his.


Summary

Commits

  • feat: add merge_base::is_ancestor() to check reachability like git merge-base --is-ancestor (gix-revision)
  • feat: add Repository::is_ancestor() and Repository::is_ancestor_with_graph() (gix)

Git baseline

git merge-base --is-ancestor A B succeeds if A can be reached from B, including when both are the same commit, and fails for unrelated histories. With a commit-graph, Git doesn't walk commits with a lower generation than A.

is_ancestor() does the same and ends the walk once only commits with a lower generation than A remain. It also answers false without any traversal if B is in the commit-graph but A isn't, as a commit-graph contains all ancestors of its commits, or if A's generation isn't lower than B's. Only nonzero, unsaturated generations are used.

Validation

  • cargo test — gix-revision merge-base 11, gix repository::revision 5, also with GIX_TEST_FIXTURE_HASH=sha256 and with GIX_TEST_IGNORE_ARCHIVES=1
  • cargo clippy -p gix-revision -p gix --all-targets, cargo fmt --check, cargo doc with -D warnings for both crates, and cargo check without the revision or merge_base features
  • The new validate test takes its expectations from the existing git merge-base --all baselines: A is an ancestor of B exactly if A is their only merge-base. That rule agrees with git merge-base --is-ancestor (git 2.52.0) on all 103 single-pair baselines.
  • A differential run of 2,150 commit pairs from this repository's history against git merge-base --is-ancestor (git 2.52.0) — 1,078 of them ancestors, with walks through merges and 50 pairs of identical commits — agrees on every pair without a commit-graph, with a complete one, and with one that lacks the newest 1,499 commits.
  • Disabling either shortcut, the reliability check of the second one or the walk's lower bound, dropping the self-ancestor case, or checking the wrong color makes at least one new test fail. The first commit builds and passes the gix-revision tests on its own.

Not addressed here

  • Mergebase support #2795 also lists merge-base --independent, which would expose remove_redundant(). Happy to follow up on that separately if it's wanted.
  • Like merge_base(), Repository::is_ancestor() uses the commit-graph whenever core.commitGraph allows it, so the TODO in Mergebase support #2795 about not using commit-graphs when grafts or object replacements are in effect applies to both.
  • cargo doc -p gix --no-deps with -D warnings stops on an unresolved link to Self::with_fetch_options in gix/src/clone/access.rs unless blocking-network-client is enabled. It's unrelated to this change, so it's left alone.

… merge-base --is-ancestor`

Callers who want to know whether one commit is an ancestor of another had to compute merge-bases
and compare the result (see GitoxideLabs#2914). `is_ancestor()` answers directly, considers a commit its own
ancestor, and returns `false` for unrelated histories.

It reuses the merge-base painting and checks whether the candidate was reached from the descendant.
With a commit-graph, it answers `false` without traversal if the descendant is in it but the
candidate isn't, as a commit-graph contains all ancestors of its commits, or if both have reliable
generations and the candidate's isn't lower, as parents have lower generations than their children.
Otherwise, a reliable generation of the candidate stops the walk once only commits with a lower
generation remain, as these can't reach it. `merge_base()` passes no such bound and is unchanged.
…th_graph()`

They answer whether one commit is reachable from another, like `git merge-base --is-ancestor`,
without computing merge-bases and comparing them (see GitoxideLabs#2914). Like the merge-base methods,
the `_with_graph()` variant reuses a revision graph across calls to speed up repeated queries.

`is_ancestor()` carries a `git2` alias for `graph_descendant_of()`, which takes the descendant
first and doesn't consider a commit its own descendant.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant