{"record":{"id":"01e0f49c48909a1a","repo":"GitoxideLabs/gitoxide","slug":"counts-were-resolved-beforehand","errorCode":null,"errorMessage":"counts were resolved beforehand","messagePattern":"counts were resolved beforehand","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gix-pack/src/data/output/entry/iter_from_counts.rs","lineNumber":112,"sourceCode":"            .expect(\"infallible - we ignore none-existing objects\");\n            progress.lock().show_throughput(start);\n        }\n        let counts_range_by_pack_id = match mode {\n            Mode::PackCopyAndBaseObjects => {\n                let mut progress = progress.add_child_with_id(\"sorting\".into(), ProgressId::SortEntries.into());\n                progress.init(Some(counts.len()), gix_features::progress::count(\"counts\"));\n                let start = std::time::Instant::now();\n\n                use crate::data::output::count::PackLocation::*;\n                counts.sort_by(|lhs, rhs| match (&lhs.entry_pack_location, &rhs.entry_pack_location) {\n                    (LookedUp(None), LookedUp(None)) => Ordering::Equal,\n                    (LookedUp(Some(_)), LookedUp(None)) => Ordering::Greater,\n                    (LookedUp(None), LookedUp(Some(_))) => Ordering::Less,\n                    (LookedUp(Some(lhs)), LookedUp(Some(rhs))) => lhs\n                        .pack_id\n                        .cmp(&rhs.pack_id)\n                        .then(lhs.pack_offset.cmp(&rhs.pack_offset)),\n                    (_, _) => unreachable!(\"counts were resolved beforehand\"),\n                });\n\n                let mut index: Vec<(u32, std::ops::Range<usize>)> = Vec::new();\n                let mut chunks_pack_start = counts.partition_point(|e| e.entry_pack_location.is_none());\n                let mut slice = &counts[chunks_pack_start..];\n                while !slice.is_empty() {\n                    let current_pack_id = slice[0].entry_pack_location.as_ref().expect(\"packed object\").pack_id;\n                    let pack_end = slice.partition_point(|e| {\n                        e.entry_pack_location.as_ref().expect(\"packed object\").pack_id == current_pack_id\n                    });\n                    index.push((current_pack_id, chunks_pack_start..chunks_pack_start + pack_end));\n                    slice = &slice[pack_end..];\n                    chunks_pack_start += pack_end;\n                }\n\n                progress.set(counts.len());\n                progress.show_throughput(start);\n","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gix-pack/src/data/output/entry/iter_from_counts.rs#L94-L130","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure all counts have resolved pack locations (run the count/lookup iteration) before passing them to `iter_from_counts`.","Filter or reject `NotLookedUp` counts before calling (see validation code), or re-resolve them against the ODB.","If intentionally packing without locations, use the code path that doesn't require resolved counts."],"exampleFix":"// before\nlet iter = iter_from_counts(counts, ...)?; // panics if some counts are NotLookedUp\n// after\nif counts.iter().any(|c| matches!(c.entry_pack_location, PackLocation::NotLookedUp)) {\n    return Err(anyhow!(\"all counts must have resolved pack locations\"));\n}\nlet iter = iter_from_counts(counts, ...)?;","handlingStrategy":"validation","validationCode":"// Reject unresolved counts before constructing the iterator\nif counts.iter().any(|c| matches!(c.entry_pack_location, PackLocation::NotLookedUp)) {\n    return Err(anyhow!(\"counts must be location-resolved before iter_from_counts\"));\n}","typeGuard":"fn all_resolved(counts: &[Count]) -> bool {\n    counts.iter().all(|c| matches!(c.entry_pack_location, PackLocation::LookedUp(_)))\n}","tryCatchPattern":"// Wrap iterator construction to convert panic into an error\nlet iter = std::panic::catch_unwind(AssertUnwindSafe(|| iter_from_counts(counts, ...)))\n    .map_err(|p| anyhow!(\"unresolved counts passed to iter_from_counts: {:?}\", p))??;","preventionTips":["Only feed counts into `iter_from_counts` that came from a full lookup/resolution pass.","Add an assertion/filter for `NotLookedUp` counts at API boundaries.","When mixing counts from multiple sources, re-resolve them all against the same ODB first."],"tags":["panic","pack","unresolved-state","internal-invariant","rust"],"backgroundTag":"invalid-state-transition","analyzedSha":"e73179060badf27222d790981fac3f84c1830a7e","analyzedAt":"2026-09-08T11:26:50.865Z","contentChangedAt":"2026-09-08T11:26:50.865Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}