Git Maintenance for Long-Lived Repositories on Cloud Macs

Git Maintenance for Long-Lived Repositories on Cloud Macs

After a cloud Mac has handled builds continuously for several weeks, git fetch may still be fast while branch switching, merge-base calculation, and object traversal gradually slow down. The usual cause is not the network, but the accumulation of loose objects, fragmented pack files, stale remote references, and maintenance locks left behind by interrupted operations in a long-lived repository. Deleting .git and cloning again is simple, but it discards the existing object cache and merely postpones the problem until the next cycle.

A more reliable approach is to treat repository maintenance as a separate operation: measure first, run incremental tasks under an exclusive lock, and then verify object connectivity. The following workflow is suitable for both fixed workspaces and build nodes that use multiple worktrees.

Establish a comparable repository baseline first

Do not start by looking at the total directory size. The number, organization, and reachability of Git objects provide more useful information. Enter the repository and record the following results:

git count-objects -vH
git rev-parse --git-dir
git rev-parse --git-common-dir
git for-each-ref --format='%(refname)' refs/remotes | wc -l
find "$(git rev-parse --git-common-dir)/objects/pack" -name '*.pack' | wc -l

A high count indicates many loose objects, while a steadily increasing packs value suggests that previous jobs have been generating small pack files. When using worktrees, maintenance must target the shared directory returned by git rev-parse --git-common-dir; checking only the .git pointer file in the current worktree is insufficient.

Record count, in-pack, packs, and size-pack before and after each maintenance run in a plain-text log. Do not judge success solely by reduced disk usage: updating the commit graph may free almost no space while still improving history traversal and merge-base queries.

Choose incremental tasks instead of aggressive cleanup

Git maintenance tasks can be run separately. On long-lived build nodes, start with the commit graph, loose-object cleanup, and incremental repacking:

git config maintenance.strategy incremental
git maintenance run \
  --task=commit-graph \
  --task=loose-objects \
  --task=incremental-repack

The commit graph speeds up history reachability queries. The multi-pack index prevents Git from scanning every pack individually, while incremental repacking consolidates objects gradually to avoid the time and temporary disk-space costs of a full repack.

Do not use git gc --prune=now as a routine scheduled task. Immediate pruning removes the recovery grace period and may conflict with concurrent processes that still hold references to old objects.

Even after a large-scale branch deletion, keep the default grace period initially. Consider deeper reclamation only after confirming that no build, checkout, rebase, submodule update, or archive task is running.

Manage remote references separately

Remote-tracking branches that are never cleaned up expand the traversal scope. Preview the changes before pruning:

git remote prune origin --dry-run
git remote prune origin

This affects only remote-tracking references. It does not replace object reclamation or delete local development branches. If a build script depends on a reference that has already been removed from the remote, fix the script rather than retaining the stale reference indefinitely.

Isolate builds and maintenance with an atomic lock

Maintenance and build jobs must share the same repository-level lock. Because mkdir is atomic within the same file system, it works well as a simple locking primitive:

common_dir="$(git rev-parse --git-common-dir)"
lock_dir="$common_dir/maintenance.lock"

if ! mkdir "$lock_dir" 2>/dev/null; then
  printf '%s\n' "maintenance skipped: repository is busy"
  exit 0
fi

trap 'rmdir "$lock_dir"' EXIT INT TERM

git maintenance run \
  --task=commit-graph \
  --task=loose-objects \
  --task=incremental-repack

The build entry point must also check or acquire the same lock; locking only the maintenance script provides no protection. Multiple worktrees must not create separate locks in their working directories because they share the same object store.

You can write the process ID and start time into the lock directory for manual troubleshooting, but do not delete the lock merely because its timestamp is old. First use ps to confirm whether the process still exists, then check whether git pack-objects, git index-pack, or git maintenance is running.

Verify indexes and object connectivity

A zero exit status from a maintenance command only means that the command completed. It does not guarantee that the repository is ready to resume builds. Run at least these three types of verification:

git commit-graph verify
git multi-pack-index verify
git fsck --connectivity-only

The checks can be incorporated into the pipeline as follows:

| Check | Purpose | Action after failure | |---|---|---| | `commit-graph verify` | Validate commit-graph layers and references | Rewrite the commit graph and verify it again | | `multi-pack-index verify` | Validate multi-pack index targets | Rebuild the index; do not delete packs manually | | `fsck --connectivity-only` | Check that all reachable objects are present | Stop reusing the repository and retrieve missing objects from a trusted remote | | `count-objects -vH` | Compare repository structure before and after maintenance | Track the trend without setting arbitrary thresholds |

If verification fails, do not begin with more aggressive reclamation. Preserve the evidence, including command output, the Git version, the shared object-directory path, and the commit ID of the most recent successful build. Missing objects can be fetched again from a trusted remote. If recovery still fails, isolate the old directory and create a fresh clone instead of repeatedly attempting deletions in the original directory.

Integrate maintenance into the node lifecycle

Maintenance frequency should be based on the repository’s rate of change rather than a fixed interval shared by every project. A frequently updated monorepo can run incremental maintenance each day after the final job finishes. For less active projects, trigger it according to trends in loose-object and pack counts. Also verify available disk space before starting, because old and new packs briefly coexist during repacking.

On VMOak cloud Macs, place the maintenance script in a read-only operations directory outside the repository, and have the scheduler pass in the target path. The script must not load a same-named file from a project branch, which would allow an ordinary code change to rewrite the maintenance logic. If a node hosts multiple repositories, lock and verify each repository separately. A failure in one repository must not trigger cleanup in the others.

Final acceptance should assess more than whether disk usage has decreased. Confirm that routine operations have not regressed by performing a remote update, checking out the target commit, running a merge-base query, and preparing a read-only build, then compare the logs from before and after maintenance. This turns Git maintenance into an auditable node operation rather than an improvised deletion command run only when disk pressure appears.

Frequently asked questions

Can Git maintenance run while a build is active?

Avoid running cleanup beside checkout, rebase, or packaging jobs. Use an atomic repository lock and schedule maintenance only when the workspace is idle.

Why not schedule git gc --prune=now?

Immediate pruning removes the recovery window and increases the risk of deleting objects still referenced by concurrent processes. Incremental repacking is safer for persistent runners.

What should be verified after maintenance?

Verify the commit-graph, multi-pack index, and object connectivity, then record pack counts, loose-object counts, and disk usage so the result remains auditable.

Dedicated physical node

Choose a cloud Mac for builds, automation, or remote creative work

Compare the M4 16GB and M4 Pro 64GB configurations, then confirm the rental term and available node before placing your order.

Choose a cloud Mac configuration