Scheduling git maintenance for background repacking Jump to heading
Every commit, fetch and rebase writes loose objects. Gitβs historical answer was to notice the pile-up during an ordinary command and pack it right there β the familiar Auto packing the repository for optimum performance message that arrives, without fail, in the middle of the one command you needed to finish quickly. The modern answer is a scheduler that does the same work on its own time. This recipe applies the maintenance half of Large Repository Performance, and it is the cheapest improvement on that page: two commands, no workflow change, no coordination with anyone.
When to use this approach Jump to heading
- Developers occasionally report a Git command hanging for tens of seconds with an βauto packingβ message.
git count-objects -vHshows tens of thousands of loose objects on a working clone.git log,git blameor a review toolβs branch comparison feels slow relative to the repositoryβs size β the commit-graph is probably missing or stale.- Fetches are slower than the amount of new work justifies, and a background prefetch would absorb the cost.
- The repository is otherwise healthy: if the problem is genuinely a multi-gigabyte history, pair this with partial clone rather than expecting maintenance alone to fix it.
Step 1 β Confirm the symptom before changing anything Jump to heading
# Loose objects are the ones that have not been packed yet
git count-objects -vH
# Watch for: count: 84213 size: 612.40 MiB (a large "count" is the tell)
# Is a commit-graph present, and how old is it?
ls -l .git/objects/info/commit-graph* 2>/dev/null || echo "no commit-graph"
# Baseline the operations you expect to improve
time git log --oneline -5000 > /dev/null
time git status What changed: nothing β but you now have a loose-object count and two timings to compare against.
Step 2 β Register the repository with the scheduler Jump to heading
# Register this repository and install a platform scheduler entry
git maintenance start What changed: Git added the repository to a global list and created a scheduler entry β a systemd timer on Linux, a launchd agent on macOS, a scheduled task on Windows β that runs git maintenance run on an hourly, daily and weekly cadence.
# Which repositories are registered?
git config --global --get-all maintenance.repo
# What is the schedule?
systemctl --user list-timers 'git-maintenance*' # Linux
# launchctl list | grep git-maintenance # macOS Step 3 β Disable the blocking automatic collector Jump to heading
Registering the scheduler does not switch the old behaviour off. Both will run until you say otherwise, which means you keep the interruption you were trying to remove.
# Stop the in-command collector from firing
git config maintenance.auto false
# Belt and braces on older tooling that still consults gc.auto
git config gc.auto 0 What changed: packing now happens only on the schedule, never as a surprise inside git commit or git checkout.
git config maintenance.auto # expect: false
git config gc.auto # expect: 0 SAFETY WARNING β with automatic collection disabled, nothing reclaims space on a machine where the scheduler never runs: a container image, a CI runner, or a server where the user session that owns the timer does not persist. On those hosts either leave
maintenance.autoalone or add an explicitgit maintenance run --task=gcto your provisioning. A repository that grows unbounded because both mechanisms are off is a worse outcome than the interruption.
Step 4 β Tune which tasks run and how often Jump to heading
# Turn an individual task on or off
git config maintenance.prefetch.enabled true
git config maintenance.commit-graph.enabled true
git config maintenance.pack-refs.enabled false # not worth it below ~10 000 refs
# Run one task immediately, without waiting for the schedule
git maintenance run --task=commit-graph # Confirm the graph was written and history queries improved
ls -l .git/objects/info/commit-graph*
time git log --oneline -5000 > /dev/null # compare against the Step 1 baseline The prefetch task deserves a note because its behaviour surprises people who expect it to update their branches. It downloads new objects into a hidden namespace and deliberately leaves remote-tracking refs alone, so nothing moves under your feet β but the next git fetch you run is nearly instant because the data is already on disk.
Step 5 β Verify the schedule is actually firing Jump to heading
# 1. Registered
git config --global --get-all maintenance.repo | grep "$(pwd)"
# 2. Scheduled, and has actually run
systemctl --user list-timers 'git-maintenance*'
# LAST must be populated, not "n/a"
# 3. Producing the intended effect, checked a day later
git count-objects -vH # "count:" should be far lower than the Step 1 figure If the timer exists but has never fired, the usual cause on Linux is that the user session does not linger after logout. loginctl enable-linger "$USER" fixes it, and is worth checking before concluding that maintenance does not work.
Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
Is git maintenance a replacement for git gc? Jump to heading
For everyday upkeep, yes β it runs the same underlying work as scheduled background tasks instead of as an interruption in the middle of someoneβs command. It is deliberately more conservative: incremental repack rather than a full repack, and no aggressive pruning of unreachable objects. When you genuinely need to reclaim space after deleting large objects, run git gc --prune deliberately; do not wait for maintenance to do it.
Does the prefetch task change what I see in git branch? Jump to heading
No, and that is the point. Prefetch downloads new objects into a separate hidden ref namespace rather than updating your remote-tracking branches, so nothing appears to change until you run git fetch yourself. The next fetch is then nearly instant because the objects are already local β you get the speed without the surprise of refs moving under you.
Will this run on a laptop that is asleep? Jump to heading
The task simply does not fire while the machine is asleep and catches up on the next scheduled window. Because the tasks are incremental, a few missed runs are harmless β the loose-object count grows a little and is packed at the next opportunity. This is exactly why the incremental design matters more than the frequency.
Related Jump to heading
- Large Repository Performance β the parent guide, including how to tell a maintenance problem from a size problem.
- Speeding Up Clones with Partial Clone β pairs naturally with maintenance: a smaller clone that also stays well packed.
- Migrating Large Binaries to Git LFS β for the case where no amount of packing helps because the objects genuinely do not belong.