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 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!