GitPackfiles

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

Bash
# 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

Text
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

Text
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% smaller
The .git/objects/pack/ Directory

Inspect the pack directory

Bash
ls -lh .git/objects/pack/

Pack directory contents

Text
.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

Bash
# 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

Bash
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)
Note
Git runs `git gc --auto` automatically when certain thresholds are hit (default: 6700 loose objects or 50 packs). You rarely need to run it manually in normal workflows.
Manually Repacking

Manual repack commands

Bash
# 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

Bash
# Verify pack integrity and show all objects
git verify-pack -v .git/objects/pack/*.idx | head -20

verify-pack output

Text
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

Bash
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

Bash
# 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
When you do a shallow clone, the server sends a special pack file that contains only the objects reachable within the requested depth. The `/.git/shallow` file records the graft points. This pack file is typically much smaller than a full pack.

Pack size: shallow vs full clone

Bash
# 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 exist

  • git gc — run garbage collection: repack loose objects, prune unreachable objects

  • git gc --aggressive — stronger delta compression (use monthly on large repos)

  • git repack -a -d -b — manual repack with bitmap generation

  • git verify-pack -v <idx> — inspect pack contents and verify integrity

  • git fsck — verify all objects (loose and packed) are uncorrupted

Tip
On Windows, repositories with many loose objects (before GC) can become very slow because NTFS has poor performance with many small files. Running `git gc` regularly is more important on Windows than on macOS or Linux.
Warning
Never manually modify or delete files in `/.git/objects/pack/`. Deleting a pack file will make all objects in it permanently inaccessible, destroying history. Always use `git gc` or `git repack` to manage packs.