Skip to content

Packing and Garbage Collection

Git objects go through three phases:

flowchart LR
    A["Loose Object<br/>(newly created)"] --> B["Packed Object<br/>(compressed in packfile)"]
    B --> C["Unreachable Object<br/>(no ref points to it)"]
    C --> D["Pruned Object<br/>(deleted by gc)"]

    style A fill:#e8f5e9
    style B fill:#e3f2fd
    style C fill:#fff3e0
    style D fill:#ffcdd2

When you run git add or git commitGit creates objects as individual zlib-compressed files under .git/objects/. These are called loose objects.

Performance characteristics:

  • Creation: O(1)O(1) — just write a file.
  • Lookup: O(logn)O(\log n) — filesystem directory lookup (first 2 hex chars) + file read.
  • Storage: Each object is compressed independently. No delta compression between objects.
  • Overhead: Each file consumes a filesystem inode and a disk block (minimum 4 KB, even for small objects).

For small repositories (few hundred objects), loose objects are fine. For large repositories (millions of objects), the overhead becomes significant.

When the number of loose objects exceeds gc.auto (default: 6700), Git automatically packs them into a packfile. You can also trigger packing manually:

Terminal window
## Pack all loose objects
$ git gc
## Pack aggressively (slower but better compression)
$ git gc --aggressive

A packfile (.git/objects/pack/pack-<hash>.pack) contains:

SectionDescription
HeaderMagic bytes (PACK), version (2), number of objects
ObjectsCompressed objects (delta-compressed against other objects)
TrailerSHA-1 checksum of all preceding content

Packfiles use delta compression to store similar objects efficiently. Instead of storing each object in full, Git stores one base object and then stores subsequent objects as deltas (differences from the base):

Base object: commit A (full content, 200 bytes)
Delta 1: commit B → "change author email" (20 bytes)
Delta 2: commit C → "change commit message" (15 bytes)

For the Linux kernel repository, delta compression reduces the packfile size by approximately 10×10\times compared to loose objects.

Delta objects can themselves be delta-compressed against other delta objects, forming a delta chain:

commit A (base)
└── commit B (delta of A)
└── commit C (delta of B)
└── commit D (delta of C)

Deep delta chains (depth > 50) degrade performance because Git must decompress the entire chain to access the final object. git gc limits delta depth to pack.depth (default: 50).

Each packfile has a corresponding .idx (index) file that enables O(1)O(1) lookup of objects by SHA-1 hash. The index is a sorted binary table:

OffsetSHA-1CRC32
0a3f2b1c…0x8a7f…
234b7e9d4f…0x3c2d…
567c1d2e3f…0x9e1b…

An object is unreachable if no reference (branch, tag, HEAD, reflog, stash) points to it — directly or transitively (through a tree or commit chain).

Terminal window
# Find unreachable objects
$ git fsck --unreachable
# Prune unreachable objects older than 2 weeks (default)
$ git gc
# Prune ALL unreachable objects immediately
$ git gc --prune=now