Skip to content

complete the migration from thiserror to gix-error - #2847

Open
Sebastian Thiel (Byron) wants to merge 51 commits into
mainfrom
gix-error-completion
Open

Sebastian Thiel (Byron) wants to merge 51 commits into
mainfrom
gix-error-completion

Conversation

@Byron

@Byron Byron commented Jul 28, 2026 •

Copy link
Copy Markdown
Member

Tasks

  • ~~refackiew~ - after verifying 5 commits, I think the work done here is good enough to rubber-stamp it, and rather post-fix as needed. It's too valuable to not have it soon in gix to reduce all the overhead.
  • proper not_found() support for gix-ref errors.
  • avoid can_retry() duplication in gix-transport
  • metadata support so downstream can get more data out, if they know it
  • refackiew gix-error

FollowUp

Review these custom error types:


I’ll check the remaining custom error definitions and distinguish thiserror types from hand-written ones.

31 custom error types remain across 17 crates in the current working tree, including uncommitted changes.

This counts production types implementing std::error::Error, excluding gix-error itself, tests, examples, and aliases.

Crate Count
gix-ref 5
gix 4
gix-transport 4
gix-validate 4
gix-config 2
12 other crates 1 each
Total 31

The single-type crates are gix-command, gix-diff, gix-fs, gix-index, gix-lock, gix-odb, gix-pack, gix-packetline, gix-refspec, gix-sec, gix-tempfile, and gix-utils.

Zero first-party thiserror derives or direct dependencies remain; it still appears transitively in Cargo.lock.

Some remaining types support recovery or downcasting, so 31 remaining doesn’t necessarily mean 31 should be removed.


Everything below this line was generated by Codex GPT-5.

Created by Codex on behalf of Byron. Byron will review before this is ready to merge.

Reported issue

$issue-full-auto etc/plan/gix-error.md lays out a plan to replace thiserror with gix-error. For each crate to replace thiserror in, also check if any of its variants is matched on. If so, hand-expand to the code that thiserror would produce and remove it. Otherwise, use gix-error in its place. Each commit should pass cargo check --workspace --all-targets.

Finally, gix (crate) should be able to use gix-error::Error via gix::Error exclusively and mostly use ?. Note that in gix there is also utilities to see if certain errors can be retried - this functionality should be put into gix-error, probably directly on gix-error::Error.

The gitoxide-core and gitoxide crates should keep anyhow, and that should work natively with gix-error - probably gix will have to forward the gix-error/anyhow feature to achieve that as well.

RetryableError should only be used when the error otherwise is too specific. If gix-error can inspect an error chain with well-known errors, it should do that. Keep an eye out for other standard classifications such as ValidationError; NotFound should be a well-known gix-error type. Object-kind mismatch can be a ValidationError.

Assuming all plumbing crates have already been processed so only thiserror in gix is left: avoid hand-expanded pattern-matched enums when gix::Error classification or a source-chain search for well-known plumbing errors works instead.

Refs #2351

Summary

  • removes direct thiserror use from workspace crates and exposes top-level failures through gix::Error
  • adds standard retry, corruption, not-found, and validation classifications to gix-error
  • determines retryability from known source-chain errors, retaining the explicit retry wrapper only at dependency-specific boundaries
  • preserves concrete source chains, classifications, and probable causes when errors are converted and raised again
  • keeps anyhow in the binaries and forwards the gix-error/anyhow feature through gix

Validation

  • cargo check --workspace --all-targets
  • cargo test -p gix-error
  • cargo test -p gix-error --features auto-chain-error --test auto-chain-error
  • cargo test -p gix --test gix revision::spec::
  • remote, clone, credential-helper, shallow-clone, and blocking/async network feature tests during the migration
  • one Codex commit review per final commit hash

Sebastian Thiel (Byron) pushed a commit that referenced this pull request Jul 28, 2026
CI exposed three remaining migration adaptations: gix-shallow doctests could not convert Exn through Box<dyn Error>, the ein init journey snapshots still expected the removed enum message, and lint rejected two mechanical expressions. Convert Exn explicitly in the doctest, update both init snapshots, and apply the two lint-preserving rewrites.

Observed in test-doc, test-journey, and lint on PR #2847.
@ameyypawar

Copy link
Copy Markdown
Contributor

Did the comparison. Three things, plus one offer.

