Awesome Go

githooks

CategoryVersion Control
SubcategoryVersion Control
Stars132

Per-repo and shared Git hooks with version control and auto update

About githooks

This large README is shown as a plain-text preview. Read the full README at the source

<p align="center">
<img src="docs/githooks-logo.svg">
<strong>v3</strong>
</p>

# Githooks

[![CircleCI](https://circleci.com/gh/gabyx/Githooks.svg?style=svg)](https://circleci.com/gh/gabyx/Githooks)
[![Coverage Status](https://coveralls.io/repos/github/gabyx/Githooks/badge.svg?branch=main)](https://coveralls.io/github/gabyx/Githooks?branch=main)
[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go)
[![goreleaser](https://github.com/gabyx/Githooks/actions/workflows/release.yml/badge.svg?branch=prepare-v2.0.3)](https://github.com/gabyx/Githooks/actions/workflows/release.yml)
[![Go Report Card](https://goreportcard.com/badge/github.com/gabyx/githooks)](https://goreportcard.com/report/github.com/gabyx/githooks)
[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/nlohmann/json/master/LICENSE.MIT)
[![GitHub Releases](https://img.shields.io/github/release/gabyx/githooks.svg)](https://github.com/gabyx/githooks/releases)
![Git Version](https://img.shields.io/badge/Git-%E2%89%A5v2.28.0,%20latest%20tests%20v2.43.0-blue)
![Go Version](https://img.shields.io/badge/Go-1.26-blue)
![OS](https://img.shields.io/badge/OS-linux,%20macOs,%20Windows-blue)

A **platform-independent hooks manager** written in Go to support shared hook
repositories and per-repository [Git hooks](https://git-scm.com/docs/githooks),
checked into the working repository. This implementation is the Go port and
successor of the [original implementation](https://github.com/rycus86/githooks)
(see [Migration](#migrating)).

> [!IMPORTANT]
>
> This Git hook manager does **one job and only that**: running Git hooks. It
> will **never** maintain your tools or support language toolchains and all
> other sidestories you might have to make your hooks execute. There are
> solutions to this like [Nix ā¤ļøā€šŸ”„](https://nixos.org/) or container managers like
> `podman` (or `docker` -> don't).

To make this work, the installer creates run-wrappers for Githooks that are
installed into the `.git/hooks` folders on request (by default). There's more
[to the story though](#templates-or-global-hooks). When one of the Githooks
run-wrappers executes, Githooks starts up and tries to find matching hooks in
the `.githooks` directory under the project root, and invoke them one-by-one.
Also it searches for hooks in configured shared hook repositories.

**This Git hook manager supports:**

- Running repository checked-in hooks.
- Running shared hooks from other Git repositories (with auto-update). See these
  [containerized example hook repositories](#example-githooks-repositories).
- Git LFS support.
- **No** _it works on my machine_ by
  [running hooks over containers](#running-hooks-in-containers) and
  [automatic build/pull integration of container images](#pull-and-build-integration)
  (optional) _Hint: better run it over Nix DevShells and not containers_.
- Command line interface.
- Fast execution due to compiled executable. (even **2-3x faster from
  `v2.1.1`**)
- Fast parallel execution over threadpool.
- Ignoring non-shared and shared hooks with patterns.
- Automatic Githooks updates: Fully configurable for your own company by
  url/branch and deploy settings.
- **Bonus:** [Platform-independent dialog tool](#dialog-tool) for user prompts
  inside your own hooks.

## Table of Content

<!--toc:start-->

- [Githooks](#githooks)
  - [Table of Content](#table-of-content)
  - [Layout and Options](#layout-and-options)
  - [Execution](#execution)
    - [Staged Files](#staged-files)
    - [Hook Run Configuration](#hook-run-configuration)
    - [Parallel Execution](#parallel-execution)
  - [Supported Hooks](#supported-hooks)
  - [Git Large File Storage (Git LFS) Support](#git-large-file-storage-git-lfs-support)
  - [Shared Hook Repositories](#shared-hook-repositories)
    - [Global Configuration](#global-configuration)
    - [Local Configuration](#local-configuration)
    - [Example Githooks Repositories](#example-githooks-repositories)
    - [Repository Configuration](#repository-configuration)
    - [Supported URLS](#supported-urls)
    - [Skip Non-Existing Shared Hooks](#skip-non-existing-shared-hooks)
  - [Layout of Shared Hook Repositories](#layout-of-shared-hook-repositories)
    - [Shared Repository Namespace](#shared-repository-namespace)
  - [Ignoring Hooks and Files](#ignoring-hooks-and-files)
  - [Trusting Hooks](#trusting-hooks)
  - [Disabling Githooks](#disabling-githooks)
  - [Environment Variables](#environment-variables)
    - [Arguments to Shared Hooks](#arguments-to-shared-hooks)
  - [Log & Traces](#log-traces)
  - [Installing or Removing Run-Wrappers](#installing-or-removing-run-wrappers)
  - [Running Hooks in Containers](#running-hooks-in-containers)
    - [Podman Manager (rootless)](#podman-manager-rootless)
    - [Docker Manager](#docker-manager)
    - [Pull and Build Integration](#pull-and-build-integration)
    - [Locate Githooks Container Images](#locate-githooks-container-images)
  - [Running Hooks/Scripts Manually](#running-hooksscripts-manually)
  - [User Prompts](#user-prompts)
  - [Installation](#installation)
    - [Quick (Secure)](#quick-secure)
    - [Package Manager `nix`](#package-manager-nix)
    - [Procedure](#procedure)
    - [Install Modes](#install-modes)
    - [Install Mode - Manual](#install-mode-manual)
    - [Install Mode - Centralized Hooks](#install-mode-centralized-hooks)
    - [Install from different URL and Branch](#install-from-different-url-and-branch)
    - [Use in CI](#use-in-ci)
      - [Nested Containers](#nested-containers)
    - [Gitlab Demo](#gitlab-demo)
    - [No Installation](#no-installation)
    - [Non-Interactive Installation](#non-interactive-installation)
    - [Install on the Server](#install-on-the-server)
      - [Setup for Bare Repositories](#setup-for-bare-repositories)
    - [Global Hooks or No Global Hooks](#global-hooks-or-no-global-hooks)
      - [Manual: Use Githooks Selectively](#manual-use-githooks-selectively)
      - [Centralized: Use Githooks For All Repositories](#centralized-use-githooks-for-all-repositories)
    - [Updates](#updates)
      - [Automatic Update Checks](#automatic-update-checks)
      - [Update Mechanics](#update-mechanics)
  - [Uninstalling](#uninstalling)
  - [YAML Specifications](#yaml-specifications)
  - [Migration](#migration)
  - [Dialog Tool](#dialog-tool)
    - [Build From Source](#build-from-source)
    - [Dependencies](#dependencies)
  - [Tests and Debugging](#tests-and-debugging)
    - [Debugging in the Dev Container](#debugging-in-the-dev-container)
    - [Todos](#todos)
  - [Changelog](#changelog)
    - [Version v2.x.x](#version-v2xx)
  - [FAQ](#faq)
  - [Acknowledgements](#acknowledgements)
  - [Authors](#authors)
  - [Support & Donation](#support-donation)
  - [License](#license)
  <!--toc:end-->

## Layout and Options

Take this snippet of a Git repository layout as an example:

```bash
/
ā”œā”€ā”€ .githooks/
│    ā”œā”€ā”€ commit-msg/          # All commit-msg hooks.
│    │    ā”œā”€ā”€ validate        # Normal hook script.
│    │    └── add-text        # Normal hook script.
│    │
│    ā”œā”€ā”€ pre-commit/          # All pre-commit hooks.
│    │    ā”œā”€ā”€ .ignore.yaml    # Ignores relative to 'pre-commit' folder.
│    │    ā”œā”€ā”€ 01-validate     # Normal hook script.
│    │    ā”œā”€ā”€ 02-lint         # Normal hook script.
│    │    ā”œā”€ā”€ 03-test.yaml    # Hook run configuration.
│    │    ā”œā”€ā”€ docs.md         # Ignored in '.ignore.yaml'.
│    │    └── final/          # Batch folder 'final' which runs all in parallel.
│    │        ā”œā”€ā”€ 01-validate # Normal hook script.
│    │        └── 02-upload   # Normal hook script.
│    │
│    ā”œā”€ā”€ post-merge           # An executable file.
│    │
│    ā”œā”€ā”€ post-checkout/       # All post-checkout hooks.
│    │   ā”œā”€ā”€ .all-parallel    # All hooks in this folder run in parallel.
│    │   └── ...
│    ā”œā”€ā”€ ...
│    ā”œā”€ā”€ .images.yaml         # Container image spec for use in e.g `03-test.yaml`.
│    ā”œā”€ā”€ .ignore.yaml         # Main ignores.
│    ā”œā”€ā”€ .shared.yaml         # Shared hook configuration.
│    ā”œā”€ā”€ .envs.yaml           # Environment variables passed to shared hooks.
│    └── .lfs-required        # LFS is required.
└── ...
```

All hooks to be executed live under the `.githooks` top-level folder, that
should be checked into the repository. Inside, we can have directories with the
name of the hook (like `commit-msg` and `pre-commit` above), or a file matching
the hook name (like `post-merge` in the example). The filenames in the directory
do not matter, but the ones starting with a `.` (dotfiles) will be excluded by
default. All others are executed in lexical order according to the Go function
[`Walk`](https://golang.org/pkg/path/filepath/#Walk) rules. Subfolders as e.g.
`final` get treated as parallel batch and all hooks inside are by default
executed in parallel over the thread pool. See
[Parallel Execution](#parallel-execution) for details.

You can use the [command line helper](docs/cli/git_hooks.md) (a globally
configured Git alias `alias.hooks`), that is `git hooks list`, to list all hooks
and their current state that apply to the current repository. For this
repository this [looks like the following.](docs/githooks-list.png)

## Execution

If a file is executable, it is directly invoked, otherwise it is interpreted
with the `sh` shell. On Windows that mostly means dispatching to the `bash.exe`
from [https://gitforwindows.org](https://gitforwindows.org).

**All parameters and standard input** are forwarded from Git to the hooks. The
standard output and standard error of any hook which Githooks runs is captured
**together**<span id="a1">[<sup>1</sup>](#1)</span> and printed to the standard
error stream which might or might not get read by Git itself (e.g. `pre-push`).

Hooks can also be specified by a run configuration in a corresponding YAML file,
see [Hook Run Configuration](#hook-run-configuration).

### Staged Files

Hooks related to `commit` events (where it makes sense, not `post-commit`) will
also have a `${STAGED_FILES}` or `${STAGED_FILES_FILE}` environment variable
set. By default, `STAGED_FILES` contains the list of staged and changed files
according to `git diff --cached --diff-filter=ACMR --name-only`. File paths are
separated by a newline `\n`. If you want to iterate in a shell script over them,
and expect spaces in paths, you might want to set the `IFS` like this:

```shell
IFS="
"
for file in ${STAGED_FILES}; do
    echo "$file"
done
```

The `ACMR` filter in the `git diff` will include staged files that are added,
copied, modified or renamed.

To enable the `STAGED_FILES_FILE` variable which contains the path to the file
containing the paths to all staged files (separated by null-chars `\0`, e.g.
`<path>\0<path>\0`) use

```shell
git config githooks.exportStagedFilesAsFile true
```

and read this file in `bash` with something like:

```shell
#!/bin/bash
while read -rd $'\\0' file; do
    echo "$file"
done < "$STAGED_FILES_FILE"
```

**<span id="1"><sup>1</sup></span>[āŽ](#a1) Note:** This caveat is basically
there because standard output and error might get interleaved badly and so far
no solution to this small problem has been tackled yet. It is far better to
output both streams in the correct order, and therefore send it to the error
stream because that will not conflict in anyway with Git (see
[fsmonitor-watchman](https://git-scm.com/docs/githooks#_fsmonitor_watchman),
unsupported right now.). If that poses a real problem for you, open an issue.

### Hook Run Configuration

Each supported hook can also be specified by a configuration file
`<hookName>.yaml` where `<hookName>` is any
[supported hook name](#supported-hooks). An example might look like the
following:

```yaml
# The command to run.
# - if it contains path separators and is relative, it its evaluated relative to
#   the worktree of the repository where this config resides.
cmd: "dist/command-of-${env:USER}.exe"

# The arguments given to `cmd`.
args:
  - "-s"
  - "--all"
  - "${env:GPG_PUBLIC_KEY}"
  - "--test ${git-l:my-local-git-config-var}"

# If you want to make sure your file is not
# treated always as the newest version. Fix the version by:
version: 1
```

All additional arguments given by Git to `<hookName>` will be appended last onto
`args`. All environment and Git config variables in `args` and `cmd` are
substituted with the following syntax:

- `${env:VAR}` : An environment variable `VAR`.
- `${git:VAR}` : A Git config variable `VAR` which corresponds to
  `git config 'VAR'`.
- `${git-l:VAR}` : A Git config variable `VAR` which corresponds to
  `git config --local 'VAR'`.
- `${git-g:VAR}` : A Git config variable `VAR` which corresponds to
  `git config --global 'VAR'`.
- `${git-s:VAR}` : A Git config variable `VAR` which corresponds to
  `git config --system 'VAR'`.

Not existing environment variables or Git config variables are replaced with the
empty string by default. If you use `${!...:VAR}` (e.g `${!git-s:VAR }`) it will
trigger an error and fail the hook if the variable `VAR` is not found. Escaping
the above syntax works with `\${...}`.

**Sidenote**: You might wonder why this configuration is not gathered in one
single YAML file for all hooks. The reason is that each hook invocation by Git
is separate. Avoiding reading this total file several times needs time and since
we want speed and only an opt-in solution this is avoided.

Githooks defines the
[environment variables in this table](#environment-variables) on hooks
invocation.

### Parallel Execution

As in the [example](#layout-and-options), all discovered hooks in subfolders
`<batchName>`, e.g. `<repoPath>/<hooksDir>/<hookName>/<batchName>/*` where
`<hooksDir>` is either

- `.githooks` for repository checked-in hooks or
- `githooks`, `.githooks` or `.` for shared repository hooks,

are assigned the same batch name `<batchName>` and processed in parallel. Each
batch is a synchronisation point and starts after the one before has finished.
The threadpool uses by default as many threads as cores on the system. The
number of threads can be controlled by the Git configuration variable
`githooks.numThreads` set anywhere, e.g. in the local or global Git
configuration.

If you place a file `.all-parallel` inside `<hooksDir>/<hookName>`, all
discovered hooks inside `<hooksDir>/<hookName>` are assigned to the same batch
name `all` resulting in executing all hooks in one parallel batch.

You can inspect the computed batch name by running
[`git hooks list --batch-name`](/docs/cli/git_hooks_list.md).

## Supported Hooks

The supported hooks are listed below. Refer to the
[Git documentation](https://git-scm.com/docs/githooks) for information on what
they do and what parameters they receive.

It is recommended to use `--maintained-hooks` options during install
([1](#installation-mode-normal), [2](#installing-or-removing-run-wrappers)) to
only select the hooks which are really needed, since executing the Githooks
manager for all hooks might slow down Git operations (especially for
`reference-transaction`).

- `applypatch-msg`
- `pre-applypatch`
- `post-applypatch`
- `pre-commit`
- `pre-merge-commit`
- `prepare-commit-msg`
- `commit-msg`
- `post-commit`
- `pre-rebase`
- `post-checkout` (non-zero exit code is wrapped to 1)
- `post-merge`
- `pre-push`
- `pre-receive`
- `update`
- `post-receive`
- `post-update`
- `reference-transaction`
- `push-to-checkout`
- `pre-auto-gc`
- `post-rewrite`
- `sendemail-validate`
- `post-index-change`

The hook `fsmonitor-watchman` is currently not supported. If you have a use-case
for it and want to use it with this tool, please open an issue.

## Git Large File Storage (Git LFS) Support

If the user has installed [Git Large File Storage](https://git-lfs.github.com/)
(`git-lfs`) by calling `git lfs install` globally or locally for a repository
only, `git-lfs` installs 4 hooks when initializing (`git init`) or cloning
(`git clone`) a repository:

- `post-checkout`
- `post-commit`
- `post-merge`
- `pre-push`

Since Githooks overwrites the hooks in `<repoPath>/.git/hooks`, it will also run
all _Git LFS_ hooks internally if the `git-lfs` executable is found on the
system path. You can enforce having `git-lfs` installed on the system by placing
a `<repoPath>/.githooks/.lfs-required` file inside the repository, then if
`git-lfs` is missing, a warning is shown and the hook will exit with code `1`.
For some `post-*` hooks this does not mean that the outcome of the git command
can be influenced even tough the exit code is `1`, for example `post-commit`
hooks can't fail commits. A clone of a repository containing this file might
still work but would issue a warning and exit with code `1`, a push - however -
will fail if `git-lfs` is missing.

It is advisable for repositories using _Git LFS_ to also have a pre-commit hook
(e.g. `examples/lfs/pre-commit`) checked in which enforces a correct
installation of _Git LFS_.

## Shared Hook Repositories

The hooks are primarily designed to execute programs or scripts in the
`<repoPath>/.githooks` folder of a single repository. However there are
use-cases for common hooks, shared between many repositories with similar
requirements and functionality. For example, you could make sure Python
dependencies are updated on projects that have a `requirements.txt` file, or an
`mvn verify` is executed on `pre-commit` for Maven projects, etc.

For this reason, you can place a `.shared.yaml` file (see
[specs](#yaml-specifications)) inside the `<repoPath>/.githooks` folder, which
can hold a list of repositories which contain common and shared hooks.
Alternatively, you can have shared repositories set by multiple
`githooks.shared` local or global Git configuration variables, and the hooks in
these repositories will execute for all local projects where Githooks is
installed. See [git hooks shared](docs/cli/git_hooks_shared.md) for configuring
all 3 types of shared hooks repositories.

Below are example values for these setting.

### Global Configuration

```shell
$ git config --global --get-all githooks.shared # shared hooks in global config (for all repositories)
https://github.com/shared/hooks-python.git
[email protected]:shared/repo.git@mybranch
```

### Local Configuration

```shell
$ cd myrepo
$ git config --local --get-all githooks.shared # shared hooks in local config (for specific repository)
ssh://[email protected]/shared/[email protected]
/opt/myspecialhooks
```

### Example Githooks Repositories

Here are some shared hook repositories to get you started with:

- [Shell](https://github.com/gabyx/githooks-shell)
- [Python](https://github.com/gabyx/githooks-python)
- [C++](https://github.com/gabyx/githooks-cpp)
- [Configuration Files](https://github.com/gabyx/githooks-configs)
- [Documentation](https://github.com/gabyx/githooks-docs)

Application of the hooks:

- [Markdown2PDF](https://github.com/gabyx/RsMarkdown2PDF-Service), with CI.

They are all fully containerized so you do not have to worry about requirements
except `docker`.

### Repository Configuration

A example config `<repoPath>/.githooks/shared.yaml` (see
[specs](#yaml-specifications)):

```yaml
version: 1
urls:
  - ssh://[email protected]/shared/special-hooks.git@otherbranch
  - [email protected]:shared/repo.git@mybranch
```

The install script offers to set up shared hooks in the global Git config. but
you can do it any time by changing the global configuration variable.

### Supported URLS

Supported URL for shared hooks are:

- **All URLs [Git supports](https://git-scm.com/docs/git-clone#_git_urls)** such
  as:
  - `ssh://github.com/shared/hooks-maven.git@mybranch` and also the short `scp`
    form `[email protected]:shared/hooks-maven.git`
  - `git://[email protected]/shared/hooks-python.git`
  - `file:///local/path/to/bare-repo.git@mybranch`

  All URLs can include a tag specification syntax at the end like `...@<tag>`,
  where `<tag>` is a Git tag, branch or commit hash. The `file://` protocol is

Frequently Asked Questions

What is githooks?

githooks is a Version Control library for the Go programming language. Per-repo and shared Git hooks with version control and auto update

How do I install githooks?

Install githooks with the Go module system using `go get gabyx/githooks`. Check the repository for the current installation instructions.

What category does githooks belong to?

githooks is listed under Version Control, specifically Version Control.

← Back to Version Control