GitoxideLabs/gitoxide · error

counts were resolved beforehand

Error message

counts were resolved beforehand

What it means

In `iter_from_counts` (gix-pack `data/output/entry/iter_from_counts.rs`), the sorting comparator assumes every `Count` has been resolved to a `LookedUp` pack location, so comparing anything else is declared unreachable. The `unreachable!()` fires when a count in `NotLookedUp` state participates in the sort, meaning the caller passed counts whose pack locations were never resolved.

Solutions

  1. Ensure all counts have resolved pack locations (run the count/lookup iteration) before passing them to `iter_from_counts`.
  2. Filter or reject `NotLookedUp` counts before calling (see validation code), or re-resolve them against the ODB.
  3. If intentionally packing without locations, use the code path that doesn't require resolved counts.

Example fix

// before
let iter = iter_from_counts(counts, ...)?; // panics if some counts are NotLookedUp
// after
if counts.iter().any(|c| matches!(c.entry_pack_location, PackLocation::NotLookedUp)) {
    return Err(anyhow!("all counts must have resolved pack locations"));
}
let iter = iter_from_counts(counts, ...)?;
Defensive patterns

Strategy: validation

Validate before calling

// Reject unresolved counts before constructing the iterator
if counts.iter().any(|c| matches!(c.entry_pack_location, PackLocation::NotLookedUp)) {
    return Err(anyhow!("counts must be location-resolved before iter_from_counts"));
}

Type guard

fn all_resolved(counts: &[Count]) -> bool {
    counts.iter().all(|c| matches!(c.entry_pack_location, PackLocation::LookedUp(_)))
}

Try / catch

// Wrap iterator construction to convert panic into an error
let iter = std::panic::catch_unwind(AssertUnwindSafe(|| iter_from_counts(counts, ...)))
    .map_err(|p| anyhow!("unresolved counts passed to iter_from_counts: {:?}", p))??;

Prevention

When it happens

Trigger: Calling the public `iter_from_counts` constructor with counts whose `entry_pack_location` values include `PackLocation::NotLookedUp` (locations not resolved beforehand); the `_ => _` comparator arm then panics.

Common situations: Building pack output from counts collected without the lookup/resolution phase, mixing counts from different configurations (some resolved, some not), or version changes in how counts are produced upstream.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08). Data as JSON: /api/errors/01e0f49c48909a1a. Report an issue: GitHub.

Appendix: source

Thrown at gix-pack/src/data/output/entry/iter_from_counts.rs:112

            .expect("infallible - we ignore none-existing objects");
            progress.lock().show_throughput(start);
        }
        let counts_range_by_pack_id = match mode {
            Mode::PackCopyAndBaseObjects => {
                let mut progress = progress.add_child_with_id("sorting".into(), ProgressId::SortEntries.into());
                progress.init(Some(counts.len()), gix_features::progress::count("counts"));
                let start = std::time::Instant::now();

                use crate::data::output::count::PackLocation::*;
                counts.sort_by(|lhs, rhs| match (&lhs.entry_pack_location, &rhs.entry_pack_location) {
                    (LookedUp(None), LookedUp(None)) => Ordering::Equal,
                    (LookedUp(Some(_)), LookedUp(None)) => Ordering::Greater,
                    (LookedUp(None), LookedUp(Some(_))) => Ordering::Less,
                    (LookedUp(Some(lhs)), LookedUp(Some(rhs))) => lhs
                        .pack_id
                        .cmp(&rhs.pack_id)
                        .then(lhs.pack_offset.cmp(&rhs.pack_offset)),
                    (_, _) => unreachable!("counts were resolved beforehand"),
                });

                let mut index: Vec<(u32, std::ops::Range<usize>)> = Vec::new();
                let mut chunks_pack_start = counts.partition_point(|e| e.entry_pack_location.is_none());
                let mut slice = &counts[chunks_pack_start..];
                while !slice.is_empty() {
                    let current_pack_id = slice[0].entry_pack_location.as_ref().expect("packed object").pack_id;
                    let pack_end = slice.partition_point(|e| {
                        e.entry_pack_location.as_ref().expect("packed object").pack_id == current_pack_id
                    });
                    index.push((current_pack_id, chunks_pack_start..chunks_pack_start + pack_end));
                    slice = &slice[pack_end..];
                    chunks_pack_start += pack_end;
                }

                progress.set(counts.len());
                progress.show_throughput(start);

View on GitHub (pinned to e73179060b)