1. fetch::Error::Negotiate truncates the chain. Its source() returns None, and unlike the Http arm — which iterates the Exn's frames directly — Negotiate falls through to gix_error::can_retry(self), which walks source(). So anything retryable below a negotiate failure is unreachable. It's public-API surface rather than a live bug: nothing in-tree calls fetch::Error::can_retry today, and receive_pack already remaps Negotiate into a CorruptionError. Worth noting client::Error::source() returns None for Http and SshInvocation too — the frame-iterating arm compensates can_retry specifically, but anything else walking source() would hit the same wall.

2. is_not_found() matches any io::ErrorKind::NotFound anywhere in the chain, and it's the discriminator at Submodule::open(), Repository::head() and head_tree_id_or_empty(). I couldn't produce a concrete path where that misfires — unborn heads raise a marked NotFoundError, missing-object errors are unmarked and propagate correctly — but the predicate is broader than the question being asked at each site, and a marker planted deeper later would change behaviour silently. Worth a second look rather than a bug report.

3. from_error on something already a gix_error::Error flattens its chain. Exn::new degenerates every source below the top to strings. It compiles and the suite passes, because the classifiers downcast to crate::Error and recurse. Live instances exist — e.g. self.head().map_err(gix_error::Error::from_error) in gix/src/repository/index.rs, where head() already returns gix_error::Error.

I've written a guard for that: from_error returns the value unchanged when it's already an Error, plus a #[track_caller] debug_assert naming the caller. Silent in release, loud in tests, no unsafe. Happy to send it as its own small PR — it protects any future conversion.

Also: in traversal_names_do_not_escape_the_modules_directory, the three erased-API assertions (git_dir_try_old_form, open, state) went from matching ParentComponent to is_validation(). The first assertion on sm.git_dir() still checks the specific error, so the test isn't toothless — but those three no longer distinguish a traversal rejection from any other validation failure.

On the comparison: I audited every source() arm in #2716 — 541 across 168 hand-written impls — against the derives they replaced. All match. And the guard's assert never fired across cargo test -p gix (416 tests), so no double-wrap on any tested path. That's a runtime check over tested paths, not a static proof.

There's more from the sweep — a per-type verdict on all 42 types #2716 left concrete, an erasure order for the E0119 chains, and a list of dropped #[error] messages. Say the word if any of that is useful.

@Byron

Byron commented Aug 4, 2026

Copy link
Copy Markdown
Member Author

Thanks Amey Pawar (@ameyypawar), while noting that I find no pleasure in reading these AI generated blobs of text.

My main gripe is that it's a bot speaking through you, so unless you say you produced this text by hand or think you could produce it, disclosure is the way to go. I recommend adding a few lines of yourself on top giving me your verdict, no matter what it is (i.e. something like "this looks reasonable to me, and I spot-checked one of these claims"), followed, by a separator to clearly mark the AI blob.

Thanks again.

Sebastian Thiel (Byron) pushed a commit that referenced this pull request Aug 4, 2026
CI exposed three remaining migration adaptations: gix-shallow doctests could not convert Exn through Box<dyn Error>, the ein init journey snapshots still expected the removed enum message, and lint rejected two mechanical expressions. Convert Exn explicitly in the doctest, update both init snapshots, and apply the two lint-preserving rewrites.

Observed in test-doc, test-journey, and lint on PR #2847.
Sebastian Thiel (Byron) pushed a commit that referenced this pull request Aug 4, 2026
CI exposed three remaining migration adaptations: gix-shallow doctests could not convert Exn through Box<dyn Error>, the ein init journey snapshots still expected the removed enum message, and lint rejected two mechanical expressions. Convert Exn explicitly in the doctest, update both init snapshots, and apply the two lint-preserving rewrites.

Observed in test-doc, test-journey, and lint on PR #2847.
Sebastian Thiel (Byron) pushed a commit that referenced this pull request Aug 4, 2026
CI exposed three remaining migration adaptations: gix-shallow doctests could not convert Exn through Box<dyn Error>, the ein init journey snapshots still expected the removed enum message, and lint rejected two mechanical expressions. Convert Exn explicitly in the doctest, update both init snapshots, and apply the two lint-preserving rewrites.

Observed in test-doc, test-journey, and lint on PR #2847.
@ameyypawar

