Low-level internals of the pack file subsystem: index lookup, delta chain
resolution, zlib inflation, object caching, pack planning, and I/O
strategy. This document complements git-pack-execution.md, which covers
the high-level execution pipeline and strategy selection.
The path from a pack file on disk to a materialized object in memory involves seven cooperating layers:
flowchart TD
IDX["Pack Index (.idx)<br/>pack_idx.rs"] -->|"OID → offset"| MIDX["MIDX<br/>midx.rs"]
MIDX -->|"OID → (pack_id, offset)"| PLAN["Pack Planner<br/>pack_plan.rs"]
PLAN -->|"delta closure + exec order"| EXEC["Pack Executor<br/>pack_exec.rs"]
EXEC -->|"offset → bytes"| IO["Pack I/O<br/>pack_io.rs, pack_reader.rs"]
IO -->|"compressed bytes"| INFLATE["Inflate Dispatch<br/>pack_inflate.rs, pack_decode.rs,<br/>pack_inflate_libdeflate.rs"]
INFLATE -->|"inflated payload"| DELTA["Delta Application<br/>pack_inflate.rs, pack_delta.rs"]
DELTA -->|"materialized object"| CACHE["Pack Cache<br/>pack_cache.rs"]
- Index lookup resolves an OID to a
(pack_id, byte_offset)pair via fanout tables and binary search. - Pack planning expands the delta closure for candidate blobs,
building a sorted
need_offsetsarray and optional DFS execution order. - Pack execution drives the decode loop, dispatching by entry type.
- I/O provides mmap-backed or slice-backed byte access to pack files.
- Inflate dispatch decompresses pack entry payloads with hard output caps, routing small exact-size non-delta entries through libdeflate and leaving large or delta payloads on the streaming flate2 path.
- Delta application reconstructs target objects from base + delta instruction streams.
- Caching stores decoded objects in a tiered set-associative CLOCK cache to avoid re-decoding shared delta bases.
| Type | Description |
|---|---|
IdxView<'a> |
Zero-copy view over a pack index v2 file |
IdxOidIter<'a> |
Iterator yielding (oid_bytes, index) pairs in sorted order |
IdxError |
Pack index parsing errors (corrupt, unsupported version, size limit) |
| Type | Description |
|---|---|
PackFile<'a> |
Zero-copy view over pack bytes, excluding trailing hash |
PackHeader |
Cached pack header metadata (oid_len, data_end) for repeated parsing |
EntryHeader |
Parsed entry header: uncompressed size, data_start offset, entry kind |
EntryKind |
Entry type: NonDelta { kind }, OfsDelta { base_offset }, RefDelta { base_oid } |
ObjectKind |
Non-delta object kind: Commit, Tree, Blob, Tag |
PackParseError |
Header/entry parsing errors |
InflateError |
Zlib decompression errors (limit exceeded, truncated, stalled, backend) |
DeltaError |
Delta application errors (truncated, size mismatch, copy out of range) |
PackObjectError |
Composite error wrapping parse, inflate, and delta errors |
| Type | Description |
|---|---|
PackDecodeLimits |
Size caps: max_header_bytes, max_object_bytes, max_delta_bytes |
PackDecodeError |
Decode errors including ObjectTooLarge and DeltaTooLarge |
| Type | Description |
|---|---|
PackIo<'a> |
External base resolver with lazy mmap-backed pack cache and loose object fallback |
PackIoLimits |
Decode limits plus cross-pack delta depth cap |
PackIoError |
I/O errors (missing MIDX, pack not found, delta depth exceeded, loose object failures) |
| Type | Description |
|---|---|
PackReader (trait) |
Read-only read_at / read_exact_at interface for deterministic I/O |
SlicePackReader<'a> |
In-memory pack reader over a byte slice |
PackReadError |
Reader errors: out of range, short read, I/O |
| Type | Description |
|---|---|
PackCache |
Two-tier (small + large) set-associative CLOCK cache |
CachedObject<'a> |
Cache hit result: object kind + borrowed bytes |
PackCacheEvictionStats |
Instrumentation: insert attempts, victim selections, dependency marks |
| Type | Description |
|---|---|
PackCandidate |
Blob candidate mapped to (pack_id, offset) with path context |
LooseCandidate |
Blob candidate not in any pack (loose object) |
PackCandidateSink (trait) |
Sink trait for packed/loose candidate output |
PackCandidateCollector<'midx> |
Collector that resolves OIDs to pack offsets via MIDX |
| Type | Description |
|---|---|
PackPlan |
Complete decode plan for a single pack file |
DeltaDep |
Delta dependency record: offset, kind, base location, data_start, delta_size |
DeltaKind |
Ofs (OFS_DELTA) or Ref (REF_DELTA) |
BaseLoc |
Offset(u64) for in-pack bases or External { oid } for cross-pack/unresolved |
CandidateAtOffset |
(offset, cand_idx) pair for sorted candidate lookup |
PackPlanStats |
Summary: candidate count, need count, external bases, forward deps, span |
PackPlanConfig |
Planning limits: max delta depth, header bytes, worklist entries, base lookups |
PackPlanError |
Planning errors: parse failure, cycle detected, worklist/lookup limit exceeded |
PackView<'a> |
Header-only pack view for planning (no inflate) |
OidResolver (trait) |
Resolves OIDs to (pack_id, offset) — implemented by MidxView |
Pack index files (.idx v2) provide O(log N) lookup from OID to pack byte
offset. The implementation in pack_idx.rs is zero-copy: all table slices
borrow directly from the mapped file bytes.
+------------------+
| Magic (4B) | 0xff 't' 'O' 'c'
| Version (4B) | Big-endian 2
+------------------+
| Fanout (1024B) | 256 × u32 BE cumulative counts
+------------------+
| OID Table | N × oid_len bytes (lexicographically sorted)
+------------------+
| CRC Table | N × 4 bytes (skipped; not validated)
+------------------+
| Offset Table | N × 4 bytes (MSB=1 → large offset indirection)
+------------------+
| Large Offsets | M × 8 bytes (optional, for packs > 2 GB)
+------------------+
| Pack Checksum | oid_len bytes
| Idx Checksum | oid_len bytes
+------------------+
-
Fanout bucket: The first byte of the OID selects a fanout entry.
fanout[first_byte]gives the upper bound (exclusive) of the range;fanout[first_byte - 1](or 0) gives the lower bound. Complexity: O(1). (IdxView::fanout,pack_idx.rs) -
Binary search: Within the fanout bucket, binary search the OID table. OIDs are stored as raw bytes in lexicographic order. Complexity: O(log B) where B is the bucket size.
-
Offset resolution: The index at the match position maps to the offset table. Small offsets (MSB clear) are direct 32-bit values. Large offsets (MSB set) index into the large-offset table for a full 64-bit pack offset. (
IdxView::offset_at,pack_idx.rs)
Individual pack indices are merged into a multi-pack index (MIDX) that
provides a single namespace over all packs. The MIDX find_oid method
(midx.rs) uses the same fanout + binary search pattern. For sorted
OID streams, find_oid_sorted (midx.rs) maintains a monotonic
cursor that avoids restarting the binary search from the bucket boundary,
enabling merge-join style mapping.
Git delta encoding stores objects as instruction streams that reconstruct a target from a base. Two delta types exist, distinguished by how the base is referenced:
| Type | Tag | Base Reference | Resolution |
|---|---|---|---|
OFS_DELTA |
6 | Negative byte offset in same pack | Direct seek within pack bytes |
REF_DELTA |
7 | 20/32-byte OID | MIDX lookup, may cross packs or fall to loose objects |
A delta payload begins with two LEB128 varints encoding the expected base size and result size, followed by a sequence of opcodes:
| Opcode | Bit Pattern | Description |
|---|---|---|
| Copy | 1xxxxxxx |
Copy bytes from base: low 4 bits select offset bytes, bits 4-6 select size bytes (little-endian). Size 0 encodes 0x10000. |
| Insert | 0xxxxxxx (nonzero) |
Insert the next N bytes from the delta stream literally |
| Reserved | 00000000 |
Invalid — returns DeltaError::BadCommandZero |
Copy parameter decoding (decode_copy_params, pack_inflate.rs) is
on the hot loop. It pre-computes the total parameter byte count from
popcount(off_mask) + popcount(size_mask), performs one bounds check, then
decodes with tight branches using get_unchecked.
apply_delta (pack_inflate.rs) writes directly into pre-reserved
Vec spare capacity via raw pointer arithmetic, eliminating per-chunk
bounds checks and Vec::extend overhead:
- Parse header varints (base size, result size).
- Validate base size matches actual base length.
- Reserve
result_sizebytes in the outputVec. - Invoke
interpret_delta_bodywhich decodes opcodes and emits chunks via a closure that writes through a rawout_ptr. - After all opcodes,
set_len(written)finalizes the output.
The output is capped by max_out to prevent unbounded allocation on
corrupt deltas. A streaming variant (apply_delta_into, pack_inflate.rs)
sends chunks to a caller-provided sink without buffering the full result.
When a delta's base is itself a delta, the chain must be walked to a non-delta root. Two resolution paths exist:
ObjectStore path (object_store.rs, for tree loading):
flowchart TD
START["entry_header_at(offset)"] --> KIND{Entry type?}
KIND -->|NonDelta| INFLATE["inflate → result_buf"]
KIND -->|OFS_DELTA| DCACHE{"TreeDeltaCache hit?"}
DCACHE -->|hit| APPLY_CACHED["apply_delta(cached_base, delta)"]
DCACHE -->|miss| PUSH["push DeltaFrame<br/>follow base_offset"]
PUSH --> START
KIND -->|REF_DELTA| RECURSIVE["recursive load via MIDX"]
RECURSIVE --> APPLY_EXT["apply_delta(external_base, delta)"]
INFLATE --> EMPTY{"stack empty?"}
EMPTY -->|yes| DONE["return result"]
EMPTY -->|no| UNWIND["unwind: apply each frame<br/>swap(base_buf, result_buf)"]
APPLY_CACHED --> UNWIND
APPLY_EXT --> UNWIND
UNWIND --> DONE
Phase 1 (walk forward) collects lightweight DeltaFrame structs without
inflating payloads. Phase 2 (unwind backward) inflates and applies each
frame in reverse, alternating base_buf and result_buf via
std::mem::swap to eliminate per-hop allocation. Depth is bounded by
MAX_DELTA_DEPTH (64).
Pack executor path (pack_exec.rs): Uses pre-computed DeltaDepHot
metadata from the plan to skip header parsing on the fast path. Falls back
to walk_delta_chain_to_root + unwind_and_build_base for chains not
captured by the planner.
All pack entry payloads are zlib-compressed. The inflate layer provides four entry points with different allocation and lifetime strategies:
| Function | File | Decompressor | Output | Use Case |
|---|---|---|---|---|
inflate_limited_with |
pack_inflate.rs |
Caller-owned Decompress |
Vec spare capacity, hard max_out cap |
Pack executor hot path |
inflate_limited |
pack_inflate.rs |
Thread-local InflateScratch |
Same as above | Convenience for single-threaded callers |
inflate_exact_with |
pack_inflate.rs |
Caller-owned | Exact expected bytes or error |
Large non-delta entries with known size |
inflate_stream |
pack_inflate.rs |
Thread-local | Chunked callback, exact expected total |
Large objects that should not be buffered |
inflate_nondelta_exact |
pack_inflate_libdeflate.rs |
Thread-local libdeflate decompressor | Exact expected bytes plus compressed bytes consumed |
Small non-delta entries with known size |
inflate_limited_with writes directly into Vec::spare_capacity_mut(),
capped so total output never exceeds max_out:
- The
Decompressis reset (de.reset(true)) before each use. - Output
Vecis cleared andreserve(max_out)pre-allocates capacity. - Debug builds poison spare capacity with
0xDEto detect uninitialized reads. - Each loop iteration calls
decompress_uninitinto the spare slice. set_lenis advanced by the produced byte count (validated againstmax_outandcapacity).- Terminates on
StreamEnd(returning consumed input bytes) or error.
The thread-local InflateScratch (pack_inflate.rs) bundles a
Decompress instance and a 64 KiB scratch buffer into a single
RefCell, halving the TLS lookup cost. Small non-delta entry decoding
also uses a thread-local libdeflate decompressor in
pack_inflate_libdeflate.rs. Reentrant callers can still use
inflate_limited_with and inflate_exact_with, but
inflate_entry_payload may panic on same-thread reentrancy because it
borrows the flate2 TLS scratch (InflateScratch) before dispatching.
inflate_entry_payload_with avoids the flate2 TLS borrow but may still
panic when libde is None and the libdeflate TLS fallback is selected.
pack_decode.rs wraps the raw inflate functions with size enforcement:
entry_header_at(pack_decode.rs) parses the entry header and rejects non-delta entries exceedingmax_object_bytesand delta entries exceedingmax_delta_bytesbefore any inflation occurs.inflate_entry_payload_with(pack_decode.rs) inflates non-delta entries with an exact-size contract, routing payloads at or belowLIBDEFLATE_THRESHOLD_BYTESthrough libdeflate and leaving larger non-delta or delta entries on the flate2 path.
PackCache (pack_cache.rs) is a two-tier, set-associative,
preallocated cache keyed by pack byte offset:
| Tier | Slot Size | Budget | Purpose |
|---|---|---|---|
| Small | ≤ 64 KiB (DEFAULT_SMALL_SLOT_SIZE) |
~2/3 of capacity | Majority of objects |
| Large | ≤ 2 MiB (DEFAULT_LARGE_SLOT_SIZE) |
~1/3 of capacity | Popular delta bases in 64 KiB–2 MiB range |
Objects exceeding 2 MiB are not cached. If total capacity is below
32 MiB (MIN_LARGE_TIER_BYTES), only the small tier is enabled.
Both tiers use 4-way set associativity (WAYS = 4). The set count is
rounded down to a power of two. Set selection uses a MurmurHash3 fmix64
finalizer (hash_offset, pack_cache.rs) folded to 32 bits, then
masked to the set count.
offset ──→ fmix64(offset) ──→ XOR-fold to u32 ──→ & (sets - 1) = set_index
Default eviction is CLOCK (second-chance):
- On get hit: set the slot's
clockbit to 1. - On insert (set full): sweep from the persisted
clock_handposition:- If slot is invalid or
clock == 0: evict, advance hand. - If
clock == 1: clear to 0 (second chance), advance hand. - After a full sweep with no victim: evict the hand position unconditionally.
- If slot is invalid or
Enabled via SCANNER_RS_PACK_CACHE_EVICTION=dependency_clock. This
variant adds bounded protection for likely delta bases:
- On get hit: record the hit offset as a
pending_dependency_hint. - On next insert: if the previously-hit base is within
DEPENDENCY_HINT_MAX_DISTANCE(16 MiB) of the new entry's offset, set the base'sdependency_guardtoDEPENDENCY_GUARD_MAX(2). - During victim selection: guarded slots are skipped (guard decremented)
for up to
WAYS * 2probes. After the bounded sweep, a forced eviction occurs.
This protects delta bases that are likely to be needed by nearby entries, reducing re-decode costs for deep delta chains.
new(capacity_bytes): Allocates both tiers upfront. No hot-path allocation.get(offset): Probes small tier first, then large. ReturnsCachedObjectwith borrowed&[u8].insert(offset, kind, bytes): Routes to small or large tier by byte length. Returnsfalsefor oversize entries.reset(): Invalidates all entries without releasing storage (Tiger-Style reuse across scans).ensure_capacity(bytes): Grows if needed; resets otherwise. Only allocates when a larger repo is encountered for the first time.
Pack planning (pack_plan.rs) pre-computes the full set of offsets that
must be decoded to satisfy a set of candidate blobs, including transitive
delta bases.
flowchart TD
CANDS["Candidate blobs<br/>(OID, pack_id, offset)"] --> BUCKET["Bucket by pack_id"]
BUCKET --> PARSE["Parse entry headers<br/>for candidate offsets"]
PARSE --> EXPAND["BFS base expansion<br/>(up to max_delta_depth)"]
EXPAND --> SORT["Merge + sort + dedup<br/>→ need_offsets"]
SORT --> DEPS["Build delta_deps<br/>(DeltaDep per delta entry)"]
DEPS --> INDEX["Build delta_dep_index<br/>(dense O(1) lookup)"]
INDEX --> EXEC["Build exec_order<br/>(DFS with thin-subtree-first)"]
EXEC --> PLAN["PackPlan"]
-
Bucket (
bucket_pack_candidates_sparse,pack_plan.rs): Group candidates bypack_id. Only touched packs allocate a bucket. -
Parse (
parse_entry,pack_plan.rs): Parse each candidate's entry header to determine its type. REF_DELTA entries resolve their base OID via theOidResolver(typicallyMidxView). Results are cached in aHashMap<u64, ParsedEntry>. -
Expand (BFS worklist,
pack_plan.rs): For each delta entry, enqueue its base offset. Continue up tomax_delta_depth(default 64). Pack-local bases are added todiscovered_base_offsets; cross-pack REF_DELTA bases are recorded asBaseLoc::External. -
Merge (
pack_plan.rs): Combine candidate offsets and discovered base offsets into a sorted, deduplicatedneed_offsetsarray. -
Delta deps (
build_delta_deps,pack_plan.rs): Emit oneDeltaDepper delta entry, sorted by offset. The densedelta_dep_index(build_delta_dep_index,pack_plan.rs) maps eachneed_offsets[i]to itsdelta_depsposition in O(1) via a forward-only merge cursor. -
Exec order (
build_exec_order,pack_plan.rs): Build a cache-aware DFS execution order. Thin subtrees are processed first (ascending descendant count) to minimize live cache entries. ReturnsNonewhen natural ascending order is correct (fast path).
O((N + E) log(N + E)) where N is unique candidate offsets and E is
discovered base offsets. The final sort dominates.
| Limit | Default | Error |
|---|---|---|
| Max delta depth | 64 | Chain truncated |
| Max worklist entries | 1,000,000 | WorklistLimitExceeded |
| Max base lookups | 1,000,000 | BaseLookupLimitExceeded |
| Max header bytes | 64 | PackParseError::HeaderTooLong |
Cycles in the delta graph are detected by the DFS topological sort; if
order.len() != n after the DFS, DeltaCycleDetected is returned.
Pack files are accessed via memory-mapped I/O. Two access patterns exist:
Execution path (runner_exec.rs): mmap_pack_files maps required
packs with MADV_SEQUENTIAL / POSIX_FADV_SEQUENTIAL hints. Mmap
lifecycle is managed by the scheduler; all workers share the same
mappings.
PackIo path (pack_io.rs): Used for external base resolution
(cross-pack REF_DELTA) and loose object loading. Pack files are
lazily mapped on first access and cached as Arc<Mmap> for the
lifetime of the PackIo instance.
// pack_io.rs (simplified)
if self.pack_cache[idx].is_none() {
let file = File::open(path)?;
let mmap = unsafe { Mmap::map(&file)? };
advise_sequential(&file, &mmap);
self.pack_cache[idx] = Some(Arc::new(mmap));
}Sequential access hints (advise_sequential, pack_io.rs):
- Linux:
posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL) - All Unix:
madvise(ptr, len, MADV_SEQUENTIAL)
PackReader (pack_reader.rs) is a trait providing read_at /
read_exact_at for deterministic I/O. This enables simulation testing
with injected short reads and corruption:
| Implementation | Source | Purpose |
|---|---|---|
SlicePackReader<'a> |
pack_reader.rs |
In-memory bytes for tests |
BytesView |
pack_reader.rs |
Shared-ownership byte view |
When an OID is not found in any pack (MIDX miss), PackIo::load_loose_object
(pack_io.rs) falls back to the objects/ directory:
- Hex-encode the OID.
- Split into
XX/YYYYYY...directory/file path. - Read the file, inflate with
inflate_limited. - Parse the
<kind> <size>\0<payload>header. - Validate payload length against declared size and
max_object_bytes.
| Concept | File |
|---|---|
| Pack index v2 parser | pack_idx.rs |
| Pack index OID iterator | pack_idx.rs |
| Pack header/entry parsing | pack_inflate.rs |
| Zlib inflate (bounded) | pack_inflate.rs |
| Thread-local inflate scratch | pack_inflate.rs |
| Libdeflate inflate backend | pack_inflate_libdeflate.rs |
| Delta application | pack_inflate.rs |
| Delta copy decoding | pack_inflate.rs |
| Delta re-export | pack_delta.rs |
| Bounded decode wrappers | pack_decode.rs |
| Decode limits | pack_decode.rs |
| Pack I/O (external bases) | pack_io.rs |
| Pack I/O (loose fallback) | pack_io.rs |
| Pack I/O limits | pack_io.rs |
| Pack I/O mmap hints | pack_io.rs |
| ExternalBaseProvider impl | pack_io.rs |
| Pack reader trait | pack_reader.rs |
| Pack cache (tiered CLOCK) | pack_cache.rs |
| Cache tier internals | pack_cache.rs |
| Dependency-clock eviction | pack_cache.rs |
| Cache hash function | pack_cache.rs |
| Pack candidates | pack_candidates.rs |
| Candidate collector | pack_candidates.rs |
| Pack plan model | pack_plan_model.rs |
| Pack plan builder | pack_plan.rs |
| Delta closure expansion | pack_plan.rs |
| Delta dep index | pack_plan.rs |
| DFS exec order | pack_plan.rs |
| Pack plan config | pack_plan.rs |
docs/scanner-git/git-pack-execution.md— high-level execution pipeline, strategy selection, sharding heuristicsdocs/scanner-git/git-scanning.md— end-to-end scanning pipeline overview