Pack Files
When you first create Git objects — by running git add and git commit — Git writes each object as a separate compressed file under /.git/objects/. These are called loose objects. As a repository grows, thousands of loose object files become inefficient to store and transfer. Git solves this with pack files: a format that combines many objects into one file using delta compression, dramatically reducing storage and network transfer size.
Loose Objects
Every git add creates a blob file. Every git commit creates a tree and a commit file. These are stored one-per-file in /.git/objects/:
Counting loose objects
# Count loose objects and their total size git count-objects # 847 objects, 3421 kilobytes # Verbose breakdown git count-objects -v
git count-objects -v output
count: 847 ← number of loose objects size: 3421 ← size of loose objects in KiB in-pack: 0 ← objects stored in pack files packs: 0 ← number of pack files size-pack: 0 ← size of pack files in KiB prune-packable: 0 ← loose objects also in packs garbage: 0 ← corrupt/unknown files in objects/ size-garbage: 0
What Pack Files Are
A pack file is a binary container that stores many Git objects together using delta compression. Instead of storing every version of a file independently, a pack file stores one full version and then stores subsequent versions as just the difference (delta) from the previous version. For text files that change incrementally (source code), this produces extremely compact storage.
Delta compression diagram
Without pack (loose objects):
blob v1: "function greet() { return 'hello'; }" → stored full (42 bytes compressed)
blob v2: "function greet() { return 'world'; }" → stored full (42 bytes compressed)
blob v3: "function greet() { return 'foo'; }" → stored full (41 bytes compressed)
Total: ~126 bytes + overhead
With pack + delta compression:
base: "function greet() { return 'hello'; }" → stored full (42 bytes)
delta v2: change 'hello' to 'world' → stored as 7-byte patch
delta v3: change 'world' to 'foo' → stored as 5-byte patch
Total: ~54 bytes — 57% smallerThe .git/objects/pack/ Directory
Inspect the pack directory
ls -lh .git/objects/pack/
Pack directory contents
.git/objects/pack/ ├── pack-a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2.idx ← index file ├── pack-a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2.pack ← pack data └── pack-a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2.bitmap ← bitmap (optional, for fast clone) Each pack has two required files: .pack — the actual compressed object data .idx — an index mapping SHA hashes → offsets in the .pack file
The *.idx file is a lookup table. When Git needs to find an object by hash, it uses the index to locate its byte offset within the *.pack file without scanning the entire pack linearly.
Triggering Garbage Collection and Repacking
Manual garbage collection
# Run full GC: expire reflog, repack loose objects into packs, prune unreachable objects git gc # Aggressive GC: better delta compression (slower, use periodically) git gc --aggressive # GC output # Enumerating objects: 1847, done. # Counting objects: 100% (1847/1847), done. # Delta compression using up to 10 threads # Compressing objects: 100% (1243/1243), done. # Writing objects: 100% (1847/1847), done. # Total 1847 (delta 891), reused 0 (delta 0), pack-reused 0
Check objects after GC
git count-objects -v # count: 0 ← no more loose objects # size: 0 # in-pack: 1847 ← all objects are now in a pack # packs: 1 # size-pack: 2156 ← pack is only 2.1 MB (was 3.4 MB loose)
Manually Repacking
Manual repack commands
# Repack all objects into a single pack git repack -a -d # Repack aggressively (slower, better compression) git repack -a -d --depth=250 --window=250 # -a: pack all objects # -d: delete loose objects after packing # --depth: maximum delta chain depth (higher = smaller file, slower access) # --window: number of objects to compare for delta (higher = better compression)
Inspecting Pack File Contents
Inspect a pack file with git verify-pack
# Verify pack integrity and show all objects git verify-pack -v .git/objects/pack/*.idx | head -20
verify-pack output
a3f1c2d83e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b commit 243 169 12
9e2b0f1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a commit 180 126 181
8f14e0b9bbd2c8fc72c99b35d0a5b61e07b19b3c blob 1024 512 307
6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f blob 18 28 819 1 8f14e0b9bbd2...
↑
Format: hash type size compressed offset [delta-base]
The last entry is a delta (18 bytes → tiny diff)Find the largest objects in a pack
git verify-pack -v .git/objects/pack/*.idx | grep -v '^non delta' | sort -k 4 -n -r | head -10 # Shows the 10 largest objects by size # Useful for finding accidentally committed large files
Bitmap Indexes
A bitmap file (*.bitmap) is an optional companion to a pack file that dramatically speeds up clone and fetch operations. It precomputes reachability information so Git can quickly determine which objects need to be sent to a client.
Generate bitmap for faster clone/fetch
# Repack with bitmap generation (usually done by server-side git) git repack -a -d -b # -b = generate bitmap # Check if bitmap exists ls .git/objects/pack/*.bitmap 2>/dev/null && echo "bitmap present" || echo "no bitmap" # Bitmaps are what make GitHub's server-side clone so fast # They reduce CPU time for computing "what objects does the client need" from # minutes to seconds on large repos
Shallow Clones and Pack Files
Pack size: shallow vs full clone
# After --depth=1 clone: ls -lh .git/objects/pack/ # pack-*.pack 12.3M ← tiny pack for one commit's worth of objects # After git fetch --unshallow: ls -lh .git/objects/pack/ # pack-*.pack 502.8M ← full history pack
Comparison: Loose Objects vs Pack Files
Property | Loose Objects | Pack Files |
|---|---|---|
Storage location | .git/objects/xx/yyyyyy... | .git/objects/pack/*.pack |
Compression | zlib per object | zlib + delta compression |
Read speed | Fast (direct file access) | Fast (indexed lookup) |
Write speed | Very fast (just write a file) | Slow (requires repacking) |
Filesystem load | Many files (bad for NTFS) | Few files (efficient) |
Transfer efficiency | Poor (sent one by one) | Excellent (single stream, deltas) |
When used | Fresh commits, before GC | After GC or clone from remote |
Deduplication | Same content = same file | Same content = same entry |
Common Pack-Related Commands
git count-objects -v— see how many loose objects vs packed objects existgit gc— run garbage collection: repack loose objects, prune unreachable objectsgit gc --aggressive— stronger delta compression (use monthly on large repos)git repack -a -d -b— manual repack with bitmap generationgit verify-pack -v <idx>— inspect pack contents and verify integritygit fsck— verify all objects (loose and packed) are uncorrupted