Copy link
Copy Markdown
Contributor

Re-checked after your push. Negotiate and client::Error look right, and the repository/index.rs double-wrap is gone.

Four things I'd still look at. The message loss bothers me most; on the security tests I'd rather have your call than mine.

The sweep and this write-up are both AI-produced — I took help of AI tools throughout. I checked the two source arms and the index.rs wrap myself.


Messages dropped: 53 sites, 50 distinct. Worst: clone/fetch/mod.rs and config/mod.rs+config/tree at 9 each, update_refs/update.rs at 7 (now a bare alias, nothing re-attached), repository/mod.rs at 5.

17 assertions weakened. Two matter: the three erased-API asserts in traversal_names_do_not_escape_the_modules_directory are bare is_validation() (assertion 1 still pins ParentComponent), and remote/connect.rs:14 lost ProtocolDenied { scheme: File }.

is_not_found() matches any raw io NotFound at any depth; is_validation() has no io disjunct. So the looser one guards head_tree_id_or_empty(). Correcting myself from last time — I said I found no misfire, but detached HEAD with a missing object yields the empty tree. Symbolic HEAD is fine.

Dead branches: clone/fetch/mod.rs:253 and update_refs/mod.rs:207 downcast out of err.sources(), which never matches in chain mode. gix defaults to auto-chain-error, binaries build tree mode — so library consumers lose those paths. (gitoxide-core/repository/diff.rs:130 too, but that predates this branch.)

Also: ~31 double-wraps left after the 19 you removed — a floor, counted from monomorphised instantiations rather than grep, so I can pull the list if useful. And 97b7a7cab leaves probable_cause() on the truncated node.

Sebastian Thiel (Byron) pushed a commit that referenced this pull request Aug 5, 2026
CI exposed three remaining migration adaptations: gix-shallow doctests could not convert Exn through Box<dyn Error>, the ein init journey snapshots still expected the removed enum message, and lint rejected two mechanical expressions. Convert Exn explicitly in the doctest, update both init snapshots, and apply the two lint-preserving rewrites.

Observed in test-doc, test-journey, and lint on PR #2847.
Sebastian Thiel (Byron) pushed a commit that referenced this pull request Aug 5, 2026
CI exposed three remaining migration adaptations: gix-shallow doctests could not convert Exn through Box<dyn Error>, the ein init journey snapshots still expected the removed enum message, and lint rejected two mechanical expressions. Convert Exn explicitly in the doctest, update both init snapshots, and apply the two lint-preserving rewrites.

Observed in test-doc, test-journey, and lint on PR #2847.
Sebastian Thiel (Byron) pushed a commit that referenced this pull request Aug 5, 2026
CI exposed three remaining migration adaptations: gix-shallow doctests could not convert Exn through Box<dyn Error>, the ein init journey snapshots still expected the removed enum message, and lint rejected two mechanical expressions. Convert Exn explicitly in the doctest, update both init snapshots, and apply the two lint-preserving rewrites.

Observed in test-doc, test-journey, and lint on PR #2847.
@Byron

Byron commented Aug 5, 2026

Copy link
Copy Markdown
Member Author

Thanks Amey Pawar (@ameyypawar). This is an interesting experiment as you essentially take the role of a reviewer, while my agent double-checks and fixes. And all that without any human review, so I am already very curious on how the actual review can be done efficiently.
Meantime, agents do things through their meat-proxies 😅.

Sebastian Thiel (Byron) pushed a commit that referenced this pull request Aug 5, 2026
CI exposed three remaining migration adaptations: gix-shallow doctests could not convert Exn through Box<dyn Error>, the ein init journey snapshots still expected the removed enum message, and lint rejected two mechanical expressions. Convert Exn explicitly in the doctest, update both init snapshots, and apply the two lint-preserving rewrites.

Observed in test-doc, test-journey, and lint on PR #2847.
Sebastian Thiel (Byron) pushed a commit that referenced this pull request Aug 8, 2026
CI exposed three remaining migration adaptations: gix-shallow doctests could not convert Exn through Box<dyn Error>, the ein init journey snapshots still expected the removed enum message, and lint rejected two mechanical expressions. Convert Exn explicitly in the doctest, update both init snapshots, and apply the two lint-preserving rewrites.

