Rapid/efficient software development is often about understanding just enough of your underlying stack to keep working (and nothing more). In this vein, git is probably the most used, and least understood tool used by almost every software engineer on the planet. It's incredible how complicated git is, and how few of its users understand any of that complication.
Githooks Basics
As a pluggable system, git enables hooking into its myriad verbs such as commit and push to trigger workflows that should execute either before, ‘pre’ or after, ‘post’ the verb executes. The ‘pre-’ hooks have the added power of canceling the action before it occurs. If a pre-commit hook for example returns a non-zero exit code, the action will be canceled before it runs. Githooks unlock a tremendous opportunity for workflow optimization by ensuring certain tools (like a linter) are run on code before it is committed or pushed to the cloud.
Writing your own githook
It’s fairly trivial to write your own githook and add it to your repository. Every git repo (by default) has a hidden .git folder which has a suitably named folder hooks
. If you inspect inside the hooks folder you will see a list of sample hooks you can wire into. If you want to see some well-crafted bash, I’d recommend opening some of these files up.
Let’s quickly write our own git-hook:
echo “echo Hello World” > .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
Githooks in the real world
The Achilles heel of githooks is that they exist in a local .git
folder that is, by design, gitignored (not committed or pushed to the remote repository). To share your githooks with the other developer’s in your repo, you need something to manage these hooks and make sure that every user of your repository has the same experience. And now with trunk
that is extremely easy to do.
A quick introduction to Trunk Actions
You can read in detail about trunk actions here, but the TLDR; is that they are applets/scripts marked up in your trunk.yaml
file. They can be executed by name trunk run {action-name}
, but more importantly for this discussion — can be configured to be triggered as a githook.
Here is a an example of an action we ship in the trunk plugins repository:
Trunk Githook Management
The first element of a managed githook solution is ensuring that every user of that repo has the hooks properly wired up. Trunk will automatically begin to manage the githooks of your repository when you enable any action that triggers on a git event.
This all happens transparently to the users of the repository. The trunk daemon that is responsible for background linting now has the additional responsibility of maintaining the githooks for your trunk-enabled repositories.
Revisit ‘Hello World’
To return to our earlier example, to print hello world
before every git commit you can now define a simple action like this:
Now this example is trivial, but consider the benefits of having this hook defined in code, in a straightforward, readable location; no longer buried in a hidden folder, in a specially named file — with special execute permissions.
A Better Mousetrap
Trunk is definitely not the first application to try to help manage the installation, configuration, and execution of githooks. But we believe we’ve built a better mousetrap in our implementation. World-class developer experience can be boiled down to the ergonomics of a tool. How easy is it to author, maintain and debug problems when they come up? How intuitive is the design, so that users of the service can grok what is happening without diving into the weeds?
Modular by Design
The modular, bite-sized nature of trunk actions makes them a perfect building block for modular githook development. Teams can mix and match a collection of hooks and author each hook using the best-matched technologies.
While the sample hook-code git ships with are all written in bash, the lowest common denominator execution environment of most machines, trunk actions can be written in the language of your choosing (python, typescript, ruby, bash, etc..).
Each action is hermetically managed by trunk and can reference a specific runtime and set of libraries to install, making tool-authoring simple and robust. Additionally, trunk will manage the installation of necessary runtimes and external packages from their respective package managers (npm, pip, etc). This means you can write sophisticated programs and guarantee they will run on every box, the same way, without any fuss.
Isolated Execution
Trunk git hooks run in an isolated execution environment with a copy of the original stdin and arguments from the git event. In practice, this ensures that poorly written actions cannot accidentally stomp on the original content coming from git, and ensures more reliable results.
Optionally Interactive
Sometimes you only want to run a particular commit when an interactive terminal is available (for example, when running git commit
, but not when using a git gui). With trunk-triggered githooks, making this distinction is as simple as marking an action as interactive: true
Some Prebuilt Magic
We’ve baked into trunk some powerful git aware hooks to help tie together trunk check with our new githooks support. Out of the box, the public Trunk plugins repo ships with support for automatically running check
and fmt
on only the files you are committing/pushing.
trunk-check-pre-push
runstrunk check
on all the changed files in the current branch. helps dramatically reduce CI churn by ensuring a pull request is passing linting rules before being pushed to the cloud.trunk-fmt-pre-commit
runstrunk fmt
on all the changed files in the current branch. helps maintain the formatting of files during pull request development.
Sometimes you are in a rush to push and don’t want to wait for all the linting checks to complete locally before pushing to the cloud. We’ve all been there and we’ve got you covered. We built in support to skip checks when you don’t have time for them to run before committing/pushing.
Conclusion
Githooks management has been on our roadmap since our very first planning sessions. The goal of shifting linting/formatting left, reducing CI churn, and increasing developer productivity is our constant north star.
We’ve been running with trunk pre-commit/pre-push in our own repositories while dogfooding these features and it's been amazing how many pull requests we caught issues in before they went to CI.
It’s a little early for Christmas movie references, but I couldn’t resist. If you have any feedback on how our githooks feature works, or on trunk in general, please give us a holler.
We’d also love to have you contribute to our nascent plugins ecosystem and help share a little DevEx magic with the entire community.