GitoxideLabs/gitoxide · error
upper match already assured we only deal with blobs
Error message
upper match already assured we only deal with blobs
What it means
In `gix-merge`'s `merge_modes_prev` helper, an upper match already established that both entry kinds are blob variants (regular vs executable). The `unreachable!` fires if any other `EntryKind` (e.g. Link, Tree, Commit) reaches this code, meaning the earlier filter invariant was violated.
Solutions
- Report upstream with the trees/modes involved if it fires.
- Upgrade `gix-merge` to the latest version.
- If maintaining, narrow the outer match or return an explicit error instead of the wildcard `unreachable!`.
Example fix
// before
_ => unreachable!("upper match already assured we only deal with blobs"),
// after
kinds => return Err(message!("unexpected entry kinds {kinds:?} in mode merge")) Defensive patterns
Strategy: try-catch
Try / catch
// Not user-triggerable; isolate with catch_unwind around tree merges.
Prevention
- Keep gix-merge and gix-object versions in lockstep
- Cover mode-change merges (chmod +x toggles) in your test suite
When it happens
Trigger: Only when mode-merge logic receives a non-blob entry pair, which the enclosing match is supposed to exclude; effectively a guard against future refactors or enum changes.
Common situations: Not reachable in normal `gix_merge::tree` merges; would appear as a panic during mode resolution if entry-kind filtering regressed.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- only value and unspecified are possible here
- parent-match assures this
- fixed size array with three items
- internal nodes have no object ID key
- the matching node was checked to be a subtree
AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08).
Data as JSON: /api/errors/e77031936b8032d8.
Report an issue: GitHub.
Appendix: source
Thrown at gix-merge/src/tree/function/resolve.rs:2088
}
}
/// Merge modes derived from the same ancestor mode, `prev`.
///
/// Equal modes are retained. If blob modes differ only in their executable bit, the side that differs from `prev` wins,
/// preserving either a one-sided enable or disable of that bit.
fn merge_modes_prev(a: EntryMode, b: EntryMode, prev: EntryMode) -> Option<EntryMode> {
match (a.kind(), b.kind()) {
(_, _) if a == b => Some(a),
(a @ EntryKind::BlobExecutable, b @ (EntryKind::BlobExecutable | EntryKind::Blob))
| (a @ EntryKind::Blob, b @ EntryKind::BlobExecutable) => {
let prev = prev.kind();
let changed = if a == prev { b } else { a };
Some(
match (prev, changed) {
(EntryKind::Blob, EntryKind::BlobExecutable) => EntryKind::BlobExecutable,
(EntryKind::BlobExecutable, EntryKind::Blob) => EntryKind::Blob,
_ => unreachable!("upper match already assured we only deal with blobs"),
}
.into(),
)
}
_ => None,
}
}
fn push_deferred(change_and_idx: (Change, Option<usize>), changes: &mut ChangeList) {
push_deferred_with_rewrite(change_and_idx, None, changes);
}
fn push_deferred_with_rewrite(
(change, ours_idx): (Change, Option<usize>),
new_location: Option<(BString, usize)>,
changes: &mut ChangeList,
) {
changes.push(TrackedChange::new(change, Some(ours_idx), new_location));View on GitHub (pinned to e73179060b)