Observed in test-doc, test-journey, and lint on PR #2847.
Sebastian Thiel (Byron) pushed a commit that referenced this pull request Aug 18, 2026
CI exposed three remaining migration adaptations: gix-shallow doctests could not convert Exn through Box<dyn Error>, the ein init journey snapshots still expected the removed enum message, and lint rejected two mechanical expressions. Convert Exn explicitly in the doctest, update both init snapshots, and apply the two lint-preserving rewrites.

Observed in test-doc, test-journey, and lint on PR #2847.
@Byron Sebastian Thiel (Byron) changed the title change!: complete the migration from thiserror to gix-error complete the migration from thiserror to gix-error Aug 18, 2026
Sebastian Thiel (Byron) pushed a commit that referenced this pull request Aug 19, 2026
CI exposed three remaining migration adaptations: gix-shallow doctests could not convert Exn through Box<dyn Error>, the ein init journey snapshots still expected the removed enum message, and lint rejected two mechanical expressions. Convert Exn explicitly in the doctest, update both init snapshots, and apply the two lint-preserving rewrites.

Observed in test-doc, test-journey, and lint on PR #2847.
Sebastian Thiel (Byron) pushed a commit that referenced this pull request Aug 19, 2026
CI exposed three remaining migration adaptations: gix-shallow doctests could not convert Exn through Box<dyn Error>, the ein init journey snapshots still expected the removed enum message, and lint rejected two mechanical expressions. Convert Exn explicitly in the doctest, update both init snapshots, and apply the two lint-preserving rewrites.

Observed in test-doc, test-journey, and lint on PR #2847.
Codex (codex) and others added 25 commits September 23, 2026 21:37
<!-- Byron -->
rubberstamp, but looked at it more to understand why it's more code.
Answer: downstream relies on better error classification.
However, I think this can also be reduced a bit.
<!-- Byron -->
rubberstamp

But looked into it more to see why it has so much more code. The simple answer is that it basically doesn't use `gix-error`,
but writes error types manually.
This needs rework.
<!-- Byron -->

rubberstamp, but it needs some more work to not repeat `can_retry()`
<!-- Byron -->
rubberstamp

<!-- agent -->
Keep the original `TryReserveError` inside the `OutOfMemory` I/O error so structured classification can distinguish allocator failure without treating malformed stream metadata as corruption.
<!-- Byron -->
rubberstamp

<!-- agent -->
The crate-by-crate migration retained operation-specific error aliases to
limit downstream churn. With the migration complete, those names only hide
the shared error types and keep otherwise empty API namespaces alive.

Use the underlying `gix_error` types directly throughout the workspace,
including indirect aliases, renamed exports, test helpers, and the URL fuzz
target. Remove namespaces and files that only held forwarding aliases, and
update documentation and migration guidance to use the canonical types.
Adjust the source locations recorded in error snapshots after deleting the
alias declarations.

Keep `gix::{Error, Exn}` and `gix::error` as the central facade, along with
unrenamed canonical re-exports, required associated types, concrete errors,
and aliases that add structure. Preserve each `Exn` parameter, conditional
error alternative, error message, and source chain. Include all downstream
adaptations in this breaking change so the stack remains buildable.
<!-- Byron -->
rubberstamp

<!-- agent -->
Replace classification-only downcasts with semantic predicates for
retryability, missing resources, invalid input, corruption, and resource
exhaustion. Use borrowed probable-cause inspection where callers only
need the underlying error, removing temporary ownership conversions.
<!-- Byron -->
rubberstamp

<!-- agent -->
The central `gix::Error` already implements the conversions used by `?`.
Remove redundant `map_err(Exn::into_error)` and equivalent `Error::from`
calls, including conversions inside context closures, while retaining the
context itself and explicit conversions at returned-result boundaries.
<!-- Byron -->
rubberstamp

<!-- agent -->
`ResultExt` already accepts exceptions directly. Remove five erasures in
pack generation and merge paths that immediately add context, and avoid
converting the committer exception to `Error` before adding clone context.
Preserve each context message and final erasure where the callback needs it.
<!-- Byron -->
rubberstamp

<!-- agent -->
Inspect retry policies directly on `Exn` in `gix-index`, `gix-transport`,
and `gix-worktree-stream` tests. Remove conversions to `gix_error::Error`
that were only needed to inspect these exceptions.
<!-- agent -->
`std::io::Error::source()` skips its custom payload, hiding classification
markers, custom error types, and branches of a nested `gix_error::Error`.
Retain the payload while walking native sources so borrowed retry policies,
exception traversal, and both porcelain error modes see the complete cause.

