GitoxideLabs/gitoxide · error
ids should be ordered, and we make sure to keep ahead with…
Error message
ids should be ordered, and we make sure to keep ahead with them
What it means
While building the 256-slot fanout table of a pack index, entries are assumed to be sorted by their object ID. If the current entry's first ID byte is less than the fanout slot byte being filled, the iterator has gone backwards, meaning input IDs were not sorted; the code panics via unreachable! instead of returning an error.
Solutions
- Sort entries by full object ID (ascending, lexicographic big-endian byte order) before passing to write_to
- Deduplicate or otherwise ensure IDs strictly advance so the iterator never moves backwards
- Add a pre-write assertion that sorts and validates the id sequence in your packing code
Example fix
// before index::write_to(entries_in_insertion_order, ...) // after entries.sort_by(|a, b| a.id.as_bytes().cmp(b.id.as_bytes())); index::write_to(entries, ...)
Defensive patterns
Strategy: validation
Validate before calling
fn assert_sorted(ids: &[gix_hash::ObjectId]) -> bool {
ids.windows(2).all(|w| w[0] <= w[1])
}
assert!(assert_sorted(&ids), "ids must be ascending before index::write_to"); Try / catch
// unreachable! panics; guard by validating before the call
if !assert_sorted(&ids) { return Err("ids must be sorted by object id"); }
let bytes = index::write_to(entries.iter(), ...)?; Prevention
- Always sort entries by full big-endian object id before index writing
- Never sort by any secondary key without id as the primary key
- Add a debug assertion for id ordering in pack-generation pipelines
When it happens
Trigger: Calling `write_to` (via `gix_pack::index::write_to` / `Index::write_to`) with an iterator of `(entry, id)` pairs whose object IDs are not strictly ascending — e.g. entries collected in insertion order or sorted by a different key.
Common situations: Custom pack generation code feeding unsorted or duplicate-handled id lists; refactoring that changes sort order; sorting by OID as bytes on a platform with different byte-order assumptions.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- ' ' is not a valid configuration key
- Cannot use iter_v1() on index of type
- Cannot use iter_v2() on index of type
- BUG: tries to obtain object id from symbolic target
- BUG: expected peeled reference target but found symbolic one
AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08).
Data as JSON: /api/errors/eeb42b75bc06a7e4.
Report an issue: GitHub.
Appendix: source
Thrown at gix-pack/src/index/encode.rs:16
use std::cmp::Ordering;
pub(crate) const LARGE_OFFSET_THRESHOLD: u64 = 0x7fff_ffff;
pub(crate) const HIGH_BIT: u32 = 0x8000_0000;
pub(crate) fn fanout(iter: &mut dyn ExactSizeIterator<Item = u8>) -> [u32; 256] {
let mut fan_out = [0u32; 256];
let entries_len = iter.len() as u32;
let mut iter = iter.enumerate();
let mut idx_and_entry = iter.next();
let mut upper_bound = 0;
for (offset_be, byte) in fan_out.iter_mut().zip(0u8..=255) {
*offset_be = match idx_and_entry.as_ref() {
Some((_idx, first_byte)) => match first_byte.cmp(&byte) {
Ordering::Less => unreachable!("ids should be ordered, and we make sure to keep ahead with them"),
Ordering::Greater => upper_bound,
Ordering::Equal => {
if byte == 255 {
entries_len
} else {
idx_and_entry = iter.find(|(_, first_byte)| *first_byte != byte);
upper_bound = idx_and_entry.as_ref().map_or(entries_len, |(idx, _)| *idx as u32);
upper_bound
}
}
},
None => entries_len,
};
}
fan_out
}
View on GitHub (pinned to e73179060b)