This is Part 4 of a series of posts on my git hooks. Here, I describe my sharing and update strategies.
Remember, my git hook project is available on GitHub
Sharing Git Hooks Between Projects
Git makes it pretty easy to give a predetermined set of githooks to a new project, but not to keep them up-to-date across multiple projects.
init.templatedir - Close, but No Cigar
As you know, git provides a
.git/hooks/ directory in every repo, which is where git hooks should live.
Git also looks to see if a git config variable
init.templatedir has been defined. If so, it will copy the specified directory of git hooks into any new repo’s
.git/hooks/ directory automatically.
That’s awfully handy, but it only makes a static copy of the individual hook files into the repo. It doesn’t support symlinks, or any other mechanism that would make it easy to update projects. And that’s a problem for me.
Wait, Update HOW Many?
Between full-time employment projects, contract work, and fun side projects, I’m touching about ten repos a week, spread between at least four machines. I definitely can’t manually update the git hooks in all of those repos.
That’s why I use symlinks to keep all of those git hooks up to date. I…
- Create a local copy of my git hooks repo.
- Whenever I set up / clone a new repo, I run the git hooks’ setup.sh script, passing the path to the new repo as an argument.
- That setup script creates symlinks to my “central” local copy inside the new repo’s
From now on, whenever I update the “central” local copy, all of the repos on that machine immediately see the changes, because of those symlinks.
Forcing the Share - A Use for init.templatedir
A script to symlink the git hooks is nice, but it begs the question, “How do I remember to run the script on a new repo in the first place?”
Well, I didn’t for a long time. I’d check in a stupid mistake, wonder why my hooks didn’t prevent it, and realize that I’d never installed the hooks into that particular repo in the first place! How could I make myself do it?
That’s when I realized that there is a use for the static copies created by the
init.templatedir, after all. I use it to install an auto-failing git hook that reports back one message;
Install your good git hook, dummy! Here, I’ll show you how, just cut-and-paste this , complete with paths to directories…”
It’s worded a little more nicely than that, but that’s the point.
What’s the Mechanism?
So now, bear with me, it’s complex. Maybe I’ll draw a sequence diagram someday.
- My basic “machine setup repo and script”1 (let’s call that Repo S, for “setup”) installs the directory of “messaging” “auto-fail” git hooks (we’ll call those Hook S, again, for “setup”) and sets up
- Because of that, any git repo set up in the future will have Hook S copied into it by default.
- The first time that the user tries to commit to a new repo (let’s call that Repo N, for “new,”), that “auto-fail” Hook S will run. It prompts the user to make a local clone of my “real” git hook repo (let’s call that Repo H, for “hooks”), and to run its setup script to install symlinks into Repo N2.
- Repo H’s setup script sets up its symbolic links inside of Repo N, as described previously. That also removes Repo N’s copy of Hook S, replacing it with symlinks to Repo S’s hooks. So now, Repo N is completely set up, with symlinks to good git hooks. Huzzah!
- … but surprise! There’s more! That script also sets another git config variable (one of my own devising),
hooks.symlinksourcerepo, to point Repo H.
- From now on, whenever Hook S fails a commit in another new repo, it will see that
hooks.symlinksourcerepois set. Hook S uses that to infer that the user has already cloned Repo H, and where it was cloned to. That allows it to give incredibly detailed commands telling exactly how to run Repo H’s setup script on the brand new repo, without suggesting that clones be made, etc.
That’s it on the needlessly expository git hooks saga. Part of me wonders if that was needlessly complicated. But I think that this will all serve me well as I create new repos on new machines, and, well, that’s the point!
Updates in the blog as events warrant. For the entire story, go to my archive of git posts.
The setup script is basically idempotent, so I don’t mind running it over and over again to update the git hooks or config variables. ↩
I like keeping my machine setup repo (mainly for my use, and as an object lesson for others) distinct from my git hooks repo (which people are already reusing). I still get the heebie-jeebies when I try to make one git repo install another, or introduce any other kind of “hard” dependency between them. So I have my setup repo (ultimately) “suggest” that you install the git hooks repo. If anyone thinks that I’m splitting hairs, making a silly distinction, or just being lame, please call me on it. I’d love the feedback. ↩