Document how custom errors expose their immediate cause and classification
markers. Cover every classification, nested branches, and concrete payload
downcasts, and update diagnostic snapshots for the retained I/O payload.

Assisted-by: GPT 6.0
Co-authored-by: GPT 6.0 <codex@openai.com>
…nversion

<!-- agent -->
Expose the shared `ValidationError` marker from reference, tag, submodule,
and path-component errors without changing their variants or input details.
This lets callers distinguish invalid names from absent references through
`gix::Error`, including optional reference lookups.

Assisted-by: GPT 6.0
Co-authored-by: GPT 6.0 <codex@openai.com>
<!-- Byron -->

rubberstamp, checked diff

<!-- agent -->
Expose a `ValidationError` source from command-line parser errors so their
classification survives raising and conversion to `gix_error::Error`.
Keep the original parser variant available for callers that need details.

Regression cases cover missing quotes, dangling escapes, and assignment-only
input. All command tests and doctests pass in both error modes; focused
Clippy also passes.
<!-- Byron -->

stamp of approval, after looking at the diff and description.

<!-- agent -->

Forwarding to the inner I/O error's `source()` hides the I/O error itself,
preventing callers from inspecting its kind through a custom persistence
error. Return the immediate cause so generic error classification can find
it, while preserving the handle needed to recover from failed persistence.

Extend both writable-file and marker recovery tests to check the source.
All tempfile tests and doctests pass.
Admittedly, I was mostly checking the public API, thinking that it's
most certainly useful and overally, having lazy iterators for everything
is a step in the right direction.

This wasn't a crazily detailed review in the interest of time.

<!-- agent -->
Error inspection rebuilt the entire error graph before returning its first
item, so even a root match visited unrelated sources and allocated storage.
Use a shared iterator that expands each node only when another item is needed,
preserving breadth-first order, concrete types, and caller locations in both
error representations.

Expose `classify(&error)` for custom borrowed errors, and share classification
predicates with `Error` and `Exn`. The shared traversal and predicate definitions
remove more code than the borrowed API adds. Keep `probable_cause()` unchanged.

Assisted-by: GPT 6.0
Co-authored-by: GPT 6.0 <codex@openai.com>
<!-- Byron -->

rubber stamp, but checked the diff and asked for fixes to use gix_error::TestResult instead,
which for the sake of simplicity is going to happen in a future commit.

<!-- agent -->
Merge-base traversal replaced object-store and decoding errors with a static
message. Missing objects, retryable I/O failures, and custom backend errors
therefore lost their causes and classifications at the revision boundary.

Return `Exn<Message>` and raise graph insertion context around the original
failure. Remove the forwarding `Error` alias and `Simple` type, and adapt the
porcelain API to return `gix::Error`.

The regression reproduces the lost backend cause before the fix and verifies
its concrete I/O kind, missing-resource classification, and retry policy after
conversion. All 113 revision tests and the porcelain revision tests pass;
`gix` and `gix-merge` compile with the changed signature.
<!-- Byron -->
rubber stamp, checked diff

<!-- agent -->
Curl replaces upload and download callback failures with generic transfer
errors. Keep the original I/O error alongside curl's diagnostic so custom
retry policies and other classifications remain inspectable after conversion
to `gix::Error`. Clear the saved callback failure between transfers.

Also retain the integer parser's cause when a virtual-host port is invalid.
Both conversions previously discarded their sources in `map_err()`.
<!-- Byron -->

rubber stamp, looked at diff

<!-- agent -->
Writing to a byte slice may succeed with a short write. Delta application
ignored that byte count, so oversized copy and insert instructions silently
truncated their output instead of reporting corrupt data.

Split off an output slice of exactly the required size before copying.
This also removes two mappings of I/O failures that slice writes cannot
produce. A regression covers both copy and insert instructions; all
`gix-pack` tests pass.
Looked at this in detail to understand how error handling improvements
were made. It all makes sense, and teaches me to... not ignore or
skip over errors, ever, it's basically a bug unless there is a test
that proves it's not a bug.

