gix: negotiate and adopt sha256 object format#2642
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR adds SHA-256 object-format awareness to fetch/clone flows (notably protocol v2), including automatically adopting a remote’s object format during a fresh clone, and extends the test suite/fixtures to cover SHA-256 scenarios.
Changes:
- Echo
object-formatin protocol v2 commands and parse server-advertisedobject-formatinto the fetch ref-map. - Teach
clone::fetch_onlyto adopt a remote’s object hash (SHA-256) during initial clone by reinitializing/reopening the empty repo and retrying. - Add fixtures and tests (including nextest matrix updates) to validate SHA-256 behavior across protocol/refmap/clone.
Reviewed changes
Copilot reviewed 12 out of 16 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| justfile | Runs gix-protocol tests with additional sha256 feature combinations. |
| gix/tests/gix/util.rs | Extends SHA1↔SHA256 fixture hash mapping for tests. |
| gix/tests/gix/clone.rs | Stabilizes shallow ID comparisons; adds SHA-256 clone adoption test. |
| gix/tests/fixtures/make_sha256_remote.sh | Adds a deterministic SHA-256 remote fixture generator. |
| gix/src/remote/connection/fetch/update_refs/tests.rs | Makes tests hash-kind agnostic; fixes NULL handling for SHA-256. |
| gix/src/clone/fetch/util.rs | Adds repo reinitialization helper to adopt remote object format. |
| gix/src/clone/fetch/mod.rs | Implements adopt-and-retry on object-format mismatch during fetch_only. |
| gix-worktree-stream/tests/fixtures/generated-archives/.gitignore | Ignores a new SHA-256 generated fixture archive. |
| gix-protocol/tests/protocol/fetch/v2.rs | Updates v2 request expectations; adds SHA-256 v2 test. |
| gix-protocol/tests/protocol/fetch/ref_map.rs | Adds unit tests for object-format capability parsing. |
| gix-protocol/src/fetch/refmap/init.rs | Parses object-format into gix_hash::Kind and uses hash-specific null IDs. |
| gix-protocol/src/command.rs | Echoes server object-format in all v2 commands; validates it as an allowed feature. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
51c5f0e to
73c1cdf
Compare
73c1cdf to
8cb8290
Compare
8cb8290 to
9758eff
Compare
|
I reworded the commit message subject prefixes to follow the Gitoxide conventions (added |
RefMap::from_refs only recognized sha1, so it refused to talk to sha256 remotes even with the sha256 feature on. Parse the server's object-format instead, accepting any hash the build supports. When the server omits the capability, default to Sha1 if available, otherwise report UnknownObjectFormat. This also replaces a hardcoded `Kind::Sha1` null placeholder, which did not compile in sha256-only builds, with the resolved hash. Tests cover the sha1, sha256, unknown, and missing cases. Co-Authored-By: Claude Opus 4.8 <[email protected]> Signed-off-by: Adrian Ratiu <[email protected]>
A v2 client must report its object format to the server, otherwise the server assumes SHA-1 and aborts the request with a "mismatched object format" error when talking to a SHA-256 repository. This affects both ls-refs and fetch. A persistent connection retains the object format negotiated during ls-refs, but a stateless transport like HTTP sends each command as its own request, so every v2 command must echo the object-format the server advertised. So add the echo to `Command::default_features` so it applies to every v2 command and permit object-format during v2 feature validation. Also add v2 wire-format sha256 tests for ls-refs and ref-in-want. Co-Authored-By: Claude Opus 4.8 <[email protected]> Signed-off-by: Adrian Ratiu <[email protected]>
A clone into a freshly initialized repository hit an `unimplemented!` panic when the remote used sha256, since the local repository defaults to sha1. Reconfigure the still-empty local repository to the remote's object format and retry the fetch, matching git's behavior of inheriting the remote's hash on clone. Without the sha256 feature gix_hash::Kind has a single variant, so the local and remote hashes can never differ; the mismatch check is compiled out entirely in that case. Co-Authored-By: Claude Fable 5 <[email protected]> Signed-off-by: Adrian Ratiu <[email protected]>
The clone fetch loop adopts the remote's object format and retries when it differs from the freshly initialized local repository. Adoption makes the next iteration's hashes match, but if the reopened repository somehow still disagrees the loop would re-handshake forever. Track whether we already retried and fail with IncompatibleObjectHash on a second mismatch instead. The error variant is introduced here, as this is its only user. Also silence clippy::never_loop on default (sha1) features, where the sha256-gated `continue` is absent and the loop always returns first pass. Co-Authored-By: Claude Fable 5 <[email protected]> Signed-off-by: Adrian Ratiu <[email protected]>
Add a dedicated test for the clone-time object-format adoption. Basically a fixture remote created with --object-format=sha256 independent of GIX_TEST_FIXTURE_HASH gets cloned into a fresh SHA-1-default repository which must adopt the remote format. The test asserts the adopted format both in memory and as persisted on disk, and that the remote is written exactly once despite the adoption retry. Co-Authored-By: Claude Opus 4.8 <[email protected]> Signed-off-by: Adrian Ratiu <[email protected]>
Derive expected object ids from the fixture repo instead of hardcoding SHA-1 hex, so the update_refs tests pass under both SHA-1 and SHA-256 fixtures (GIX_TEST_FIXTURE_HASH). Co-Authored-By: Claude Opus 4.7 <[email protected]> Signed-off-by: Adrian Ratiu <[email protected]>
9758eff to
b439eaa
Compare
The fetch_pack tests assert on pack data and index checksums, which are hash-algorithm dependent. Add the SHA-256 equivalents so the tests pass under GIX_TEST_FIXTURE_HASH=sha256. Co-Authored-By: Claude Opus 4.8 <[email protected]> Signed-off-by: Adrian Ratiu <[email protected]> compare shallow boundary commits order-independently The on-disk order of shallow boundary commits is hash-dependent. This means that inside a hash alorithm the commit order is deterministic and it reshuffless across hashes. E.g. a commit which sorts 1st under sha1 might sort 3rd under sha256. This is the reason why the clone shallow/unshallow tests fail under sha256 when comparing against the sha1-ordered expectations, due to the hex_to_id() calls preserving the sha1-ordering even when converting the hashes to sha256. Thus we sort the expected side before comparing. Note: only the expected side needs sorting, as the actual side already comes sorted by gix_shallow::read. Co-Authored-By: Claude Opus 4.8 <[email protected]> Signed-off-by: Adrian Ratiu <[email protected]> add generated sha256 archive for symlink-prefix-reuse fixture Adds the SHA-256 variant of the make_symlink_prefix_reuse_advisory fixture archive, matching its committed SHA-1 sibling. Contains no host-absolute paths, so it is safe to track. Co-Authored-By: Claude Opus 4.7 <[email protected]> Signed-off-by: Adrian Ratiu <[email protected]> do not track the sha256 basic fixture archive This mirrors the existing basic.tar file exclusion. Co-Authored-By: Claude Opus 4.8 <[email protected]> Signed-off-by: Adrian Ratiu <[email protected]>
b439eaa to
0ed353f
Compare
|
Thanks! I couldn't believe my eyes when I couldn't find these lines in the After all, they are needed to run the changes introduced to |
Sebastian Thiel (@Byron) Yes, I added them in this commit (right at the end): 9067bcb Perhaps I should have explicitly added them in a separate commit. 🙂 Edit: They seem to be present in your latest push: 0ed353f |
|
Adrian Ratiu (@10ne1) 9067bcb just adds them for In any case, I'd expect this to PR to merge soon. |
0ed353f to
e9445c7
Compare
e9445c7 to
3262d99
Compare
3262d99 to
ea0f627
Compare
The major change is to actually run the `gix` tests for the affected code. Co-authored-by: Sebastian Thiel <[email protected]>
b866ed3 to
c6789d3
Compare
c6789d3 to
2bf6a59
Compare
…olled hash kind. Connections can now be created from detached remotes, internally, which makes them independent of the underlying repository. This is breaking as it comes with a cleanup of `Connection` lifetimes, which will break anyone who stores them in a struct. So hopefully nobody is affected. Co-authored-by: Sebastian Thiel <[email protected]> address auto-review
Co-authored-by: Sebastian Thiel <[email protected]>
2bf6a59 to
3927d70
Compare
Sebastian Thiel (Byron)
left a comment
There was a problem hiding this comment.
Alright, that's it 🎉!
The Attempt workaround was removed in favor of the refactoring that was needed to be able to change/reopen the repository in place. In future, that's even more important as it will also have to deal with reftable.
Due to not using Attempt, it will be avoiding to redo the entire fetch operation.
And as a side-effect, a long-standing Connection lifetime bug was also fixed.
Journey tests were added as well, proving that SHA-256 transport and protocol parts now work. I also validated that manually, awing at the SHA-256 references provided by the remote.
Will merge soon.
What
This teaches the v2 protocol fetch client to negotiate object-format with sha256 remotes and to adopt the remote object format when cloning. Before this, gix assumed sha1 during protocol negotiation and hit an unimplemented panic when a clone target turned out to use sha256.
This addresses the gix-protocol line item of Batch 3 in #281:
Testing
These tests were suggested by Sebastian Thiel (@Byron), so I also added their equivalent to the justfile:
AI disclosure
This PR was written with the help of Claude Code, but was not vibe-coded. I'm not a fan of vibe-coding. I used AI to better understand the codebase myself (asked it a lot of questions), review both my code and the code generated by AI, used it especially to detect bugs and corner-cases and suggest fixes, however I very carefully reviewed & edited each code/comment lines, the commit messages and so on, until I was satisfied with the result.
Byron's Tasks
Attemptcan be removed, as it does affect callers apparently (*progress).