Classify a tracked ref against its remote head [IFC-3280]
Why
compare_local_remote compares commits for equality, so a fast-forward and a rewritten history
are indistinguishable. This adds the classifier the reconciliation needs. Nothing is wired to it
yet.
First item of IFC-3210.
What changed
A new backend/infrahub/git/divergence/ package: the five-member classification, the detector,
and a gateway that keeps every git type out of the detector.
Three things the code found that the design had wrong:
- Asking "is this commit here" is harder than it looks. GitPython's
Repo.commitresolves lazily
and hands back an object for a sha that is not there, and the object database reader raises the
same ValueError for a missing object as for a reader that died. The gateway runs
git rev-parse --verify --quiet <sha>^{commit}as a plain command instead, and comparing what
it resolved to against the input rejects an annotated tag, which peeling would otherwise accept.
Git returns the same status for a commit it holds but cannot read, so the gateway proves the
object database answers forHEADbefore it reports an absence. A pack it cannot read raises
instead of reading as a rewrite. - Resolving a commit object goes through the shared reader, so a dead pipe escaped the gateway as
BrokenPipeError.merge-base --is-ancestoranswers the comparison in one call. - The gateway binds to one repository at construction. Taking a repository per call would put a
git type in the detector's signature.
A graph commit ahead of the remote head means the remote was rewound, because no write site
records a commit the remote never had. That is a rewrite. The worktree resets onto the remote head
all the same: the merge pushes before it records, so the reset discards no commit that exists
nowhere else.
How to test
uv run pytest backend/tests/unit/git/divergence/
52 tests, no database, under two seconds. Reverting the presence check makes
test_a_broken_object_database_is_an_error_not_an_absence pass silently instead of raising, and
reverting the HEAD check makes test_an_unreadable_pack_is_an_error_not_an_absence do the same.
Checklist
- Tests added
- Changelog entry: not applicable, nothing user visible ships until the reconciliation lands
- I have reviewed AI generated content