<!-- agent -->
Fetch ref updates discarded commit decoding and traversal setup errors,
treating any such failure as permission to force the update. A malformed
local or remote commit could therefore overwrite a ref without a force
refspec. Traversal errors were also ignored when looking for the ancestor.

Propagate those failures with their original causes and context. Check
object kinds explicitly to retain the existing behavior for non-commit
targets without mistaking corruption for an object-kind mismatch.

Assisted-by: GPT 6.0
Co-authored-by: GPT 6.0 <codex@openai.com>
<!-- Byron -->

Rubber stamp, looked at diff. This is a cleanup commit.
There is going to be considerable cleanup done later as well.

<!-- agent -->
Parsers and adapters discarded encoding, integer, date, signature, and
object-access failures when replacing them with context. Preserve their
concrete causes so classification and downcasting keep working after
conversion to `gix::Error` or an I/O error.

Return `Exn` from fallible path, command-line, gitdir, and pack-entry
conversions where necessary, and adapt their consumers in the same change.
Packed-ref and reflog errors retain their parser sources and input details;
reflog recovery reports the actual recovery failure. Loose-object verification
now propagates lookup and enumeration failures instead of treating every
lookup error as retryable or silently skipping failed enumeration.

Remove unnecessary UTF-8 conversions for ASCII suffixes and check span bounds
before narrowing. Parsers that only return `()` explicitly destructure it.
No production `map_err()` closure still discards a wildcard-bound error.
Also preserve causes in formatting-only CLI and commit-graph adapters, where
stringification previously lost checksum corruption classifications.
This is just for convenience, as I can never remember.

Assisted-by: GPT 6.0
Co-authored-by: GPT 6.0 <codex@openai.com>
<!-- agent -->
Callers can enrich errors with named values they already possess without
introducing a custom payload type. `Metadata` keeps a message and an ordered
dictionary of typed scalar values, including lossless bytes and native paths.

`Error::metadata()` and `Exn::metadata()` iterate separate contexts through
existing error traversal, preserving original causes and classifications.
Recovery continues to use classifications and concrete domain errors. Document
metadata keys on each function that directly returns them.

Assisted-by: GPT 6.0
Co-authored-by: GPT 6.0 <codex@openai.com>
…y signals

<!-- Byron -->

Took a closer look over about 40% of the commit, skimmed/skipped the rest it. So not a complete rubber stamp.
Later-on, a lot of these explicit downcasts should be turned into quick classification checks.

<!-- agent -->
Return canonical `Exn` errors from reference operations and `gix::Error` at
porcelain boundaries. Preserve native parser, filesystem, lock and custom
name-conversion sources instead of rewrapping them in operation-specific enums.

Use documented `Metadata` dictionaries for diagnostic paths, reference names,
input bytes and positions. Keep concrete signals for absent references,
malformed loose references, stale expected values, existing references and
missing committer identity. These support GitButler-style recovery without
string matching; stale reference values still require reconciliation before
retrying. Fetch only treats an absent referent as unborn, propagating malformed
referents and read failures.

Remove empty error namespaces and duplicate conversions along with their
workspace callers. Preserve unterminated packed input and count peeled lines
when reporting iterator positions.
<!-- Byron -->

looked at the diff in detail, but went through quickly.
Enough to give it my name, but really barely so. Some refactoring done as well,
but nothing major.

<!-- agent -->
Return canonical `Exn` errors for loose and dynamic object lookup, alternate
resolution, prefix lookup and integrity verification. Preserve original I/O,
decoder, allocation, persistence and custom reader sources instead of forwarding
them through operation-specific error enums.

Use documented scalar `Metadata` contexts for native paths, object IDs, sizes,
pack counts and recursion limits. Keep `alternate::Cycle` with its discovered
directory chain, and preserve explicit retryability for interrupted verification
or concurrent disk changes. An absent delta base remains not found, while a
recursion limit alone implies neither absence nor corruption. Empty loose files
are now classified as corruption.

Remove empty error namespaces and redundant conversions in porcelain and CLI
callers. Keep the genuine I/O boundary for store initialization and pack loading,
using the existing adapter to retain both the I/O kind and the complete cause.

