Repacking
How repackRepository consolidates packs by reusing stored entries, and how it verifies the result.
Object storage accumulates one new pack file per push forever unless
something consolidates them. repackRepository does that consolidation.
repackRepositoryrepackRepository(repo: Repo, options?: RepackOptions, hooks?: HttpHooks): Promise<string[]>| Parameter | Type | Description |
|---|---|---|
repo | Repo | The repository to consolidate. |
options? | RepackOptions | { threshold?: number } — skip below this many accumulated packs. Default 4. |
hooks? | HttpHooks | See the overview page. |
Returns: Promise<string[]>
The gitdir-relative paths of the pack files it removed locally — every
.pack, .idx, .keep, .bitmap and .rev except the new pack's. After syncing the new consolidated pack to any secondary
storage, delete those same paths there too — see the note in
Serving clones and pushes.
Also exported standalone (not just as part of applyReceivePack's
push-triggered repack) for out-of-band maintenance — clearing a backlog
that accumulated before this threshold existed, on a repo that won't
otherwise get repacked until its next push crosses it again.
Why the threshold
Consolidating is O(total repo object count) — a full reachability traversal plus a full pack build over everything, not just what the triggering push added. Paying that cost on every single push makes push latency grow with total repo size forever, not with the size of the just-pushed delta. Below the threshold, clone/fetch's own near-O(1) pack search (isomorphic-git checks each pack's index) is already cheap enough that consolidating isn't worth the extra push latency — skip it and let the next call re-check once enough small pushes have piled up packs.
How the consolidated pack is built
When every reachable object is already in a pack (the normal case — pushes
arrive as packs), repackRepository builds the new pack by copying stored
entries out of the existing packs, the same pack reuse upload-pack uses
(see Serving clones and pushes).
Nothing is inflated or re-deflated, deltas are kept, and the consolidated
pack is no larger than the packs it replaces. Only when some objects are
loose does it fall back to reading each object, recomputing its SHA-1 from
the bytes returned, and writing a pack of full, non-deltified entries.
Submodule gitlinks (tree entries with mode 160000) point at commits in
another repository; the walk doesn't follow them, so a repository with
submodules in its history consolidates normally.
How the result is verified
isomorphic-git's packed-object read path never checks a resolved object's
SHA-1 against the requested oid, so the new pack is not trusted on the
strength of how it was built. The reachability walk that decides what the
pack must contain bounds its in-flight reads with one semaphore per walk
(MAX_CONCURRENT_READS), and git.indexPack then resolves every entry —
deltas included — and derives each oid by hashing the result. Every reachable
oid must appear in that index; extra delta bases carried over by reuse are
fine.
If any reachable oid is missing from the new index, the new pack and index are deleted and the repack aborts — old packs are never removed on anything less than full certainty the replacement is complete.
Repack failure is non-fatal
If a repack throws partway through, it's caught and reported via
hooks.onWarn rather than failing the push that triggered it — a push
still succeeds even when its trailing repack doesn't; the repo just carries
one extra pack file until the next attempt.