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

  1. Report upstream with the trees/modes involved if it fires.
  2. Upgrade `gix-merge` to the latest version.
  3. 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

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


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)