Replace wrapper-construction tests with actual custom-reader, malformed-object,
missing-delta and depth-limit failures. Validate metadata and classifications
after conversion, including native path values and retained cycle details.
Comment thread gix/src/repository/shallow.rs Fixed
Comment thread gix-ref/src/store/packed/buffer.rs Dismissed
Comment thread gix-ref/src/store/packed/buffer.rs Dismissed
Comment thread gix-ref/src/store/packed/buffer.rs Dismissed
Comment thread gix-ref/src/store/file/packed.rs Dismissed
Comment thread gix-discover/src/is.rs Fixed
<!-- agent -->
Complete error snapshots expose useful context and causes, but temporary
paths, dynamically assigned ports, object IDs, and platform-specific I/O
messages make their output unstable.

Add `redact_debug_snapshot()` to apply explicit replacements, reuse object-ID
normalization, and render OS errors by their portable `ErrorKind`. Handle
plain and debug-escaped Windows paths while retaining meaningful suffixes
and unrelated backslashes. Return an owned `Debug` value so `insta` displays
the diagnostic without additional quoting or newline escaping.

Cover redaction, object identity, and OS error formatting, and convert the
environment, SBOM argument, and Rust fixture error assertions to complete
inline snapshots. The helper is available before downstream diagnostic
snapshot migrations need it.

Assisted-by: GPT 6.0
Co-authored-by: GPT 6.0 <codex@openai.com>
<!-- Byron -->

I refackiewed gix-error with the usual care, and skimmed through all the
downstream changes, rubber-stamping it.
Definitely cleaned up a few bits, like config-key related error handling,
and error related types.

<!-- agent -->

A single failure could require separate errors for its message, category,
and diagnostic values. Classification wrappers could obscure the concrete
cause, while cause-selection heuristics could choose a different sibling
after adding context or flattening an exception tree. Represent each
failure with one diagnostic and preserve its actual causal structure.

Make `Message` the shared diagnostic type, with a message, optional `Class`,
and named `MetadataValue`s. Classified constructors and the `with_class()`
and `with()` builders attach categories and details without extra error
layers. Plain messages and string conversions remain unclassified.
`Metadata` is now the value dictionary; `Exn::metadata()` and
`Error::metadata()` yield only non-empty dictionaries in traversal order,
keeping each context separate and preserving bytes, paths, and numeric types.

Introduce `ClassificationMarker` to classify existing concrete errors or
provide classification-only sources. Classification still inspects these
markers, while diagnostic traversal, downcasts, cause selection, and
reports skip them and retain their real descendants. Preserve concrete
I/O errors and their original kinds for recovery decisions.

Give `Frame`, `Exn`, and `Error` the same `probable_cause()` policy: follow
the unique causal path to a leaf or the first diagnostic branch, retaining
the aggregate instead of choosing an arbitrary sibling. Include native
sources, I/O payloads, nested errors, and explicitly raised children in both
tree and chain representations. Preserve caller locations through transparent
markers, avoid duplicate native causes in reports, and honor alternate
`TestError` formatting. Marker-only errors retain a classification fallback.

Add `ExnResult<T = (), E = exn::Untyped>` and `ExnMessageResult<T = ()>`,
re-export them from `gix`, and adopt them throughout the workspace. Keep
callback bounds erased and preserve concrete error types where callers
need their payloads. Group supporting classification and display types in
`gix_error::types`, and frame inspection and erasure in `gix_error::exn`.

Migrate constructors, signatures, and recovery checks together. Replace the
`gix::config::key::Error` family with ordinary diagnostics and central
`gix::Error` results. Provide `config::key::error()` and `error_with_value()`
for `key`, optional `environment_override`, and optional `input` metadata.
Retain parser causes, rejected numeric values, accepted special values,
and configuration leniency. Collapse artificial error layers elsewhere
while preserving concrete failures and partial outcomes.

Make complete diagnostics reviewable through inline `insta` snapshots,
retaining assertions for classifications, retry policies, concrete causes,
and stored data. Use the shared snapshot redaction helper for unstable
paths, ports, object IDs, and platform-specific OS errors. Update migration
guidance, examples, and CLI snapshots, and use static X.509 assertion messages to
avoid dumping verifier identities and raw output.

