fix(node): preserve promisor mirror mode on unknown withheld-paths lookup (#48)#69
fix(node): preserve promisor mirror mode on unknown withheld-paths lookup (#48)#69beardthelion wants to merge 1 commit into
Conversation
…okup (#48) Peer sync decided mirror mode from the origin's withheld-paths answer via classify_mirror, whose _ => Plain arm collapsed both Some(empty) (genuinely public) and None (lookup 404'd / 5xx / network / parse error) into Plain. For an existing promisor mirror that meant fetch_repo(.., Plain) unset remote.origin.promisor + partialclonefilter and ran git fetch --refetch, so a transient withheld-paths outage downgraded a still-withheld mirror to a full clone and broke syncs until the endpoint recovered. Replace classify_mirror with resolve_mirror_mode(withheld, exists, promisor): only Some(empty) downgrades an existing mirror; None preserves the on-disk promisor mode. A genuine public transition always returns Some(empty), never None, so preserving on None cannot mask one; it only suppresses the transient-error false positive. Add a three-valued PromisorProbe read from git config --get exit codes so a transient probe failure (Unknown) is not mistaken for a definitive NotPromisor and cannot itself trigger the downgrade. One warn marks the preserve path, derived from the resolved mode so it cannot drift. Tests cover every resolve_mirror_mode arm (incl. the regression and the indeterminate-probe case) and the probe's Promisor / NotPromisor / Unknown outcomes against real git repos.
📝 WalkthroughWalkthrough
ChangesThree-valued mirror-mode resolution
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
crates/gitlawb-node/src/sync.rs (1)
508-510: ⚡ Quick winMinor documentation inaccuracy: exit code 1 does not mean "not a git repo".
The comment states exit 1 occurs when the key is absent "or the directory is not a git repo." However,
git config --getexits 1 specifically when the key is absent in a valid git repository. For an invalid/non-existent repo, git exits 128 (which correctly maps toUnknownat line 531). The test at line 854 confirms this behavior.📝 Suggested doc fix
-/// `git config --get` exits 0 when the key is set, 1 when it is absent (or the -/// directory is not a git repo) — both definitive `NotPromisor` — and other +/// `git config --get` exits 0 when the key is set, 1 when it is absent in a valid +/// git repo — a definitive `NotPromisor` — and other🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/gitlawb-node/src/sync.rs` around lines 508 - 510, The documentation comment for git config exit codes contains an inaccuracy. Update the comment around the git config validation logic to clarify that exit code 1 specifically indicates the key is absent in a valid git repository, not that the directory is not a git repo. Remove the "or the directory is not a git repo" clause from the exit code 1 description, since exit code 128 (not 1) is what git returns for invalid or non-existent repositories. This aligns the documentation with the actual behavior confirmed by the test at line 854.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@crates/gitlawb-node/src/sync.rs`:
- Around line 508-510: The documentation comment for git config exit codes
contains an inaccuracy. Update the comment around the git config validation
logic to clarify that exit code 1 specifically indicates the key is absent in a
valid git repository, not that the directory is not a git repo. Remove the "or
the directory is not a git repo" clause from the exit code 1 description, since
exit code 128 (not 1) is what git returns for invalid or non-existent
repositories. This aligns the documentation with the actual behavior confirmed
by the test at line 854.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: de5c0390-a193-4b91-81bf-99bb816e21f3
📒 Files selected for processing (1)
crates/gitlawb-node/src/sync.rs
Summary
Peer sync downgraded a still-withheld promisor mirror to a full clone on a transient
withheld-pathsoutage. This makes the mirror-mode decision distinguish "unknown lookup" from "genuinely public" and preserve the existing promisor mode when the lookup is unknown.Motivation & context
Closes #48
process_batchdecided how to mirror a repo from the origin's anonymouswithheld-pathsanswer.fetch_withheldreturnsSome(globs)on a 2xx andNoneon every failure (404, 5xx, network, parse).classify_mirrorthen mappedSome(non-empty) => Promisorbut_ => Plain, collapsing bothSome(empty)(genuinely public) andNone(unknown) intoPlain. For an existing promisor mirror that ranfetch_repo(.., Plain), which unsetremote.origin.promisor+remote.origin.partialclonefilterand rangit fetch --refetch. So a transient outage of thewithheld-pathsendpoint against a still-withheld repo tore down the partial-clone config and forced a full refetch against an origin that still withholds blobs for anonymous callers, breaking syncs until the endpoint recovered. Sibling of #47, same fail-open-vs-fail-closed family.Kind of change
What changed
gitlawb-node (
crates/gitlawb-node/src/sync.rs):classify_mirror(withheld)withresolve_mirror_mode(withheld, exists, promisor).Some(non-empty) => PromisorandSome(empty) => Plainare unchanged. OnNone, an existing mirror preserves its promisor mode instead of downgrading; a fresh clone staysPlain(the existing fail-closed-at-the-git-layer behavior). A genuine public transition always returnsSome(empty), neverNone, so preserving onNonecannot mask one; it only suppresses the transient-error false positive.PromisorProbe(Promisor/NotPromisor/Unknown) read fromgit config --get remote.origin.promisorexit codes.git config --getcollapses "key absent" (exit 1) and "git errored" (exit ≥2 / spawn failure) into the same non-zero exit otherwise; distinguishing them means a transient probe failure (Unknown) is not read as a definitiveNotPromisorand cannot itself trigger the downgrade.warn!marks the preserve path, derived from the resolved mode so it cannot drift from the decision.A persistently unreachable
withheld-pathsroute pins an existing promisor mirror until the endpoint recovers (the per-cycle warn is the operator-visible signal). This is an intentional confidentiality-over-liveness bias: a still-withheld repo's anonymous git read endpoint 404s, so a preserved mirror fetches nothing harmful, and only a public-but-route-permanently-dropped repo loses backfill liveness.How a reviewer can verify
The core regression is
resolve_preserves_promisor_on_unknown_lookup_for_existing_mirror:None+ existing + on-disk promisor must stayPromisor, never downgrade.resolve_preserves_promisor_on_indeterminate_probecovers the probe-failure (Unknown) case.probe_reports_promisor_for_promisor_mirror/probe_reports_not_promisor_when_key_absent/probe_reports_unknown_on_git_errorexercise the three probe outcomes against real git repos.plain_fetch_clears_promisor_config_on_transitionis unchanged and still passes, proving a genuineSome(empty)public transition still clears promisor config.Before you request review
cargo test --workspacepasses locallycargo fmt --allandcargo clippy --workspace --all-targets -- -D warningsare cleanfeat(...),fix(...),docs(...)).env.exampleupdated if behavior or config changed (or N/A)Notes for reviewers
Out of scope (follow-ups): typed error reasons in
fetch_withheld(404 vs 5xx vs parse) and retry/backoff on a failed lookup; both deferred deliberately since the empty-vs-unknown distinction the fix needs already survives in theOptionreturn. Thewithheld-pathsendpoint is unauthenticated (pre-existing); node-level auth for it is worth a separate look.Summary by CodeRabbit
Bug Fixes
#48).Improvements