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
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.
To get started, all you need to do is run
git maintenance start in the root of your project.
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
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:
All of the above will improve the performance of multiple commands such as
The second action is registering your project within the
[maintenance] section your global Git
[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 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.
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/alfred repo = /Users/bkuhlmann/Engineering/OSS/archiver repo = /Users/bkuhlmann/Engineering/OSS/auther repo = /Users/bkuhlmann/Engineering/OSS/bashsmith repo = /Users/bkuhlmann/Engineering/OSS/benchmarks repo = /Users/bkuhlmann/Engineering/OSS/bkuhlmann repo = /Users/bkuhlmann/Engineering/OSS/caliber repo = /Users/bkuhlmann/Engineering/OSS/docker-alpine-ruby repo = /Users/bkuhlmann/Engineering/OSS/dotfiles repo = /Users/bkuhlmann/Engineering/OSS/flacsmith repo = /Users/bkuhlmann/Engineering/OSS/form-validator repo = /Users/bkuhlmann/Engineering/OSS/gemsmith repo = /Users/bkuhlmann/Engineering/OSS/git-lint repo = /Users/bkuhlmann/Engineering/OSS/git_plus 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/prawn_plus 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/test repo = /Users/bkuhlmann/Engineering/OSS/tocer repo = /Users/bkuhlmann/Engineering/OSS/ubuntu 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
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.
When reading through the Git Maintenance documentation, there is a
Troubleshooting callout for not
git gc with
git maintenance because
git gc does not respect the object database lock
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.
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:
I hope this article has been of help and encourages further automation of your Git workflow. Enjoy!