BREAKING CHANGE: replace `ValidationError`, `CorruptionError`,
`NotFoundError`, `ResourceExhaustionError`, and `RetryableError` with
`Message` diagnostics or transparent `ClassificationMarker`s. The former
`Metadata` error becomes a dictionary, `Value` becomes `MetadataValue`,
and `Something` is removed. Access offending input through named metadata.
Import `Frame` and `Untyped` from `exn`, and classification/display helpers
from `types`. Replace free `can_retry()` and `can_retry_lenient()` calls
with methods on `classify(error)`. Configuration error aliases are removed;
diagnostic traversal omits markers, metadata iteration skips empty maps,
and probable-cause selection stops at the first diagnostic branch.

Recorded validation includes 3,988 workspace tests excluding `gix-tix`,
2,277 SHA-256 fixture tests, minimal `gix`, blocking and async networking,
curl and reqwest, both error representations, doctests, feature builds,
and Clippy. Windows-only tests were not executed.

Assisted-by: GPT 6.0
Co-authored-by: GPT 6.0 <codex@openai.com>
Co-authored-by: Byron <sebastian.thiel@icloud.com>
<!-- agent -->
Keep Rust security findings focused on production code by excluding
`examples/`, `tests/`, `fuzz/`, and `benches/` throughout the workspace.
Scope `paths-ignore` to the Rust job, whose `build-mode: none` supports
these filters. Inline unit tests remain included.

Validated YAML parsing, recursive path coverage, and `git diff --check`.

Assisted-by: GPT 6.0
Co-authored-by: GPT 6.0 <codex@openai.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The merge fuzz harness now suppresses unrelated not-found failures, potentially hiding object-lookup regressions.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 High severity

Open (1)
What changed in this PR

Migrates the workspace from thiserror enums to gix-error, standardizing error propagation, classification, metadata, and gix::Error boundaries.

Changes:

  • Replaces crate-specific error enums with ExnResult, ExnMessageResult, and gix::Error.
  • Adds retry, validation, corruption, not-found, resource-exhaustion, metadata, and probable-cause support.
  • Updates consumers, tests, snapshots, examples, fuzzers, and manifests.
File Description
gix-error/​** Expands core error classification, metadata, chaining, and tests.
gix/​** Adopts unified public gix::Error APIs and updates tests/examples.
gix-actor/​**, gix-archive/​**, gix-attributes/​**, gix-blame/​**, gix-chunk/​** Migrates error consumers and tests.
gix-command/​**, gix-commitgraph/​**, gix-config-value/​**, gix-config/​** Replaces custom errors and adds classified diagnostics.
gix-credentials/​**, gix-date/​**, gix-diff/​**, gix-dir/​**, gix-discover/​** Migrates APIs, examples, and tests.
gix-features/​**, gix-filter/​**, gix-fs/​**, gix-fsck/​**, gix-hash/​** Standardizes error results and assertions.
gix-ignore/​**, gix-index/​**, gix-lock/​**, gix-mailmap/​**, gix-merge/​** Migrates errors and classification handling.
gix-negotiate/​**, gix-note/​**, gix-object/​**, gix-odb/​**, gix-pack/​** Preserves context and classifications across object operations.
gix-packetline/​**, gix-path/​**, gix-pathspec/​**, gix-prompt/​** Replaces typed enums with gix-error results.
gix-protocol/​**, gix-ref/​**, gix-refspec/​**, gix-revision/​**, gix-revwalk/​** Migrates protocol, reference, and revision failures.
gix-shallow/​**, gix-status/​**, gix-submodule/​**, gix-tempfile/​**, gix-tix/​** Updates error propagation and recovery logic.
gix-transport/​**, gix-traverse/​**, gix-url/​**, gix-validate/​**, gix-worktree*/​**, gix-zlib/​** Adds standardized classifications and result aliases.
gitoxide-core/​**, src/​**, tests/​it/​**, tests/​tools/​** Adapts binaries and integration utilities to unified errors.
tests/​snapshots/​** Updates expected user-facing error chains.
Cargo.toml, .gitignore, .github/​workflows/​codeql.yml Updates linting, editor sharing, and CodeQL configuration.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@@ -289,7 +289,7 @@ fn fuzz(data: &[u8]) {
Ok(outcome) => outcome,
// Resolving a binary add/add conflict with its absent ancestor cannot
// produce a resource. This is a valid configuration-dependent error.
Err(gix_merge::tree::Error::MergeResourceNotFound) => continue,
Err(err) if err.is_not_found() => continue,
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.

5 participants