The letter A styled as Alchemists logo. lchemists
Published March 20, 2021 Updated August 21, 2023
Cover
Git Maintenance

Prior to the release of Git 2.31.0, we had to maintain the health of our Git repositories manually. Thankfully, this new version gives us access to more automation, specifically Git Maintenance eliminates the following situation from interrupting your workflow:

Auto packing the repository for optimum performance. You may also
run "git gc" manually. See "git help gc" for more information.

The above happens because Git defaults to writing loose objects to improve write performance. On the flip-side, it’s faster for Git to read from a packfile so Git optimizes for writing while pausing occasionally in the foreground with git gc --auto to catch up and pack previously written objects.

With Git Maintenance, we get the best of the above through automated background maintenance so let’s spend some time together digging deeper to leverage this new power in our own projects.

Getting Started

To get started, all you need to do is run git maintenance start in the root of your project. Example:

cd dotfiles
git maintenance start

From this point forward Git will maintain the health of your project for you. 🎉

A Closer Look

While it is satisfying to get started with little effort, running git maintenance start performs several actions on your behalf that are worth exploring. The first is adding the following settings to your project’s local Git configuration:

[maintenance]
  auto = false
  strategy = incremental

Setting auto to false is what makes our new background maintenance possible and is critical to preventing the message shown at the start of this article due to foreground maintenance normally being turned on by default.

The incremental strategy is shorthand for the following:

  • gc: disabled.

  • commit-graph: hourly.

  • prefetch: hourly.

  • loose-objects: daily.

  • incremental-repack: daily.

All of the above will improve the performance of multiple commands such as pull, push, log, etc.

The second action is registering your project within the [maintenance] section your global Git configuration (i.e. $HOME/.gitconfig). Example:

[maintenance]
  repo = /Users/bkuhlmann/Engineering/OSS/dotfiles

â„šī¸ I’ll talk more about how to make the global configuration serve you better shortly.

The third and last action is configuring your macOS launch agents so all of your maintenance tasks are run in the background. You can get a list of these configurations using Exa:

x $HOME/Library/LaunchAgents/org.git-scm.git*

💡 x is an alias to exa in my environment. For more on this, check out my screencast for more information.

The above will yield the following:

Permissions Size User      Group Date Modified    Name
.rw-r--r--  1.4k bkuhlmann staff 2021-03-18 20:42 /Users/bkuhlmann/Library/LaunchAgents/org.git-scm.git.daily.plist
.rw-r--r--  2.7k bkuhlmann staff 2021-03-18 20:42 /Users/bkuhlmann/Library/LaunchAgents/org.git-scm.git.hourly.plist
.rw-r--r--   752 bkuhlmann staff 2021-03-18 20:42 /Users/bkuhlmann/Library/LaunchAgents/org.git-scm.git.weekly.plist

I encourage you to inspect each of these files but let’s focus on the weekly configuration:

<?xml version="1.0"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
    <string>org.git-scm.git.weekly</string>
    <key>ProgramArguments</key>
    <array>
      <string>/opt/homebrew/Cellar/git/2.31.0/libexec/git-core/git</string>
      <string>--exec-path=/opt/homebrew/Cellar/git/2.31.0/libexec/git-core</string>
      <string>for-each-repo</string>
      <string>--config=maintenance.repo</string>
      <string>maintenance</string>
      <string>run</string>
      <string>--schedule=weekly</string>
    </array>
    <key>StartCalendarInterval</key>
    <array>
      <dict>
        <key>Day</key><integer>0</integer>
        <key>Hour</key><integer>0</integer>
        <key>Minute</key><integer>0</integer>
      </dict>
    </array>
  </dict>
</plist>

Notice the absolute path to Git: /opt/homebrew/Cellar/git/2.31.0. This path ensures you are using the same version of Git when maintenance was first started on your project. This also means, you’ll need to run git maintenance start on at least one of your projects when upgrading Git in the future since the documentation states that these files are overwritten each time start is run.

Further Automation

While we’ve been discussing the maintenance of a single project, I’d recommend you forgo running git maintenance start in each of your projects. Instead, define these settings once via your global Git configuration which I hinted at earlier. Here’s what mine, roughly, looks like:

[maintenance]
  auto = false
  strategy = incremental
  repo = /Users/bkuhlmann/Engineering/OSS/auther
  repo = /Users/bkuhlmann/Engineering/OSS/bashsmith
  repo = /Users/bkuhlmann/Engineering/OSS/benchmarks
  repo = /Users/bkuhlmann/Engineering/OSS/caliber
  repo = /Users/bkuhlmann/Engineering/OSS/dotfiles
  repo = /Users/bkuhlmann/Engineering/OSS/gemsmith
  repo = /Users/bkuhlmann/Engineering/OSS/git-lint
  repo = /Users/bkuhlmann/Engineering/OSS/mac_os
  repo = /Users/bkuhlmann/Engineering/OSS/mac_os-config
  repo = /Users/bkuhlmann/Engineering/OSS/milestoner
  repo = /Users/bkuhlmann/Engineering/OSS/navigator
  repo = /Users/bkuhlmann/Engineering/OSS/pennyworth
  repo = /Users/bkuhlmann/Engineering/OSS/pragmater
  repo = /Users/bkuhlmann/Engineering/OSS/refinements
  repo = /Users/bkuhlmann/Engineering/OSS/rubysmith
  repo = /Users/bkuhlmann/Engineering/OSS/runcom
  repo = /Users/bkuhlmann/Engineering/OSS/sublime_text_kit
  repo = /Users/bkuhlmann/Engineering/OSS/sublime_text_setup
  repo = /Users/bkuhlmann/Engineering/OSS/tocer
  repo = /Users/bkuhlmann/Engineering/OSS/versionaire
  repo = /Users/bkuhlmann/Engineering/OSS/xdg

Notice I’ve enabled background maintenance for all of my projects by default. To generate the list of repositories to monitor, I popped into my OSS directory and ran the following to give me a list of directories:

ls -d1 "$PWD"/*

Additionally, I updated my gi alias which has been shorthand for git init to look like this now:

alias gi="git init && git config --global --add maintenance.repo $PWD"

Now, when I initialize a new Git repository, it’ll automatically be added to my global configuration for scheduled background maintenance. 🎉

One drawback to this approach is there is no automated way to unregister a repository should you delete or move a repository. Definitely, unfortunate, because you might need to double check your configuration from time to time.

Caveats

When reading through the Git Maintenance documentation, there is a Troubleshooting callout for not using git gc with git maintenance because git gc does not respect the object database lock used by git maintenance. Upon first reading my earlier Machine Upkeep article you might recall I used this code:

git fsck && git repack -Ad && gc && git rerere gc

I have since corrected the above code — and article — to be aware of Git Maintenance as follows:

git fsck && git repack -Ad && git maintenance run --task=gc && git rerere gc

Depending on your setup, you might want to make similar corrections.

Conclusion

As readers of this site are most likely aware, I’m a big fan of enabling as much automation possible so here are few additional links in case they are interest:

  • Git Configuration - See my Dotfiles project, global Git configuration, and related aliases/functions.

  • Git Rebase - See my talk and related article for more info.

  • Git Lint - See my talk and associated project/tool.

I hope this article has been of help and encourages further automation of your Git workflow. Enjoy!