{"id":21033590,"url":"https://github.com/gabyx/githooks","last_synced_at":"2025-05-15T13:32:14.461Z","repository":{"id":39863548,"uuid":"194219619","full_name":"gabyx/Githooks","owner":"gabyx","description":"🦎 Githooks: per-repo and shared Git hooks with version control and auto update. [✩Star] if you're using it!","archived":false,"fork":false,"pushed_at":"2025-04-14T11:16:12.000Z","size":4120,"stargazers_count":111,"open_issues_count":14,"forks_count":5,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-05-11T22:39:08.037Z","etag":null,"topics":["best-practices","containerization","docker","formatting","git","git-hooks","go","linting"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gabyx.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null},"funding":{"github":["gabyx"]}},"created_at":"2019-06-28T06:28:55.000Z","updated_at":"2025-05-08T20:23:56.000Z","dependencies_parsed_at":"2023-12-14T18:51:04.914Z","dependency_job_id":"c701b62b-c1a2-4e28-980c-8018d9bbe5bd","html_url":"https://github.com/gabyx/Githooks","commit_stats":null,"previous_names":[],"tags_count":141,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gabyx%2FGithooks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gabyx%2FGithooks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gabyx%2FGithooks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gabyx%2FGithooks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gabyx","download_url":"https://codeload.github.com/gabyx/Githooks/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254349468,"owners_count":22056353,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["best-practices","containerization","docker","formatting","git","git-hooks","go","linting"],"created_at":"2024-11-19T12:58:05.396Z","updated_at":"2025-05-15T13:32:10.834Z","avatar_url":"https://github.com/gabyx.png","language":"Go","readme":"\u003cp align=\"center\"\u003e\n\u003cimg src=\"docs/githooks-logo.svg\"\u003e\n\u003cstrong\u003ev3\u003c/strong\u003e\n\u003c/p\u003e\n\n# Githooks\n\n[![CircleCI](https://circleci.com/gh/gabyx/Githooks.svg?style=svg)](https://circleci.com/gh/gabyx/Githooks)\n[![Coverage Status](https://coveralls.io/repos/github/gabyx/Githooks/badge.svg?branch=main)](https://coveralls.io/github/gabyx/Githooks?branch=main)\n[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go)\n[![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)\n[![Go Report Card](https://goreportcard.com/badge/github.com/gabyx/githooks)](https://goreportcard.com/report/github.com/gabyx/githooks)\n[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/nlohmann/json/master/LICENSE.MIT)\n[![GitHub Releases](https://img.shields.io/github/release/gabyx/githooks.svg)](https://github.com/gabyx/githooks/releases)\n![Git Version](https://img.shields.io/badge/Git-%E2%89%A5v2.28.0,%20latest%20tests%20v2.43.0-blue)\n![Go Version](https://img.shields.io/badge/Go-1.20-blue)\n![OS](https://img.shields.io/badge/OS-linux,%20macOs,%20Windows-blue)\n\nA **platform-independent hooks manager** written in Go to support shared hook\nrepositories and per-repository\n[Git hooks](https://git-scm.com/docs/cli/githooks), checked into the working\nrepository. This implementation is the Go port and successor of the\n[original implementation](https://github.com/rycus86/githooks) (see\n[Migration](#migrating)).\n\nTo make this work, the installer creates run-wrappers for Githooks that are\ninstalled into the `.git/hooks` folders on request (by default). There's more\n[to the story though](#templates-or-global-hooks). When one of the Githooks\nrun-wrappers executes, Githooks starts up and tries to find matching hooks in\nthe `.githooks` directory under the project root, and invoke them one-by-one.\nAlso it searches for hooks in configured shared hook repositories.\n\n**This Git hook manager supports:**\n\n- Running repository checked-in hooks.\n- Running shared hooks from other Git repositories (with auto-update). See these\n  [containerized example hook repositories](#example-githooks-repositories).\n- Git LFS support.\n- **No** _it works on my machine_ by\n  [running hooks over containers](#running-hooks-in-containers) and\n  [automatic build/pull integration of container images](#pull-and-build-integration)\n  (optional).\n- Command line interface.\n- Fast execution due to compiled executable. (even **2-3x faster from\n  `v2.1.1`**)\n- Fast parallel execution over threadpool.\n- Ignoring non-shared and shared hooks with patterns.\n- Automatic Githooks updates: Fully configurable for your own company by\n  url/branch and deploy settings.\n- **Bonus:** [Platform-independent dialog tool](#dialog-tool) for user prompts\n  inside your own hooks.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eTable of Content (click to expand)\u003c/b\u003e\u003c/summary\u003e\n\n\u003c!--toc:start--\u003e\n\n- [Layout and Options](#layout-and-options)\n- [Execution](#execution)\n  - [Staged Files](#staged-files)\n  - [Hook Run Configuration](#hook-run-configuration)\n  - [Parallel Execution](#parallel-execution)\n- [Supported Hooks](#supported-hooks)\n- [Git Large File Storage (Git LFS) Support](#git-large-file-storage-git-lfs-support)\n- [Shared Hook Repositories](#shared-hook-repositories)\n  - [Global Configuration](#global-configuration)\n  - [Local Configuration](#local-configuration)\n  - [Example Githooks Repositories](#example-githooks-repositories)\n  - [Repository Configuration](#repository-configuration)\n  - [Supported URLS](#supported-urls)\n  - [Skip Non-Existing Shared Hooks](#skip-non-existing-shared-hooks)\n- [Layout of Shared Hook Repositories](#layout-of-shared-hook-repositories)\n  - [Shared Repository Namespace](#shared-repository-namespace)\n- [Ignoring Hooks and Files](#ignoring-hooks-and-files)\n- [Trusting Hooks](#trusting-hooks)\n- [Disabling Githooks](#disabling-githooks)\n- [Environment Variables](#environment-variables)\n  - [Arguments to Shared Hooks](#arguments-to-shared-hooks)\n- [Log \u0026 Traces](#log-traces)\n- [Installing or Removing Run-Wrappers](#installing-or-removing-run-wrappers)\n- [Running Hooks in Containers](#running-hooks-in-containers)\n  - [Podman Manager (rootless)](#podman-manager-rootless)\n  - [Docker Manager](#docker-manager)\n  - [Pull and Build Integration](#pull-and-build-integration)\n  - [Locate Githooks Container Images](#locate-githooks-container-images)\n- [Running Hooks/Scripts Manually](#running-hooksscripts-manually)\n- [User Prompts](#user-prompts)\n- [Installation](#installation)\n  - [Quick (Secure)](#quick-secure)\n  - [Package Manager `nix`](#package-manager-nix)\n  - [Procedure](#procedure)\n  - [Install Modes](#install-modes)\n  - [Install Mode - Manual](#install-mode-manual)\n  - [Install Mode - Centralized Hooks](#install-mode-centralized-hooks)\n  - [Install from different URL and Branch](#install-from-different-url-and-branch)\n  - [Use in CI](#use-in-ci)\n    - [Nested Containers](#nested-containers)\n  - [Gitlab Demo](#gitlab-demo)\n  - [No Installation](#no-installation)\n  - [Non-Interactive Installation](#non-interactive-installation)\n  - [Install on the Server](#install-on-the-server)\n    - [Setup for Bare Repositories](#setup-for-bare-repositories)\n  - [Global Hooks or No Global Hooks](#global-hooks-or-no-global-hooks)\n    - [Manual: Use Githooks Selectively](#manual-use-githooks-selectively)\n    - [Centralized: Use Githooks For All Repositories](#centralized-use-githooks-for-all-repositories)\n  - [Updates](#updates)\n    - [Automatic Update Checks](#automatic-update-checks)\n    - [Update Mechanics](#update-mechanics)\n- [Uninstalling](#uninstalling)\n- [YAML Specifications](#yaml-specifications)\n- [Migration](#migration)\n- [Dialog Tool](#dialog-tool)\n  - [Build From Source](#build-from-source)\n  - [Dependencies](#dependencies)\n- [Tests and Debugging](#tests-and-debugging)\n  - [Debugging in the Dev Container](#debugging-in-the-dev-container)\n  - [Todos](#todos)\n- [Changelog](#changelog)\n  - [Version v2.x.x](#version-v2xx)\n- [FAQ](#faq)\n- [Acknowledgements](#acknowledgements)\n- [Authors](#authors)\n- [Support \u0026 Donation](#support-donation)\n- [License](#license)\n\u003c!--toc:end--\u003e\n\n\u003c/details\u003e\n\n## Layout and Options\n\nTake this snippet of a Git repository layout as an example:\n\n```bash\n/\n├── .githooks/\n│    ├── commit-msg/          # All commit-msg hooks.\n│    │    ├── validate        # Normal hook script.\n│    │    └── add-text        # Normal hook script.\n│    │\n│    ├── pre-commit/          # All pre-commit hooks.\n│    │    ├── .ignore.yaml    # Ignores relative to 'pre-commit' folder.\n│    │    ├── 01-validate     # Normal hook script.\n│    │    ├── 02-lint         # Normal hook script.\n│    │    ├── 03-test.yaml    # Hook run configuration.\n│    │    ├── docs.md         # Ignored in '.ignore.yaml'.\n│    │    └── final/          # Batch folder 'final' which runs all in parallel.\n│    │        ├── 01-validate # Normal hook script.\n│    │        └── 02-upload   # Normal hook script.\n│    │\n│    ├── post-merge           # An executable file.\n│    │\n│    ├── post-checkout/       # All post-checkout hooks.\n│    │   ├── .all-parallel    # All hooks in this folder run in parallel.\n│    │   └── ...\n│    ├── ...\n│    ├── .images.yaml         # Container image spec for use in e.g `03-test.yaml`.\n│    ├── .ignore.yaml         # Main ignores.\n│    ├── .shared.yaml         # Shared hook configuration.\n│    ├── .envs.yaml           # Environment variables passed to shared hooks.\n│    └── .lfs-required        # LFS is required.\n└── ...\n```\n\nAll hooks to be executed live under the `.githooks` top-level folder, that\nshould be checked into the repository. Inside, we can have directories with the\nname of the hook (like `commit-msg` and `pre-commit` above), or a file matching\nthe hook name (like `post-merge` in the example). The filenames in the directory\ndo not matter, but the ones starting with a `.` (dotfiles) will be excluded by\ndefault. All others are executed in lexical order according to the Go function\n[`Walk`](https://golang.org/pkg/path/filepath/#Walk) rules. Subfolders as e.g.\n`final` get treated as parallel batch and all hooks inside are by default\nexecuted in parallel over the thread pool. See\n[Parallel Execution](#parallel-execution) for details.\n\nYou can use the [command line helper](docs/cli/git_hooks.md) (a globally\nconfigured Git alias `alias.hooks`), that is `git hooks list`, to list all hooks\nand their current state that apply to the current repository. For this\nrepository this [looks like the following.](docs/githooks-list.png)\n\n## Execution\n\nIf a file is executable, it is directly invoked, otherwise it is interpreted\nwith the `sh` shell. On Windows that mostly means dispatching to the `bash.exe`\nfrom [https://gitforwindows.org](https://gitforwindows.org).\n\n**All parameters and standard input** are forwarded from Git to the hooks. The\nstandard output and standard error of any hook which Githooks runs is captured\n**together**\u003cspan id=\"a1\"\u003e[\u003csup\u003e1\u003c/sup\u003e](#1)\u003c/span\u003e and printed to the standard\nerror stream which might or might not get read by Git itself (e.g. `pre-push`).\n\nHooks can also be specified by a run configuration in a corresponding YAML file,\nsee [Hook Run Configuration](#hook-run-configuration).\n\n### Staged Files\n\nHooks related to `commit` events (where it makes sense, not `post-commit`) will\nalso have a `${STAGED_FILES}` or `${STAGED_FILES_FILE}` environment variable\nset. By default, `STAGED_FILES` contains the list of staged and changed files\naccording to `git diff --cached --diff-filter=ACMR --name-only`. File paths are\nseparated by a newline `\\n`. If you want to iterate in a shell script over them,\nand expect spaces in paths, you might want to set the `IFS` like this:\n\n```shell\nIFS=\"\n\"\nfor file in ${STAGED_FILES}; do\n    echo \"$file\"\ndone\n```\n\nThe `ACMR` filter in the `git diff` will include staged files that are added,\ncopied, modified or renamed.\n\nTo enable the `STAGED_FILES_FILE` variable which contains the path to the file\ncontaining the paths to all staged files (separated by null-chars `\\0`, e.g.\n`\u003cpath\u003e\\0\u003cpath\u003e\\0`) use\n\n```shell\ngit config githooks.exportStagedFilesAsFile true\n```\n\nand read this file in `bash` with something like:\n\n```shell\n#!/bin/bash\nwhile read -rd $'\\\\0' file; do\n    echo \"$file\"\ndone \u003c \"$STAGED_FILES_FILE\"\n```\n\n**\u003cspan id=\"1\"\u003e\u003csup\u003e1\u003c/sup\u003e\u003c/span\u003e[⏎](#a1) Note:** This caveat is basically\nthere because standard output and error might get interleaved badly and so far\nno solution to this small problem has been tackled yet. It is far better to\noutput both streams in the correct order, and therefore send it to the error\nstream because that will not conflict in anyway with Git (see\n[fsmonitor-watchman](https://git-scm.com/docs/githooks#_fsmonitor_watchman),\nunsupported right now.). If that poses a real problem for you, open an issue.\n\n### Hook Run Configuration\n\nEach supported hook can also be specified by a configuration file\n`\u003chookName\u003e.yaml` where `\u003chookName\u003e` is any\n[supported hook name](#supported-hooks). An example might look like the\nfollowing:\n\n```yaml\n# The command to run.\n# - if it contains path separators and is relative, it its evaluated relative to\n#   the worktree of the repository where this config resides.\ncmd: \"dist/command-of-${env:USER}.exe\"\n\n# The arguments given to `cmd`.\nargs:\n  - \"-s\"\n  - \"--all\"\n  - \"${env:GPG_PUBLIC_KEY}\"\n  - \"--test ${git-l:my-local-git-config-var}\"\n\n# If you want to make sure your file is not\n# treated always as the newest version. Fix the version by:\nversion: 1\n```\n\nAll additional arguments given by Git to `\u003chookName\u003e` will be appended last onto\n`args`. All environment and Git config variables in `args` and `cmd` are\nsubstituted with the following syntax:\n\n- `${env:VAR}` : An environment variable `VAR`.\n- `${git:VAR}` : A Git config variable `VAR` which corresponds to\n  `git config 'VAR'`.\n- `${git-l:VAR}` : A Git config variable `VAR` which corresponds to\n  `git config --local 'VAR'`.\n- `${git-g:VAR}` : A Git config variable `VAR` which corresponds to\n  `git config --global 'VAR'`.\n- `${git-s:VAR}` : A Git config variable `VAR` which corresponds to\n  `git config --system 'VAR'`.\n\nNot existing environment variables or Git config variables are replaced with the\nempty string by default. If you use `${!...:VAR}` (e.g `${!git-s:VAR }`) it will\ntrigger an error and fail the hook if the variable `VAR` is not found. Escaping\nthe above syntax works with `\\${...}`.\n\n**Sidenote**: You might wonder why this configuration is not gathered in one\nsingle YAML file for all hooks. The reason is that each hook invocation by Git\nis separate. Avoiding reading this total file several times needs time and since\nwe want speed and only an opt-in solution this is avoided.\n\nGithooks defines the\n[environment variables in this table](#environment-variables) on hooks\ninvocation.\n\n### Parallel Execution\n\nAs in the [example](#layout-and-options), all discovered hooks in subfolders\n`\u003cbatchName\u003e`, e.g. `\u003crepoPath\u003e/\u003chooksDir\u003e/\u003chookName\u003e/\u003cbatchName\u003e/*` where\n`\u003chooksDir\u003e` is either\n\n- `.githooks` for repository checked-in hooks or\n- `githooks`, `.githooks` or `.` for shared repository hooks,\n\nare assigned the same batch name `\u003cbatchName\u003e` and processed in parallel. Each\nbatch is a synchronisation point and starts after the one before has finished.\nThe threadpool uses by default as many threads as cores on the system. The\nnumber of threads can be controlled by the Git configuration variable\n`githooks.numThreads` set anywhere, e.g. in the local or global Git\nconfiguration.\n\nIf you place a file `.all-parallel` inside `\u003chooksDir\u003e/\u003chookName\u003e`, all\ndiscovered hooks inside `\u003chooksDir\u003e/\u003chookName\u003e` are assigned to the same batch\nname `all` resulting in executing all hooks in one parallel batch.\n\nYou can inspect the computed batch name by running\n[`git hooks list --batch-name`](/docs/cli/git_hooks_list.md).\n\n## Supported Hooks\n\nThe supported hooks are listed below. Refer to the\n[Git documentation](https://git-scm.com/docs/cli/githooks) for information on\nwhat they do and what parameters they receive.\n\nIt is receommended to use `--maintained-hooks` options during install\n([1](#installation-mode-normal), [2](#installing-or-removing-run-wrappers)) to\nonly select the hooks which are really needed, since executing the Githooks\nmanager for all hooks might slow down Git operations (especially for\n`reference-transaction`).\n\n- `applypatch-msg`\n- `pre-applypatch`\n- `post-applypatch`\n- `pre-commit`\n- `pre-merge-commit`\n- `prepare-commit-msg`\n- `commit-msg`\n- `post-commit`\n- `pre-rebase`\n- `post-checkout` (non-zero exit code is wrapped to 1)\n- `post-merge`\n- `pre-push`\n- `pre-receive`\n- `update`\n- `post-receive`\n- `post-update`\n- `reference-transaction`\n- `push-to-checkout`\n- `pre-auto-gc`\n- `post-rewrite`\n- `sendemail-validate`\n- `post-index-change`\n\nThe hook `fsmonitor-watchman` is currently not supported. If you have a use-case\nfor it and want to use it with this tool, please open an issue.\n\n## Git Large File Storage (Git LFS) Support\n\nIf the user has installed [Git Large File Storage](https://git-lfs.github.com/)\n(`git-lfs`) by calling `git lfs install` globally or locally for a repository\nonly, `git-lfs` installs 4 hooks when initializing (`git init`) or cloning\n(`git clone`) a repository:\n\n- `post-checkout`\n- `post-commit`\n- `post-merge`\n- `pre-push`\n\nSince Githooks overwrites the hooks in `\u003crepoPath\u003e/.git/hooks`, it will also run\nall _Git LFS_ hooks internally if the `git-lfs` executable is found on the\nsystem path. You can enforce having `git-lfs` installed on the system by placing\na `\u003crepoPath\u003e/.githooks/.lfs-required` file inside the repository, then if\n`git-lfs` is missing, a warning is shown and the hook will exit with code `1`.\nFor some `post-*` hooks this does not mean that the outcome of the git command\ncan be influenced even tough the exit code is `1`, for example `post-commit`\nhooks can't fail commits. A clone of a repository containing this file might\nstill work but would issue a warning and exit with code `1`, a push - however -\nwill fail if `git-lfs` is missing.\n\nIt is advisable for repositories using _Git LFS_ to also have a pre-commit hook\n(e.g. `examples/lfs/pre-commit`) checked in which enforces a correct\ninstallation of _Git LFS_.\n\n## Shared Hook Repositories\n\nThe hooks are primarily designed to execute programs or scripts in the\n`\u003crepoPath\u003e/.githooks` folder of a single repository. However there are\nuse-cases for common hooks, shared between many repositories with similar\nrequirements and functionality. For example, you could make sure Python\ndependencies are updated on projects that have a `requirements.txt` file, or an\n`mvn verify` is executed on `pre-commit` for Maven projects, etc.\n\nFor this reason, you can place a `.shared.yaml` file (see\n[specs](#yaml-specifications)) inside the `\u003crepoPath\u003e/.githooks` folder, which\ncan hold a list of repositories which contain common and shared hooks.\nAlternatively, you can have shared repositories set by multiple\n`githooks.shared` local or global Git configuration variables, and the hooks in\nthese repositories will execute for all local projects where Githooks is\ninstalled. See [git hooks shared](docs/cli/git_hooks_shared.md) for configuring\nall 3 types of shared hooks repositories.\n\nBelow are example values for these setting.\n\n### Global Configuration\n\n```shell\n$ git config --global --get-all githooks.shared # shared hooks in global config (for all repositories)\nhttps://github.com/shared/hooks-python.git\ngit@github.com:shared/repo.git@mybranch\n```\n\n### Local Configuration\n\n```shell\n$ cd myrepo\n$ git config --local --get-all githooks.shared # shared hooks in local config (for specific repository)\nssh://user@github.com/shared/special-hooks.git@v3.3.3\n/opt/myspecialhooks\n```\n\n### Example Githooks Repositories\n\nHere are some shared hook repositories to get you started with:\n\n- [Shell](https://github.com/gabyx/githooks-shell)\n- [Python](https://github.com/gabyx/githooks-python)\n- [C++](https://github.com/gabyx/githooks-cpp)\n- [Configuration Files](https://github.com/gabyx/githooks-configs)\n- [Documentation](https://github.com/gabyx/githooks-docs)\n\nApplication of the hooks:\n\n- [Markdown2PDF](https://github.com/gabyx/RsMarkdown2PDF-Service), with CI.\n\nThey are all fully containerized so you do not have to worry about requirements\nexcept `docker`.\n\n### Repository Configuration\n\nA example config `\u003crepoPath\u003e/.githooks/shared.yaml` (see\n[specs](#yaml-specifications)):\n\n```yaml\nversion: 1\nurls:\n  - ssh://user@github.com/shared/special-hooks.git@otherbranch\n  - git@github.com:shared/repo.git@mybranch\n```\n\nThe install script offers to set up shared hooks in the global Git config. but\nyou can do it any time by changing the global configuration variable.\n\n### Supported URLS\n\nSupported URL for shared hooks are:\n\n- **All URLs [Git supports](https://git-scm.com/docs/cli/git-clone#_git_urls)**\n  such as:\n\n  - `ssh://github.com/shared/hooks-maven.git@mybranch` and also the short `scp`\n    form `git@github.com:shared/hooks-maven.git`\n  - `git://user@github.com/shared/hooks-python.git`\n  - `file:///local/path/to/bare-repo.git@mybranch`\n\n  All URLs can include a tag specification syntax at the end like `...@\u003ctag\u003e`,\n  where `\u003ctag\u003e` is a Git tag, branch or commit hash. The `file://` protocol is\n  treated the same as a local path to a bare repository, _see next point_.\n\n- **Local paths** to bare and non-bare repositories such as:\n\n  - `/local/path/to/checkout` (gets used directly)\n  - `/local/path/to/bare-repo.git@mybranch` (gets cloned internally)\n\n  Note that relative paths are relative to the path of the repository executing\n  the hook. These entries are forbidden for **shared hooks** configured by\n  `.githooks/.shared.yaml` per repository because it makes little sense and is a\n  security risk.\n\nShared hooks repositories specified by _URLs_ and _local paths to bare\nrepository_ will be checked out into the `\u003cinstallPrefix\u003e/.githooks/shared`\nfolder (`~/.githooks/shared` by default), and are updated automatically after a\n`post-merge` event (typically a `git pull`) on any local repositories. Any other\nlocal path will be used **directly and will not be updated or modified**.\nAdditionally, the update of shared hook repositories can also be triggered on\nother hook names by setting a comma-separated list of additional hook names in\nthe Git configuration parameter `githooks.sharedHooksUpdateTriggers` on any\nconfiguration level.\n\nYou can also manage and update shared hook repositories using the\n[`git hooks shared update`](docs/cli/git_hooks_shared.md) command.\n\n### Skip Non-Existing Shared Hooks\n\n**By default, Githooks will fail if any configured shared hooks are not\navailable and you need to update them by running `git hooks update`**.\n\nBy using\n[`git hooks config skip-non-existing-shared-hooks --help`](/docs/cli/git_hooks_config_skip-non-existing-shared-hooks.md)\nyou can disable this behavior locally/globally or by environment variable\n`GITHOOKS_SKIP_NON_EXISTING_SHARED_HOOKS` (see\n[env. variables](#environment-variables)) which makes Githooks skip non-existing\nshared hooks.\n\n## Layout of Shared Hook Repositories\n\nThe layout of these shared repositories is the same as above, with the exception\nthat the hook folders (or files) can be at the project root as well, to avoid\nthe redundant `.githooks` folder.\n\nIf you want the shared hook repository to use Githooks itself (e.g. for\ndevelopment purposes by using hooks from `\u003csharedRepo\u003e/.githooks`) you can\nfurthermore place the _shared_ hooks inside a `\u003csharedRepo/githooks` subfolder.\nIn that case the `\u003csharedRepo\u003e/.githooks` folder is ignored when other users use\nthis shared repository.\n\nThe priority to find hooks in a shared hook repository is as follows: consider\nhooks\n\n1. in `\u003chooksDir\u003e := \u003csharedRepo\u003e/githooks`, if it does not exist, consider\n   hooks\n2. in `\u003chooksDir\u003e := \u003csharedRepo\u003e/.githooks`, if it does not exist consider\n   hooks\n3. in `\u003chooksDir\u003e := \u003csharedRepo\u003e` as the last fallback.\n\nEach of these directories can be of the same format as the normal `.githooks`\nfolder in a single repository.\n\nYou can get the root directory of a configured shared repository with namespace\n`\u003cnamespace\u003e` by running `git hooks shared root ns:\u003cnamespace\u003e`. This might be\nhelpful in scripts if you have common shared functionality inside this shared\nrepository you want to use.\n\n### Shared Repository Namespace\n\nA shared repository can optionally have a namespace associated with it. The name\ncan be stored in a file `.namespace` in any possible hooks directory\n`\u003chooksDir\u003e` of the shared repository, see\n[layout](#layout-of-shared-hook-repositories). The namespace comes into play\nwhen ignoring/disabling certain hooks. See\n[ignoring hooks](#ignoring-hooks-and-files). The namespace name must not contain\nwhite spaces (`\\s`) or slashes `/`.\n\nThe following namespaces names are reserved internally:\n\n- `gh-self` : for hooks in the repository where Githooks runs (if no\n  `.namespace` is existing).\n- `gh-self-repl` : for original Git hooks which were replaced by Githooks during\n  install.\n\n## Ignoring Hooks and Files\n\nThe `.ignore.yaml` (see [specs](#yaml-specifications)) files allow excluding\nfiles\n\n- from being treated as hook scripts or\n- hooks from being run.\n\nYou can ignore executing all sorts of hooks per Git repository by specifying\n**patterns** or explicit **paths** which match against a hook's (file's)\n_namespace path_. **Note:** Dot-files, e.g. `.myfile` are always ignored.\n\nEach hook either in the current repository `\u003crepoPath\u003e/.githooks/...` or inside\na shared hooks repository has a so called _namespace path_.\n\nA _namespace path_ consists of the _name of the hook_ prefixed by a _namespace_\n, e.g. :\n\n```\n  \u003cnamespacePath\u003e := ns:\u003cnamespace\u003e/\u003crelPath\u003e = \"ns:core-hooks/pre-commit/check-numbers.py\"\n```\n\nwhere `\u003crelPath\u003e = pre-commit/check-numbers.py` is the relative path to the\nhook. Each shared repository can provide its own\n[namespace](#shared-repository-namespace).\n\nA [namespace](#shared-repository-namespace) will be used when the hook belongs\nto a shared hook repository and will have a default unique value if it is not\ndefined. You can inspect all _namespace paths_ by inspecting `ns-path:` in the\noutput of [git hooks list](docs/cli/git_hooks_list.md) in the current\nrepository. All ignore entries in `.ignore.yaml` (patterns or paths) will match\nagainst these _namespace paths_.\n\nDisabling works like:\n\n```shell\n# Disable certain hooks by a pattern in this repository:\n# User ignore pattern stored in `.git/.githooks.ignore.yaml`:\n$ git hooks ignore add --pattern \"pre-commit/**\" # Store: `.git/.githooks.ignore.yaml`:\n# or stored inside the repository:\n$ git hooks ignore add --repository --pattern \"pre-commit/**\" # Store: `.githooks/.ignore.yaml`:\n\n# Disable certain shared hooks (with namespace 'my-shared-super-hooks')\n# by a glob pattern in this repository:\n$ git hooks ignore add --repository --pattern \"my-shared-super-hooks://pre-commit/**\"\n```\n\nIn the above [example](#layout-and-options]), one of the `.ignore.yaml` files\nshould contain a glob pattern `**/*.md` to exclude the `pre-commit/docs.md`\nMarkdown file. Patterns can contain double star syntax to match multiple\ndirectories, e.g. `**/*.txt` instead of `*.txt`.\n\nThe main ignore file `\u003crepoPath\u003e/\u003chookDir\u003e/.ignore.yaml` applies to all hooks.\nAny additional `\u003crepoPath\u003e/\u003chookDir\u003e/\u003chookName\u003e/.ignore.yaml` file inside\n`\u003chookDir\u003e` will be accumulated to the main file and patterns not starting with\n`ns:` **are made relative to the folder `\u003chookName\u003e`**. You can also manage\n`.ignore.yaml` files using\n[`git hooks ignore [add|remove] --help`](docs/cli/git_hooks_ignore.md). Consult\nthis command documentation for further information on the pattern syntax.\n\n## Trusting Hooks\n\nTo try and make things a little bit more secure, Githooks checks if any new\nhooks were added we haven't run before, or if any of the existing ones have\nchanged. When they have, it will prompt for confirmation (trust prompt) whether\nyou accept those changes or not, and you can also disable specific hooks to skip\nrunning them until you decide otherwise. The trust prompt is always **fatal**\nmeaning that failing to answer the prompt, or any other prompt error, will\nresult in a failing Git hook. To make the `runner` non-interactive, see\n[user prompts](#user-prompts). If a hook is still _active and untrusted_ after\nthe prompt, **Githooks will fail by default**. This is useful to be sure that\nall hooks get executed. However, you can disabled this behavior by skipping\nactive, untrusted hooks with\n[`git hooks config skip-untrusted-hooks --enable`](docs/cli/git_hooks_config_skip-untrusted-hooks.md)\nor by setting `GITHOOKS_SKIP_UNTRUSTED_HOOKS` (see\n[env. variables](#environment-variables)).\n\nThe accepted checksums are maintained in the\n`\u003crepoPath\u003e/.git/.githooks.checksum` directory, per local repository. You can\nhowever use a global checksum directory setup by making an absolute symbolic\nlink with name `.githooks.checksum` inside the template directory\n(`init.templateDir`) which gets installed in each clone.\n\nIf the repository contains a `\u003crepoPath\u003e/.githooks/trust-all` file, it is marked\nas a trusted repository. Consult\n[`git hooks trust --help`](docs/cli/git_hooks_trust.md). On the first\ninteraction with hooks, Githooks will ask for confirmation that the user trusts\nall existing and future hooks in the repository, and if she does, no more\nconfirmation prompts will be shown. This can be reverted by running\n[`git hooks config trust-all --reset`](docs/cli/git_hooks_config_trust-all.md)\ncommand. This is a per-repository setting. Consult\n[`git hooks config trust-all --help`](docs/cli/git_hooks_config_trust-all.md)\nfor more information.\n\nYou can also trust individual hooks by using\n[`git hooks trust hooks --help`](docs/cli/git_hooks_trust_hooks.md).\n\n## Disabling Githooks\n\nTo disable running any Githooks locally or globally, use the following:\n\n```shell\n# Disable Githooks completely for this repository:\n$ git hooks disable # Use --reset to undo.\n# or\n$ git hooks config disable --set # Same thing... Config: `githooks.disable`\n\n\n# Disable Githooks globally (for all repositories):\n$ git hooks disable --global # Use --reset to undo.\n# or\n$ git hooks config disable --set --global # Same thing... Config: `githooks.disable`\n```\n\nAlso, as mentioned above, all hook executions can be bypassed with a non-empty\nvalue in the `GITHOOKS_DISABLE` environment variable.\n\n## Environment Variables\n\nAll of these environment variables are either defined during Githooks runner\nexecuting or affect its behavior. These should mostly only be used locally and\nnot globally be defined.\n\n| Environment Variables                          | Effect                                                                                                                    |\n| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |\n| `GITHOOKS_OS` (defined by Githooks)            | The operating system. \u003cbr\u003eSee [Exported Environment Variables](#exported-environment-variables).                          |\n| `GITHOOKS_ARCH` (defined by Githooks)          | The system architecture. \u003cbr\u003eSee [Exported Environment Variables](#exported-environment-variables).                       |\n| `STAGED_FILES` (defined by Githooks)           | All staged files. Only set in `pre-commit`, `prepare-commit-msg` and `commit-msg` hook.                                   |\n| `GITHOOKS_CONTAINER_RUN` (defined by Githooks) | If a hook is run over a container, this variable is set and `true`                                                        |\n| `GITHOOKS_DISABLE`                             | If defined, disables running hooks run by Githooks,\u003cbr\u003eexcept `git lfs` and the replaced old hooks.                       |\n| `GITHOOKS_RUNNER_TRACE`                        | If defined, enables tracing during \u003cbr\u003eGithooks runner execution. A value of `1` enables more output.                     |\n| `GITHOOKS_LOG_LEVEL`                           | A value `debug`, `info`, `warn`, `error` or `disable` sets the log level during \u003cbr\u003eGithooks runner execution.            |\n| `GITHOOKS_SKIP_NON_EXISTING_SHARED_HOOKS=true` | Skips on `true` and fails on `false` (or empty) for non-existing shared hooks. \u003cbr\u003eSee [Trusting Hooks](#trusting-hooks). |\n| `GITHOOKS_SKIP_UNTRUSTED_HOOKS=true`           | Skips on `true` and fails on `false` (or empty) for untrusted hooks. \u003cbr\u003eSee [Trusting Hooks](#trusting-hooks).           |\n\n### Arguments to Shared Hooks\n\nYou can pass arguments to shared hooks currently by specifying a\n[`.githooks/.envs.yaml`](#yaml-specifications) file which will export\nenvironment variables when running the shared hooks selected by its\n[namespace](#shared-repository-namespace):\n\n```yaml\nenvs:\n  mystuff:\n    # All these variables are exported\n    # for shared hook namespace `mystuff`.\n    - \"MYSTUFF_CHECK_DEAD_CODE=1\"\n    - \"MYSTUFF_STAGE_ON_FORMAT=1\"\n\n  sharedA:\n    # All these variables are exported\n    # for shared hook namespace `sharedA`.\n    - \"SHAREDA_ABC=1\"\n    - \"SHAREDA_TWEET=1\"\n```\n\n## Log \u0026 Traces\n\nYou can see how the Githooks `runner` is been called by setting the environment\nvariable `GITHOOKS_RUNNER_TRACE` to a non empty value.\n\n```shell\nGITHOOKS_RUNNER_TRACE=1 git \u003ccommand\u003e ...\n```\n\n## Installing or Removing Run-Wrappers\n\nYou can install and uninstall run-wrappers inside a repository with\n[`git hooks install`](docs/cli/git_hooks_install.md). or\n[`git hooks uninstall`](docs/cli/git_hooks_install.md). This installs and\nuninstalls wrappers from `${GIT_DIR}/hooks` as well as sets and unsets local\nGithooks-internal Git configuration variables.\n\nTo install run-wrappers for only selective hooks, use `--maintained-hooks`, e.g.\n\n```shell\ncd repository\ngit hook install \\\n    --maintained-hooks \"!all, pre-commit, pre-merge-commit, prepare-commit-msg, commit-msg, post-commit\" \\\n    --maintained-hooks \"pre-rebase, post-checkout, post-merge, pre-push\"\n```\n\n**Note:** Git LFS hooks is properly taken care of when `--maintained-hooks` is\nused. That is, when you don't select a Git LFS hooks in `--maintained-hooks`,\nthe missing Git LFS hooks will be installed too.\n\n## Running Hooks in Containers\n\nYou can run hooks containerized over a container manager such as `docker`. This\nrelieves the maintainer of a Githooks shared repo from dealing with _\"It works\non my machine!\"_\n\nTo enable containerized hook runs set the Git config variable either locally or\nglobally with\n\n```shell\ngit hooks config enable-containerized-hooks [--global] --set true\n```\n\nor use the environment variable `GITHOOKS_CONTAINERIZED_HOOKS_ENABLED=true`.\n\nOptionally set the container manager (default is `docker`) like\n\n```shell\ngit hooks config container-manager-types [--global] --set \"podman,docker\"\n```\n\nThe container manager types can be a list from \\[`docker`, `podman`\\] where the\nfirst valid one is used to run the hooks.\n\nRunning a hook in a container is achieved by specifying the image reference\n(image name) inside a [hook run configuration](#hook-run-configuration), e.g.\n`\u003chooksDir\u003e/pre-commit/myhook.yaml`. This works for normal repositories as well\nas for shared Githooks repositories.\n\nFor a shared repository, the file `sharedRepo/githooks/pre-commit/checkit.yaml`\nmight look like\n\n```yaml\nversion: 3\ncmd: ./myscripts/checkit.sh\nargs:\nimage:\n  reference: \"my-shellcheck:1.2.0\"\n```\n\nwhich will launch the command `./myscript/checkit.sh` in a container\n`my-shellcheck:1.2.0`. The current Git repository where this hook is launched is\nmounted as the current working directory and the relative path\n`./myscript/checkit.sh` will be mangled to a path in the mounted read-only\nvolume of this shared Githooks repo `sharedRepo` which is cached inside\n`\u003cinstallDir\u003e/shared`.\n\n**Note:** When running a hook script or command over a container, you will not\nhave access to the same environment variables as on your host system. All\nGithooks [environment variables](#environment-variables) are forwarded however\nto the container run.\n\n**Note:** The images you run must be `rootless` (contain a `USER` statement) and\nthis user must have user/group id `1000` (Todo: We can loosen this requirement\nif really needed). See the\n[example](https://github.com/gabyx/Githooks-Shell/blob/main/githooks/container/Dockerfile).\n\n### Podman Manager (rootless)\n\n**This manager is strongly preferred due to better security and less hassle with\nvolume mounts.**\n\nThe containers are run with the following flags to `podman`:\n\n- `--userns=keep-id:uid=1000,gid=1000`:\n  [_User namespace mapping_](https://docs.podman.io/en/v4.4/markdown/options/userns.container.html).\n  Maps the user/group id of the user running Githooks (your host user) to the\n  container user/group id `1000`. This means a host user with user/group id e.g.\n  4000 will be seen inside the container as user/group id 1000. This also works\n  for all volume mounts which will have `1000:1000` permission inside the\n  container.\n\n### Docker Manager\n\nThe containers are run with the following flags to `docker`:\n\n- `--user:\u003cuid\u003e:\u003cgid\u003e`: The container is run as the same user id and group id as\n  the user which runs Githooks (your host user). See the note below why this is\n  the case.\n\n**Note:** Running commands in containers which modify files on writable volumes\nhas some caveats and quirks with permissions which are host system dependent.\nHongli Lai summarized these troubles in a\n[very good article](https://www.fullstaq.com/knowledge-hub/blogs/docker-and-the-host-filesystem-owner-matching-problem).\nLong story short if the images are run with the `docker` manager, **you should\nuse\n[`MatchHostFsOwner`](https://github.com/FooBarWidget/matchhostfsowner/releases)**\nwhich counter acts these permission problems neatly by installing\n[this into your hook's sidecar container](https://github.com/gabyx/Githooks-Shell/blob/main/githooks/container/Dockerfile#L29).\n\n### Pull and Build Integration\n\nTo have this containerized functionality neatly integrated, Githooks provides a\nway for specifying image pull and build options in an opt-in file\n`\u003chooksDir\u003e/.images.yaml`\n([see `\u003chooksDir\u003e` definition](#layout-of-shared-hook-repositories)), e.g.\n\n```yaml\nversion: 1\nimages:\n  koalaman/shellcheck:latest:\n  # will pull the image reference according to this dictionary key.\n\n  my-shellcheck:1.2.0:\n    pull: # optional\n      reference: myimages/${namespace}-shellcheck:v0.9.0\n\n  ${namespace}-my-shellcheck:1.3.0:\n    build:\n      dockerfile: ./.githooks/docker/Dockerfile\n      stage: myfinalstage\n      context: ./.githooks/docker\n```\n\nThis file will be acted upon when shared hooks are updated, e.g.\n`git hooks shared update` or when this happens [automatically](#supported-urls).\n\nYou can trigger the image pull/build procedure by running\n\n```shell\ngit hooks images update [--config ...]\n```\n\ninside a normal repo `a` which configures such a file in\n`a/.githooks/.images.yaml` or in a normal repository `b` which configures to use\na `sharedRepo` in `.shared.yaml` which configures it in\n`sharedRepo/githooks/.images.yaml`. If this shared repo `sharedRepo` has a\n[namespace `banana` configured](#layout-of-shared-hook-repositories),\n`git hooks images update` in `b` will trigger\n\n- a **pull** of image `koalaman/shellcheck:latest`,\n- a **pull** of image `myimages/banana-shellcheck:v0.9.0` and tagging it with\n  `my-shellcheck:1.2.0`,\n- and a build of an image `banana-my-shellcheck:1.3.0` of stage `myfinalstage`\n  in the respective Dockerfile `./.githooks/docker/Dockerfile` where the build\n  context is set to `.githooks/docker`.\n\n**Note:** All paths in the build specification `build:` are relative to the\nrepository root where this `.images.yaml` is located.\n\n### Locate Githooks Container Images\n\nAll built images are automatically labeled with `githooks-version` to make them\neasy to retrieve, e.g.\n\n```shell\ndocker images --filter label=githooks-version\n```\n\nor to easily delete all of them by\n\n```shell\ndocker rmi $(docker images -f \"label=githooks-version\" -q)\n```\n\n**Pruning Of Older Images:** If a shared repository is updated from\n`git hooks shared update` it might come with new images references in\n`.images.yaml`. Githooks does not yet detect which references are no longer\nneeded after the pull/build procedure nor does it offer a way yet to prune older\nimages (just use the above).\n\n## Running Hooks/Scripts Manually\n\nThe command `git hooks exec` helps to launch executables and\n[run configuration](#hook-run-configuration) the same as Githooks does when run\nnormally. This features simplifies executing add-on scripts/executables\ndistributed in shared hook repositories (and also locally with `ns:gh-self`).\n\nFor example execute the following\n[add-on 'format-all' script in this shared repository](https://github.com/gabyx/Githooks-Docs/blob/main/githooks/scripts/format-docs-all.yaml)\nwith:\n\n```shell\ngit hooks exec --containerized \\\n  ns:githooks-docs/scripts/format-docs-all.yaml -- \\\n  --force \\\n  --dir ./\n```\n\nThis will launch the specified container and run the script.\n\n**Note**: You need to have configured this shared repository inside your repo\nwhere you use Githooks and it needs to be available with\n`git hooks shared update`.\n\n## User Prompts\n\nGithooks shows user prompts during installation, updating (automatic or manual),\nuninstallation and when executing hooks (the `runner` executable).\n\nThe `runner` might get executed over a Git GUI or any other environment where no\nterminal is available. In this case all user prompts are shown as GUI dialogs\nwith the included [platform-independent dialog tool](#dialog-tool). The GUI\ndialog fallback is currently only enabled for the `runner`.\n\nGithooks distinguishes between _fatal_ and _non-fatal_ prompts.\n\n- A _fatal_ prompt will result in a complete abort if\n\n  - The prompt could not be shown (terminal or GUI dialog).\n  - The answer returned by the user is incorrect (terminal only) or the user\n    canceled the GUI dialog.\n\n- A _non-fatal_ prompt always has a default answer which is taken in the above\n  failing cases and the execution continues. Warning messages might be shown\n  however.\n\nThe `runner` will show prompts, either in the terminal or as GUI dialog, in the\nfollowing cases:\n\n1. **Trust prompt**: The user is required to trust/untrust a new/changed hook:\n   **fatal**.\n2. **Update prompts**: The user is requested to accept a new update if automatic\n   updates are enabled (`git hooks update --enable-check`): **non-fatal**.\n   - Various other prompts when the updater is launched: **non-fatal**.\n\nUser prompts during `runner` execution are sometimes not desirable (server\ninfrastructure, docker container, etc...) and need to be disabled. Setting\n`git hooks config non-interactive-runner --enable --global` will:\n\n- Take default answers for all **non-fatal** prompts. No warnings are shown.\n- Take default answer for a **fatal prompt** if it is configured: The only fatal\n  prompt is the **trust prompt** which can be configured to pass by executing\n  `git hooks config trust-all --accept`.\n\n## Installation\n\n### Quick (Secure)\n\nLaunch the below shell command. It will download the release from Github and\nlaunch the installer.\n\n**Note:** All downloaded files are checksum \u0026 signature checked.\n\n```shell\ncurl -sL https://raw.githubusercontent.com/gabyx/githooks/main/scripts/install.sh | bash\n```\n\nSee the next sections on different install options.\n\n**Note:** Use `bash -s -- -h` above to show the help message of the bootstrap\nscript and `bash -s -- -- \u003coptions\u003e` to pass arguments to the installer\n(`cli installer`), e.g. `bash -s -- -- -h` to show the help.\n\n### Package Manager `nix`\n\nGithooks is inside [`nixpkgs`](https://github.com/NixOS/nixpkgs), so you can\naccess it by `pkgs.githooks` in your `flake.nix`.\n\nTo install the Githooks derivation at a different version `\u003cversion\u003e`, add the\nfollowing to your `inputs` in your `flake.nix`:\n\n```nix\ninputs = {\n    githooks = {\n      url = \"github:gabyx/githooks?dir=nix\u0026ref=v\u003cversion\u003e\";\n      inputs.nixpkgs.follows = \"nixpkgs\";\n    };\n}\n```\n\n**You should never install a major version upgrade as Githooks should be\nuninstalled completely before. The uninstaller on any version however should\nwork backward-compatible.**\n\nand then use it in your packages, e.g. here with home-manager by doing:\n\n```nix\n{ lib, pkgs, inputs, ...}:\nlet\n  githooks = inputs.githooks.packages.\"${pkgs.system}\".default;\nin {\n  home.packages = [githooks]\n}\n```\n\n### Procedure\n\nThe installer will:\n\n1. Download the current binaries if `--update` is not given. Optionally it can\n   use a deploy settings file to specify where to get the binaries from.\n   (default is this repository here.)\n\n1. Verify the checksums and signature of the downloaded binaries.\n\n1. Launch the current installer which proceeds with the next steps. If\n   `--update` is given the newest Githooks is downloaded and installed directly.\n\n1. Find the install mode relevant hooks directory `\u003chooksDir\u003e`:\n\n   - Use the directory given with `--hooks-dir \u003cdir\u003e` on the command line.\n\n   - Use `git config --get githooks.pathForUseCoreHooksPath` if Githooks is\n     already installed.\n\n   - Use the following template directory if `--hooks-dir-use-template-dir` is\n     given:\n\n     1. Use `GIT_TEPMLATE_DIR` if set and add `/hooks`\n     1. Use Git config value `init.templateDir` if set and add `/hooks`\n     1. Use `\u003cinstall-dir\u003e/templates/hooks`.\n\n     **Note:** This will silently make all new repositories with `git init` or\n     `git clone` directly use Githooks, this is similar to the\n     [`centralized`](#install-mode-centralized-hooks) install mode.\n\n1. Write all Githooks run-wrappers into the hooks directory `\u003chooksDir\u003e` and\n\n   - Set `core.hooksPath` for [`centralized`](#install-mode-centralized-hooks)\n     install mode (`--centralized`).\n\n1. Offer to enable automatic update checks.\n\n1. Offer to find existing Git repositories on the file system (disable with\n   `--skip-install-into-existing`)\n\n   1. Make them use Githooks by either setting `core.hooksPath` (or install\n      run-wrappers if `\u003crepo-git-dir\u003e/hooks/githooks-contains-run-wrappers`\n      exists).\n\n   1. Offer to add an intro README in their `.githooks` folder.\n\n1. Install/update run-wrappers into all registered repositories: Repositories\n   using Githooks get registered in the install folders `registered.yaml` file\n   on their first hook invocation.\n\n1. Offer to set up shared hook repositories.\n\n### Install Modes\n\nThis installer can install Githooks in one of 2 ways:\n\n- **Manual**: To use Githooks in a repo, it must be installed (default behavior)\n  with `git hooks install`.\n\n- **Centralized**: Using the Git `core.hooksPath` **global** variable (set by\n  passing the `--centralized` parameter to the install script). All repositories\n  will use Githooks by default.\n\nRead the details about the differences between these 2 approaches\n[below](#global-hooks-or-no-global-hooks).\n\n### Install Mode - Manual\n\n**This is the default installation mode.**\n\nIn this mode, you decide yourself when to use Githooks in a repository simply by\ndoing one of the following with the same effect:\n\n- Run `git hooks install` or `git hooks uninstall` to install run wrappers\n  explicitly.\n\nThis means that Githooks might not run if you forget to install the hooks.\n\nTo install Githooks on your system, simply execute `cli installer`. It will\nguide you through the installation process. Check the `cli installer --help` for\navailable options. Some of them are described below:\n\nIts advised to only install Githooks for a selection of the supported hooks by\nusing `--maintained-hooks` as\n\n```shell\ncurl -sL https://raw.githubusercontent.com/gabyx/githooks/main/scripts/install.sh | bash -s -- -- \\\n    --maintained-hooks \"!all, pre-commit, pre-merge-commit, prepare-commit-msg, commit-msg, post-commit\" \\\n    --maintained-hooks \"pre-rebase, post-checkout, post-merge, pre-push\"\n```\n\nYou can still overwrite selectively for a repository by\n[installing another set of hooks](#installing-or-removing-run-wrappers). Missing\nGit LFS hooks will always be placed if necessary.\n\nIf you want, you can try out what the script would do first, without changing\nanything by using:\n\n```shell\ncurl -sL https://raw.githubusercontent.com/gabyx/githooks/main/scripts/install.sh | bash -s -- -- \\\n    --dry-run\n```\n\nOptionally, you can also pass the hooks directory to which you want to install\nthe Githooks run-wrappers by appending `--hook-dir \u003cpath\u003e` to the command above,\nfor example:\n\n```shell\ncurl -sL https://raw.githubusercontent.com/gabyx/githooks/main/scripts/install.sh | bash -s -- -- \\\n    --hooks-dir /home/public/myhooks\n```\n\n### Install Mode - Centralized Hooks\n\nYou have the option to install Githooks centralized which will use the\nrun-wrappers globally by setting the global `core.hooksPath` additionally. For\nthis, run the command below.\n\n```shell\ncurl -sL https://raw.githubusercontent.com/gabyx/githooks/main/scripts/install.sh | bash -s -- -- \\\n    --centralized\n```\n\n### Install from different URL and Branch\n\nIf you want to install from another Git repository (e.g. from your own or your\ncompanies fork), you can specify the repository clone url as well as the branch\nname (default: `main`) when installing with:\n\n```shell\ncurl -sL https://raw.githubusercontent.com/gabyx/githooks/main/scripts/install.sh | bash -s -- -- \\\n    --clone-url \"https://server.com/my-githooks-fork.git\" \\\n    --clone-branch \"release\"\n```\n\nThe installer always maintains a Githooks clone inside `\u003cinstallDir\u003e/release`\nfor its automatic update logic. The specified custom clone URL and branch will\nthen be used for further updates in the above example (see\n[update mechanics](#update-mechanics)).\n\nBecause the installer **always** downloads the latest release (here from another\nURL/branch), it needs deploy settings to know where to get the binaries from.\nEither your fork has setup these settings in their Githooks release (you\nhopefully downloaded) already or you can specify them by using\n`--deploy-api \u003ctype\u003e` or the full settings file `--deploy-settings \u003cfile\u003e`. The\n`\u003ctype\u003e` can either be `gitea` ( or `github` which is not needed since it can be\nauto-detected from the URL) and it will automatically download and **verify**\nthe binaries over the implemented API. Credentials will be collected over\n[`git credential`](https://git-scm.com/docs/cli/git-credential) to access the\nAPI. [@todo].\n\n### Use in CI\n\nThe installation depends on how you use Githooks in CI. The general approach is\nto run functionality or hooks over Githooks in CI only containerized. Doing it\nwithout containerization will be brittle and non-robust and requires you to have\nall needed tools installed in the running environment, also potentially the\ntools used in shared hook repositories.\n\nThere are generally two scenarios how you would use Githooks in CI.:\n\n1. Run functionality in hook repositories (local and shared repos): This can be\n   done by using `git hooks exec --containerized ...`. The following\n\n   ```shell\n   git hooks exec --containerized \\\n      ns:githooks-shell/scripts/check-shell-all.yaml -- --force --dir \".\"\n   ```\n\n   would run the config `scripts/check-shell-all.yaml`\n   ([see hook run configuration](#hook-run-configuration)) from the hook\n   repository [`githooks-shell`](https://github.com/gabyx/githooks-shell.git)\n   containerized.\n\n2. Run hooks containerized directly, e.g. pre-commit over a crafted\n   `git commit`, to check all staged files in that commit with all hooks\n   specified in the respective repository. This repository does exactly that in\n   [`tests/test-lint.sh`](tests/test-lint.sh).\n\n3. Run 1. or 2. but with `nix-shell` support if hooks are setup like this. No\n   containers needed, no nested container troubles. **Not implemented yet**.\n\n**Warnings:** Running a containerized hook or script in CI might mean that a\ncontainer starts as a nested container since your CI already uses a top-level\ncontainer which itself has access to `docker` or `podman`. Nested containers are\nkind of\n[tricky and mind-boggling](https://github.com/gabyx/container-nesting.git).\nGithooks will launch a container with two mounts\n\n- the workspace (the repository on which Githooks runs, e.g. `/data/repo`) bind\n  mounted to `/mnt/workspace` and\n- the shared hook repository (`~/.githooks/shared`) bind mounted to\n  `/mnt/shared`,\n\ninside the hook container.\n\nThese mounts can be influenced with the env. variable\n`GITHOOKS_CONTAINER_RUN_CONFIG_FILE`, see below.\n\n##### Nested Containers\n\nSome nomenclature for the next explanations: the host is considered a VM (your\nCI instance) and the top-level container `T` is your CI container you started on\nthis VM for a dedicated CI job (`C` has access to a container manager). The\nnested container `N` is a launched container from Githooks.\n\nAdjusting the mounts is needed because for the nested container `N` (inside\n`C`), the mounts **might** not work because the source paths (e.g. `/data/repo`\nin `-v /data/repo:/mnt/workspace`) are interpreted where the container manager\nservice runs. E.g. for `docker`, when you mount the `docker` socket into the\ntop-level container to have connect to the docker instance (running on the host,\n**generally not the best security practice**!), then that would be the host and\nthe paths would not exist (e.g. there is no `/data/repo` on the host). If you\nhave a full container manager (`podman` preferred) inside container `T`, it is\ndifferent. In that case, the mount paths are interpreted inside container `T`.\nThe mounts might still not work because the paths cannot be mounted further into\na nested container `N` due to restrictions what you can mount to nested\ncontainers - if the path comes from a bind mount from the host (VM) into `C` it\ndoes not work (AFAIK). In that case you can workaround this by\n`GITHOOKS_CONTAINER_RUN_CONFIG_FILE` which is the path to a YAML file which\nmodifies the mounts:\n\n```yaml\nversion: 1\n\n# Tell Githooks where the workspace will be in the nested container.\n# (optional, default `/mnt/workspace`)\nworkspace-path-dest: /tmp/ci-job-1/build/repo\n# Tell Githooks where the shared repository checkouts are in the nested container.\n# (optional, default: `/mnt/shared`)\nshared-path-dest: /tmp/ci-job-1/githooks-install/.githooks/shared\n\n# Do not auto-mount the workspace (bind mount), do it yourself with args.\n# (optional, default: true)\nauto-mount-workspace: false\n# Do not auto-mount the shared (bind mount), do it yourself with args.\n# (optional, default: true)\nauto-mount-shared: false\n\n# Additional arguments to `docker run` or `podman run`.\nargs: [\"-v\", \"gh-test-tmp:/tmp\"]\n```\n\nThe above will mount a volume `gh-test-tmp` volume to `/tmp` in `N` where\nGithooks will find the workspace under `/tmp/ci-job-1/build/repo` and the shared\nrepository checkouts in `/tmp/ci-job-1/githooks-install/.githooks/shared`.\n\n**Note**: You can use whatever `docker run` or `podman run` accepts. To mention\nis `--volumes-from` where you can mount the same volumes from another containers\nid.\n\n### Gitlab Demo\n\nThe repository [Markdown2PDF](https://github.com/gabyx/RsMarkdown2PDF-Service)\ncontains a CI setup in\n[`.gitlab/pipeline.yaml`](https://github.com/gabyx/RsMarkdown2PDF-Service/blob/ff2f97188742c6908d00dd05c6f30e16267239e9/.gitlab/pipeline.yaml#L31)\nfor Gitlab\n\nFor Gitlab this boils down to following pipeline step which uses dockerized\nhooks:\n\n```yaml\nformat:\n  stage: \u003cyour-stage-name\u003e\n  image: docker:24\n  rules:\n    - *defaults-rules\n  services:\n    - docker:24-dind\n  variables:\n    GITHOOKS_INSTALL_PREFIX: \"$CI_BUILDS_DIR/githooks\"\n  script:\n    - apk add git jq curl bash just findutils parallel\n    - just format\n```\n\nwhere `just format` will call the following function:\n\n```shell\nfunction ci_setup_githooks() {\n    mkdir -p \"$GITHOOKS_INSTALL_PREFIX\"\n\n    printInfo \"Install Githooks.\"\n    curl -sL \"https://raw.githubusercontent.com/gabyx/githooks/main/scripts/install.sh\" |\n        bash -s -- -- --non-interactive --prefix \"$GITHOOKS_INSTALL_PREFIX\"\n\n    git hooks config enable-containerized-hooks --global --set\n\n    printInfo \"Pull all shared Githooks repositories.\"\n    git hooks shared update\n}\n```\n\nwhich then enables you to call side-car scripts in the hook repository, e.g. as\ndemonstrated which will run over containers the same as in non-CI use cases:\n\n```shell\nfunction run_format_shared_hooks() {\n    printInfo \"Run all formats scripts in shared hook repositories.\"\n    git hooks exec --containerized \\\n        ns:githooks-shell/scripts/format-shell-all.yaml -- --force --dir \".\"\n\n    git hooks exec --containerized \\\n        ns:githooks-configs/scripts/format-configs-all.yaml -- --force --dir \".\"\n\n    git hooks exec --containerized \\\n        ns:githooks-docs/scripts/format-docs-all.yaml -- --force --dir \".\"\n\n    git hooks exec --containerized \\\n        ns:githooks-python/scripts/format-python-all.yaml -- --force --dir \".\"\n}\n```\n\n### No Installation\n\nYou can use this hook manager also without a global installation. For that you\ncan clone this repository anywhere (e.g. `\u003crepoPath\u003e`) and build the executables\nwith Go by running `githooks/scripts/build.sh --prod`. You can then use the\nhooks by setting `core.hooksPath` (in any suitable Git config) to the checked in\nrun-wrappers in `\u003crepoPath\u003e/hooks` like so:\n\n```shell\ngit clone https://github.com/gabyx/githooks.git githooks\ncd githooks\ngithooksRepo=$(pwd)\nscripts/build.sh\n```\n\nThen, to globally enable them for every repo:\n\n```shell\ngit config --global core.hooksPath \"$gihooksRepo/hooks\"\n```\n\nor locally enable them for a single repo only:\n\n```shell\ncd repo\ngit config --local core.hooksPath \"$githooksRepo/hooks\"\n```\n\n### Non-Interactive Installation\n\nYou can also run the installation in **non-interactive** mode with the command\nbelow. This will determine an appropriate template directory (detect and use the\nexisting one, or use the one passed by `--template-dir`, or use a default one),\ninstall the hooks automatically into this directory, and enable periodic update\nchecks.\n\nThe global install prefix defaults to `${HOME}` but can be changed by using the\noptions `--prefix \u003cinstallPrefix\u003e`:\n\n```shell\ncurl -sL https://raw.githubusercontent.com/gabyx/githooks/main/scripts/install.sh | bash -s -- -- \\\n    --non-interactive [--prefix \u003cinstallPrefix\u003e]\n```\n\nIt's possible to specify which template directory should be used, by passing the\n`--template-dir \u003cdir\u003e` parameter, where `\u003cdir\u003e` is the directory where you wish\nthe templates to be installed.\n\n```shell\ncurl -sL https://raw.githubusercontent.com/gabyx/githooks/main/scripts/install.sh | bash -s -- -- \\\n    --template-dir \"/home/public/.githooks-templates\"\n```\n\nBy default the script will install the hooks into the `~/.githooks/templates/`\ndirectory.\n\n### Install on the Server\n\nOn a server infrastructure where only _bare_ repositories are maintained, it is\nbest to maintain only server hooks. This can be achieved by installing with:\n\n```shell\ncurl -sL https://raw.githubusercontent.com/gabyx/githooks/main/scripts/install.sh | bash -s -- -- \\\n    --maintained-hooks \"server\"\n```\n\nThe global template directory then **only** contains the following run-wrappers\nfor Githooks:\n\n- `pre-push`\n- `pre-receive`\n- `update`\n- `post-receive`\n- `post-update`\n- `reference-transaction`\n- `push-to-checkout`\n- `pre-auto-gc`\n\nwhich get deployed with `git init` or `git clone` automatically. See also the\n[setup for bare repositories](#setup-for-bare-repositories).\n\n#### Setup for Bare Repositories\n\nIf you want to use Githooks with bare repositories on a server, you should setup\nthe following to ensure smooth operations (see [user prompts](#user-prompts)):\n\n```shell\ncd bareRepo\n# Install Githooks into this bare repository\ngit hooks install\n\n# Automatically accept changes to all existing and new\n# hooks in the current repository.\n# This makes the fatal trust prompt pass.\ngit hooks config trust-all-hooks --accept\n\n# Don't do global automatic updates, since the Githooks updater\n# might get invoked in parallel on a server.\ngit hooks config update --disable-check\n```\n\nNote: A user cannot change bare repository Githooks by pushing changes to a bare\nrepository on the server. If you use shared hook repositories in you bare\nrepository, you might consider disabling shared hooks updates by\n[`git hooks config disable-shared-hooks-update --set`](docs/cli/git_hooks_config_disable-shared-hooks-update).\n\n### Global Hooks or No Global Hooks\n\n#### Manual: Use Githooks Selectively\n\nIn this approach, the install script installs the hook run-wrapper into a common\nlocation (`~/.githooks/templates/` by default).\n\nTo make Githooks available inside a repository, you must install it with\n\n```shell\ncd repo\ngit hooks install\n```\n\nwhich will simply set the `core.hooksPath` to the common location where Githooks\nmaintains its run-wrappers. If you want a partial maintained hooks set with\n`git hooks install --maintained-hooks ...`, it will switch to install Githooks\nrun-wrappers inside this sole repository.\n\nYou have the possibility to install the Githooks run-wrappers into a Git\ntemplate directory (e.g. `GIT_TEMPLATE_DIR` or `init.templateDir` or the default\nGit template directory from the Git install) by specifying\n`--hooks-dir-use-template-dir`. This option is discouraged and only available\nfor backward compatibility and is not really needed and can be covered with the\nbelow `centralized` install mode.\n\n#### Centralized: Use Githooks For All Repositories\n\nIn this approach, the install script installs the hook run-wrapper into a common\nlocation (`~/.githooks/templates/` by default) and sets the global\n`core.hooksPath` variable to that location. Git will then, for all relevant\nactions, check the `core.hooksPath` location, instead of the default\n`${GIT_DIR}/hooks` location.\n\nThis approach works more like a _blanket_ solution, where **all\nrepositories**\u003cspan id=\"a2\"\u003e[\u003csup\u003e2\u003c/sup\u003e](#2)\u003c/span\u003e will start using the\nGithooks run-wrappers (thus launching Githooks), regardless of their location.\n\n**\u003cspan id=\"2\"\u003e\u003csup\u003e2\u003c/sup\u003e\u003c/span\u003e[⏎](#a2) Note:** It is possible to override\nthe behavior for a specific repository, by setting a local `core.hooksPath`\nvariable with value `${GIT_DIR}/hooks`, which will revert Git back to its\ndefault behavior for that specific repository. You don't need to initialize\n`git lfs install`, because they presumably be already in `${GIT_DIR}/hooks` from\nany `git clone/init`. **This does NOT work when using this inside a Git\nworktree**.\n\n### Updates\n\nYou can update the Githooks any time by running\n\n```shell\ngit hooks update\n```\n\nor one of the install commands above with `--update`.\n\nIt then downloads the binaries (GPG signed + checksummed) and dispatches to the\nnew installer to install the new version. It will update itself and simply\noverwrite the template run-wrappers with the new ones, and if you opt-in to\ninstall into existing or registered local repositories, those will get\noverwritten too.\n\n#### Automatic Update Checks\n\nYou can also enable automatic update checks during the installation, that is\nexecuted **once a day after a successful commit**. It checks for a new version\nwhere you can then call `git hooks update` your-self (\\*previous to version `3`\nthis was automatic which was removed).\n\nAutomatic updates can be enabled or disabled at any time by running the command\nbelow.\n\n```shell\n# enable with:\n$ git hooks update --enable-check # `Config: githooks.updateCheckEnabled`\n\n# disable with:\n$ git hooks update --disable-check\n```\n\n#### Update Mechanics\n\nThe update mechanism works by tracking the tags on the Git branch (chosen at\ninstall time) which is checked out in `\u003cinstallDir\u003e/release`. Normally, if there\nare new tags (versions) available, the newest tag (version) is installed.\nHowever, [prerelease version](https://semver.org) tags (e.g. `v1.0.0-rc1`) are\ngenerally skipped. You can disable this behavior by setting the global Git\nconfig value `githooks.updateCheckUsePrerelease = true`. Major version updates\nare **never** automatically installed an need the consent of the user.\n\nIf the annotated version tag or the commit message it points to\n(`git tag -l --format=%(contents) \u003ctag\u003e`) contains a trailing header which\nmatches the regex `^Update-NoSkip: *true`, than this version **will not be\nskipped**. This feature enables to enforce an update to a specific version. In\nsome cases this is useful (serialization changes etc.).\n\nThe single-line commit trailers `^Update-Info: *(.*)` (multiple ones allowed) on\nthe annotated tag is used to assemble a small changelog during update, which is\npresented to the user. The single line can contain important information/links\nto relevant fixes and changes.\n\nYou can also check for updates at any time by executing\n[`git hooks update`](docs/cli/git_hooks_update.md) or using\n[`git hooks config update-check [--enable|--disable]`](/docs/cli/git_hooks_config_update-check.md)\ncommand to enable or disable the automatic update checks.\n\n## Uninstalling\n\nIf you want to get rid of this hook manager, you can execute the uninstaller\n`\u003cinstallDir\u003e/bin/uninstaller` by\n\n```shell\ngit hooks uninstaller\n```\n\nor\n\n```shell\ncurl -sL https://raw.githubusercontent.com/gabyx/githooks/main/scripts/install.sh | bash -s -- --uninstall\n```\n\nThis will delete the run-wrappers installed in the template directory,\noptionally the installed hooks from the existing local repositories, and\nreinstates any previous hooks that were moved during the installation.\n\n## YAML Specifications\n\nYou can find YAML examples for hook ignore files `.ignore.yaml` and shared hooks\nconfig files `.shared.yaml` [here](docs/yaml-specs.md).\n\n## Migration\n\nMigrating from the `sh` [implementation here](https://github.com/gabyx/githooks)\nis easy, but unfortunately we do not yet provide an migration option during\ninstall (PRs welcome) to take over Git configuration values and other not so\nimportant settings.\n\nHowever, you can take the following steps for your old `.shared` and `.ignore`\nfiles inside your repositories to make them work directly with a new install:\n\n1. Convert all entries in `.ignore` files to a pattern in a YAML file\n   `.ignore.yaml` (see [specs](#yaml-specifications)). Each old glob pattern\n   needs to be prepended by `**/` (if not already existing) to make it work\n   correctly (because of namespaces), e.g. a pattern `.*md` becomes `**/.*md`.\n   Disable shared repositories in the old version need to be reconfigured, by\n   using ignore patterns. Check if the ignore is working by running\n   `[git hooks list](docs/cli/git_hooks_list.md)`.\n\n2. Convert all entries in `.shared` files to an url in a YAML file\n   `.shared.yaml` [here](/docs/yaml-specs.md).\n\n3. It's heartly recommended to **first** uninstall the old version, to get rid\n   of any old settings.\n\n4. Install the new version.\n\nTrusted hooks will be needed to be trusted again. To port Git configuration\nvariables use the file `githooks/hooks/gitconfig.go` which contains all used Git\nconfig keys.\n\n## Dialog Tool\n\nGithooks provides it's own **platform-independent dialog tool `dialog`** which\nis located in `\u003cinstallDir\u003e/bin`. It enables the use of **native** GUI dialogs\nsuch as:\n\n- options dialog\n- entry dialog\n- file save and file selection dialogs\n- message dialogs\n- system notifications\n\ninside of hooks and scripts.\n**[See the screenshots.](docs/dialog-screenshots/Readme.md)**\n\n_Why another tool?:_ At the moment of writing there exists no proper\nplatform-independent GUI dialog tool which is **bomb-proof in it's output and\nexit code behavior**. This tool should really enable proper and safe usage\ninside hooks and other scripts. You can even report the output in `json` format\n(use option `--json`). It was heavily inspired by\n[zenity](https://github.com/ncruces/zenity) and features some of the same\nproperties (no `cgo`, cancellation through `context`). You can use this dialog\ntool independently of Githooks.\n\n**Test it out!** 🎉: Please refer to the\n[documentation of the tool](/docs/dialog/githooks-dialog.md).\n\n### Build From Source\n\n```shell\ncd githooks\ngo mod download\ngo mod vendor\ncd githooks/apps/dialog\ngo build ./...\n./dialog --help\n```\n\n### Dependencies\n\nThe dialog tool has the following dependencies:\n\n- `macOS` : `osascript` which is provided by the system directly.\n- `Unix` : A dialog tool such as `zenity` (preferred), `qarma` or `matedialog`.\n- `Windows`: Common Controls 6 which is provided by the system directly.\n\n## Tests and Debugging\n\nRunning the integration tests with Docker:\n\n```shell\ncd githooks\nbash tests/test-alpine.sh # and other 'test-XXXX.sh' files...\n```\n\nRun certain tests only:\n\n```shell\nbash tests/test-alpine.sh --seq {001..120}\nbash tests/test-alpine.sh --seq 065\n```\n\n### Debugging in the Dev Container\n\nThere is a docker development container for debugging purposes in\n`.devcontainer`. VS Code can be launched in this remote docker container with\nthe extension `ms-vscode-remote.remote-containers`. Use\n`Remote-Containers: Open Workspace in Container...` and\n`Remote-Containers: Rebuild Container`.\n\nOnce in the development container: You can launch the VS Code tasks:\n\n- `[Dev Container] go-delve-installer`\n- etc...\n\nwhich will start the `delve` debugger headless as a server in a terminal. You\ncan then attach to the debug server with the debug configuration\n`Debug Go [remote delve]`. Set breakpoints in the source code to trigger them.\n\n### Todos\n\n- Finish deploy settings implementation for Gitea and others.\n\n## Changelog\n\n### Version v2.x.x\n\nFor upgrading from `v1.x.x` to `v2.x.x` consider the\n[braking change documentation](docs/changes/Braking-Changes-v2.md).\n\n## FAQ\n\n- **Shell on Windows shows weird characters:** Githooks outputs UTF-8 characters\n  (emojis etc.). Make sure you have the UTF-8 codepage active by doing\n  `chcp.com 65001` (either in `cmd.exe` or `git-bash.exe`, also from an\n  integrated terminal in VS Code). You can make it permanent by putting this\n  into the startup scripts of your shell, e.g. (`.bashrc`). Consider using\n  Windows Terminal.\n\n## Acknowledgements\n\n- [Original Githooks implementation in `sh`](http://github.com/rycus86/githooks)\n  by Viktor Adam.\n\n## Authors\n\n- Gabriel Nützi (`Go` implementation)\n- Viktor Adam (Initial `sh` implementation)\n- Matthijs Kooijman (suggestions \u0026 discussions)\n- and community.\n\n## Support \u0026 Donation\n\nWhen you use Githooks and you would like to say thank you for its development\nand its future maintenance: I am happy to receive any donation which will be\ndistributed equally among all contributors.\n\n[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bs/webscr?cmd=_s-xclick\u0026hosted_button_id=6S6BJL4GSMSG4)\n\n## License\n\nMIT\n","funding_links":["https://github.com/sponsors/gabyx","https://www.paypal.com/cgi-bs/webscr?cmd=_s-xclick\u0026hosted_button_id=6S6BJL4GSMSG4"],"categories":["Version Control","版本控制"],"sub_categories":["Utility/Miscellaneous","实用程序/Miscellaneous","HTTP Clients"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgabyx%2Fgithooks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgabyx%2Fgithooks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgabyx%2Fgithooks/lists"}