git maintenance
git maintenance
Git commands that add data (add, commit, fetch) optimize for a snappy UI. They do not spend time reshaping the whole object database. git maintenance is the operator tool that runs background (or manual) optimization tasks: commit-graph, prefetch, loose-object packing, multi-pack-index repacks, and more — without turning every git status into a full gc.
Mental model
foreground Git ──► writes packs / loose objects / refs (fast path)
│
▼
git maintenance run / start
│
├── commit-graph (faster reachability / log)
├── prefetch (objects ready before real fetch)
├── loose-objects (loose → pack, carefully)
├── incremental-repack (multi-pack-index dance)
└── pack-refs, … (ref iteration speed)
git maintenance start registers the repo and installs a scheduler (systemd user timers on modern Linux, else cron; launchctl / schtasks on macOS / Windows). register alone updates config; start = register + schedule.
Tasks that matter at scale
| Task | What it does | Default under incremental strategy |
|---|---|---|
commit-graph |
Incremental commit-graph write + verify; safe beside concurrent Git | hourly |
prefetch |
git fetch into refs/prefetch/ (no tag update; does not move remote-tracking branches) |
hourly |
loose-objects |
Delete loose objects already packed; pack a batch of remaining loose objects | daily |
incremental-repack |
multi-pack-index expire then repack small packs into a larger one |
daily |
pack-refs |
Pack loose refs into a single file | weekly (when enabled by strategy) |
gc |
Full garbage collection / aggressive repack — expensive; not on the incremental schedule | manual / other strategies |
Prefetch makes the next interactive git fetch mostly a tip update when objects were already downloaded. Exclude a remote with remote.<name>.skipFetchAll if that remote should not be prefetched.
incremental-repack uses the multi-pack-index (MIDX): expire unreferenced packs, then repack a batch of small packs and retarget MIDX entries. Default batch size 0 means “try to fold into one pack.”
Worked examples
Case 1: One-shot tasks on a lab clone
cd /path/to/large-repo
# Inspect whether auto thresholds say work is due
git maintenance is-needed --auto; echo "exit=$?"
# Run specific safe tasks explicitly
git maintenance run --task=commit-graph
git maintenance run --task=loose-objects
git maintenance run --task=incremental-repack
# Optional: see commit-graph files appear
ls -la .git/objects/info/commit-graph 2>/dev/null || ls -la .git/objects/info/commit-graphs/ 2>/dev/null || trueUse --task when you want a known order. Without --task, Git runs tasks with maintenance.<task>.enabled=true (by default that is mainly gc unless you set a strategy).
Case 2: Enable incremental strategy and register
cd /path/to/large-repo
git maintenance register
# Sets maintenance.strategy=incremental if unset, adds repo to global maintenance.repo,
# enables recommended schedules, sets maintenance.auto=false in this repo
git config --get maintenance.strategy
git config --get-all --global maintenance.repo | tail -n 5
git config --get maintenance.autoExpected shape: incremental, a path entry under maintenance.repo, and maintenance.auto=false so foreground commands do not also kick auto-maintenance while the scheduler owns the job.
Case 3: git maintenance start on Linux
cd /path/to/large-repo
git maintenance start --scheduler=auto
# On Linux with user systemd: systemd-timer; otherwise crontab
systemctl --user list-timers 'git-maintenance*' 2>/dev/null || true
# Or: crontab -l | sed -n '/GIT MAINTENANCE/,/END GIT MAINTENANCE/p'Timers run git for-each-repo --config=maintenance.repo maintenance run --schedule=hourly|daily|weekly. Stop with git maintenance stop (leaves the repo registered). unregister removes the repo from the global list (--force if unsure).
Case 4: Prefetch vs a normal fetch
# After maintenance has run prefetch on a registered remote:
git show-ref | grep prefetch | head
git fetch origin
# Ideal case: tips move with little or no object transferPrefetch refs live under refs/prefetch/ so your origin/main does not jump until you fetch.
Case 5: Manual vs scheduled, and locks
# Manual full gc via maintenance (preferred over bare git gc when using the framework)
git maintenance run --task=gc
# Auto mode: only if thresholds say so (loose object count, pack count, …)
git maintenance run --autoEach git maintenance run takes an object database lock. Overlapping hourly runs on huge multi-repo schedules can skip a cycle — shrink task cost or stagger repos if windows exceed an hour. Do not mix bare git gc with concurrent git maintenance run; prefer git maintenance run --task=gc.
Strategy cheat sheet
maintenance.strategy |
Intent |
|---|---|
none |
Scheduled maintenance runs nothing by default |
gc |
Run the gc task |
geometric |
Geometric pack repack + auxiliary cleanup; strong default for manual maintenance on large repos (Git 2.50+ docs) |
incremental |
Small non-deleting tasks on a schedule; manual path still uses gc |
Tune with maintenance.<task>.enabled and maintenance.<task>.schedule (hourly / daily / weekly). Threshold knobs include maintenance.commit-graph.auto, maintenance.loose-objects.auto, maintenance.incremental-repack.auto.
Safety
- Prefetch contacts remotes on a timer — ensure credentials/agents work non-interactively or exclude remotes.
gcdeletes stale data; incremental tasks prefer not to. Know which strategy you enabled before leaving a laptop on a huge monorepo overnight.- Customizing systemd units: use drop-ins under
~/.config/systemd/user/git-maintenance@.service.d/;startoverwrites the main unit files. maintenance.auto=falseafterregisteris intentional; re-enable only if you understand double scheduling.
Hands-on checklist
- On a disposable clone, run
commit-graphandincremental-repackwith--task. git maintenance registerand confirmmaintenance.strategy/maintenance.repo.git maintenance start; list timers or crontab region.- Trigger prefetch; inspect
refs/prefetch/; run a normalgit fetchand note transfer size. git maintenance stopandunregisterwhen the lab is done.
Pitfalls
| Symptom | Likely cause | Fix |
|---|---|---|
| Hourly tasks sometimes skip | Overlap on object DB lock | Fewer repos per user, cheaper tasks, or longer spacing |
| Remote-tracking branches “randomly” move | Not prefetch — something else fetching | Prefetch should only touch refs/prefetch/ |
| Disk grows after incremental mode | No full gc; MIDX keeps more packs briefly |
Periodic manual gc / geometric strategy if policy allows |
| Scheduler missing on Linux | No user systemd / lingering | loginctl enable-linger $USER or use --scheduler=crontab |
unregister errors |
Repo not in list | git maintenance unregister --force |
Sources
- git-maintenance documentation
- git-multi-pack-index (expire / repack used by
incremental-repack)