Introduction
Lefthook is a Git hooks manager. This documentation provides the reference for installing, configuring and using the lefthook.
Maybe you are looking for
-
Installation instructions to install lefthook to your system or just project.
-
Examples of lefthook common usage.
-
Configuration with details explanation of lefthook options.
❓If you have a question or found a mistake in the documentation, please create a new discussion. Small contributions help maintaining the quality of the project.
Install lefthook
Choose your fighter:
- Ruby
- Node.js
- Go
- Python
- Swift
- Scoop
- Homebrew
- Winget
- Snap
- Debian-based distro
- RPM-based distro
- Alpine
- Arch Linux
- Manual
Ruby
gem install lefthook
Troubleshooting
If you see the error lefthook: command not found
you need to check your $PATH. Also try to restart your terminal.
Node.js
Lefthook is available on NPM in the following flavors:
-
lefthook that will install the proper binary:
npm install lefthook --save-dev # or yarn: yarn add -D lefthook
-
@evilmartians/lefthook with pre-bundled binaries for all architectures:
npm install @evilmartians/lefthook --save-dev # or yarn: yarn add -D @evilmartians/lefthook
-
@evilmartians/lefthook-installer that will fetch binary file on installation:
npm install @evilmartians/lefthook-installer --save-dev # or yarn: yarn add -D @evilmartians/lefthook-installer
Note: If you use
pnpm
package manager make sure you setside-effects-cache = false
in your .npmrc, otherwise the postinstall script of the lefthook package won't be executed and hooks won't be installed.
Go
go install github.com/evilmartians/lefthook@latest
Python
python -m pip install --user lefthook
Swift
You can find the Swift wrapper plugin here.
Utilize lefthook in your Swift project using Swift Package Manager:
.package(url: "https://github.com/csjones/lefthook-plugin.git", exact: "1.9.3"),
Or, with mint:
mint run csjones/lefthook-plugin
Scoop for Windows
scoop install lefthook
Homebrew for MacOS and Linux
brew install lefthook
Winget for Windows
winget install evilmartians.lefthook
Snap for Linux
snap install --classic lefthook
APT packages for Debian/Ubuntu Linux
curl -1sLf 'https://dl.cloudsmith.io/public/evilmartians/lefthook/setup.deb.sh' | sudo -E bash
sudo apt install lefthook
See all instructions: https://cloudsmith.io/~evilmartians/repos/lefthook/setup/#formats-deb
RPM packages for CentOS/Fedora Linux
curl -1sLf 'https://dl.cloudsmith.io/public/evilmartians/lefthook/setup.rpm.sh' | sudo -E bash
sudo yum install lefthook
See all instructions: https://cloudsmith.io/~evilmartians/repos/lefthook/setup/#repository-setup-yum
APK packages for Alpine
sudo apk add --no-cache bash curl
curl -1sLf 'https://dl.cloudsmith.io/public/evilmartians/lefthook/setup.alpine.sh' | sudo -E bash
sudo apk add lefthook
See all instructions: https://cloudsmith.io/~evilmartians/repos/lefthook/setup/#formats-alpine
AUR for Arch
You can install lefthook package from AUR.
yay -S lefthook
Manuall installation with prebuilt executable
Or take it from binaries and install manually.
- Download the executable for your OS and Arch
- Put the executable under the $PATH (for unix systems)
Usage
Commands
tip
Use
lefthook help
orlefthook <command> -h/--help
to discover available commands and their options
lefthook install
lefthook install
creates an empty lefthook.yml
if a configuration file does not exist and updates git hooks to use lefthook. Run lefthook install
after cloning the git repo.
note
NPM package
lefthook
installs the hooks in a postinstall script automatically.
lefthook uninstall
lefthook uninstall
clears git hooks affected by lefthook. If you have lefthook installed as an NPM package you should remove it manually.
lefthook add
lefthook add pre-commit
will create a file .git/hooks/pre-commit
. This is the same lefthook does for install
command but you don't need to create a configuration first.
To use custom scripts as hooks create the required directories with lefthook add pre-commit --dirs
.
Example
$ lefthook add pre-push --dirs
Describe pre-push commands in lefthook.yml
:
pre-push:
scripts:
"audit.sh":
runner: bash
Edit the script:
$ vim .lefthook/pre-push/audit.sh
...
Run git push
and lefthook will run bash audit.sh
as a pre-push hook.
lefthook run
lefthook run
executes the commands and scripts configured for a given hook. Generated hooks call lefthook run
implicitly.
Example
# lefthook.yml
pre-commit:
commands:
lint:
run: yarn lint --fix
test:
commands:
js-test:
run: yarn test
Install the hook.
$ lefthook install
Run test
.
$ lefthook run test # will run 'yarn test'
Commit changes.
$ git commit # will run pre-commit hook ('yarn lint --fix')
Or run manually also
$ lefthook run pre-commit
You can also specify a flag to run only some commands:
$ lefthook run pre-commit --commands lint
and optionally run either on all files (any {staged_files}
placeholder acts as {all_files}
) or a list of files:
$ lefthook run pre-commit --all-files
$ lefthook run pre-commit --file file1.js --file file2.js
(if both are specified, --all-files
is ignored)
lefthook version
lefthook version
prints the current binary version. Print the commit hash with lefthook version --full
Example
$ lefthook version --full
1.1.3 bb099d13c24114d2859815d9d23671a32932ffe2
lefthook self-update
lefthook self-update
updates the binary with the latest lefthook release on Github. This command is available only if you install lefthook from sources or download the binary from the Github Releases. For other ways use package-specific commands to update lefthook.
ENV variables
ENV variables can be used to control lefthook behavior. Most of them have the alternative CLI or config options.
LEFTHOOK
Use LEFTHOOK=0 git ...
or LEFTHOOK=false git ...
to disable lefthook when running git commands.
Example
LEFTHOOK=0 git commit -am "Lefthook skipped"
LEFTHOOK_EXCLUDE
Use LEFTHOOK_EXCLUDE={list of tags or command names to be excluded}
to skip some commands or scripts by tag or name (for commands only). See the exclude_tags
configuration option for more details.
Example
LEFTHOOK_EXCLUDE=ruby,security,lint git commit -am "Skip some tag checks"
LEFTHOOK_OUTPUT
Use LEFTHOOK_OUTPUT={list of output values}
to specify what to print in your output. You can also set LEFTHOOK_OUTPUT=false
to disable all output except for errors. Refer to the output
configuration option for more details.
Example
$ LEFTHOOK_OUTPUT=summary lefthook run pre-commit
summary: (done in 0.52 seconds)
✔️ lint
LEFTHOOK_QUIET
You can skip some outputs printed by lefthook by setting the LEFTHOOK_QUIET
environment variable. Provide a list of output types to be skipped. See the skip_output
configuration option for more details.
Example
$ LEFTHOOK_QUIET=meta,execution lefthook run pre-commit
EXECUTE > lint
SUMMARY: (done in 0.01 seconds)
🥊 lint
LEFTHOOK_VERBOSE
Set LEFTHOOK_VERBOSE=1
or LEFTHOOK_VERBOSE=true
to enable verbose printing.
LEFTHOOK_BIN
Set LEFTHOOK_BIN
to a location where lefthook is installed to use that instead of trying to detect from the it the PATH or from a package manager.
Useful for cases when:
- lefthook is installed multiple ways, and you want to be explicit about which one is used (example: installed through homebrew, but also is in Gemfile but you are using a ruby version manager like rbenv that prepends it to the path)
- debugging and/or developing lefthook
NO_COLOR
Set NO_COLOR=true
to disable colored output in lefthook and all subcommands that lefthook calls.
CLICOLOR_FORCE
Set CLICOLOR_FORCE=true
to force colored output in lefthook and all subcommands.
Tips
Small tips for better experience with lefthook
Local config
Use lefthook-local.yml
to overwrite or extend options from the main config. (Don't forget to add this file to .gitignore
)
Disable lefthook in CI
When using NPM package lefthook
set CI=true
in your CI (if it does not set automatically). When CI=true
is set lefthook NPM package won't install the hooks in the postinstall script.
Commitlint example
Let's create a bash script to check conventional commit status .lefthook/commit-msg/commitlint.sh
:
echo $(head -n1 $1) | npx commitlint --color
Now we can ask lefthook to run our bash script by adding this code to
lefthook.yml
file:
# lefthook.yml
commit-msg:
scripts:
"commitlint.sh":
runner: bash
When you try to commit git commit -m "haha bad commit text"
script commitlint.sh
will be executed. Since commit text doesn't match the default config or custom config that you setup for commitlint
, the process will be interrupted.
Parallel execution
You can enable parallel execution if you want to speed up your checks. Lets imagine we have the following rules to lint the whole project:
bundle exec rubocop --parallel && \
bundle exec danger && \
yarn eslint --ext .es6 app/assets/javascripts && \
yarn eslint --ext .es6 test/javascripts && \
yarn eslint --ext .es6 plugins/**/assets/javascripts && \
yarn eslint --ext .es6 plugins/**/test/javascripts && \
yarn eslint app/assets/javascripts test/javascripts
Rewrite it in lefthook custom group. We call it lint
:
# lefthook.yml
lint:
parallel: true
commands:
rubocop:
run: bundle exec rubocop --parallel
danger:
run: bundle exec danger
eslint-assets:
run: yarn eslint --ext .es6 app/assets/javascripts
eslint-test:
run: yarn eslint --ext .es6 test/javascripts
eslint-plugins-assets:
run: yarn eslint --ext .es6 plugins/**/assets/javascripts
eslint-plugins-test:
run: yarn eslint --ext .es6 plugins/**/test/javascripts
eslint-assets-tests:
run: yarn eslint app/assets/javascripts test/javascripts
Then call this group directly:
lefthook run lint
Concurrent files overrides
To prevent concurrent problems with read/write files try flock
utility.
# lefthook.yml
graphql-schema:
glob: "{Gemfile.lock,app/graphql/**/*}"
run: flock webpack/application/typings/graphql-schema.json yarn typings:update && git diff --exit-code --stat HEAD webpack/application/typings
frontend-tests:
glob: "**/*.js"
run: flock -s webpack/application/typings/graphql-schema.json yarn test --findRelatedTests {files}
frontend-typings:
glob: "**/*.js"
run: flock -s webpack/application/typings/graphql-schema.json yarn run flow focus-check {files}
Capture ARGS from git in the script
Example script for prepare-commit-msg
hook:
COMMIT_MSG_FILE=$1
COMMIT_SOURCE=$2
SHA1=$3
# ...
Git LFS support
If git-lfs binary is not installed and not required in your project, LFS hooks won't be executed, and you won't be warned about it.
Lefthook runs LFS hooks internally for the following hooks:
- post-checkout
- post-commit
- post-merge
- pre-push
Errors are suppressed if git LFS is not required for the project. You can use LEFTHOOK_VERBOSE
ENV to make lefthook show git LFS output.
Pass stdin to a command or script
When you need to read the data from stdin – specify use_stdin: true
. This option is good when you write a command or script that receives data from git using stdin (for the pre-push
hook, for example).
Using an interactive command or script
When you need to interact with user – specify interactive: true
. Lefthook will connect to the current TTY and forward it to your command's or script's stdin.
Config file name
Lefthook supports the following file names for the main config:
lefthook.yml
.lefthook.yml
lefthook.yaml
.lefthook.yaml
lefthook.toml
.lefthook.toml
lefthook.json
.lefthook.json
If there are more than 1 file in the project, only one will be used, and you'll never know which one. So, please, use one format in a project.
Lefthook also merges an extra config with the name lefthook-local
. All supported formats can be applied to this -local
config. If you name your main config with the leading dot, like .lefthook.json
, the -local
config also must be named with the leading dot: .lefthook-local.json
.
Options
assert_lefthook_installed
colors
no_tty
extends
min_version
output
skip_output
source_dir
source_dir_local
rc
remotes
- Git hook
assert_lefthook_installed
Default: false
When set to true
, fail (with exit status 1) if lefthook
executable can't be found in $PATH, under node_modules/, as a Ruby gem, or other supported method. This makes sure git hook won't omit lefthook
rules if lefthook
ever was installed.
colors
Default: auto
Whether enable or disable colorful output of Lefthook. This option can be overwritten with --colors
option. You can also provide your own color codes.
Example
Disable colors.
# lefthook.yml
colors: false
Custom color codes. Can be hex or ANSI codes.
# lefthook.yml
colors:
cyan: 14
gray: 244
green: '#32CD32'
red: '#FF1493'
yellow: '#F0E68C'
no_tty
Default: false
Whether hide spinner and other interactive things. This can be also controlled with --no-tty
option for lefthook run
command.
Example
# lefthook.yml
no_tty: true
extends
You can extend your config with another one YAML file. Its content will be merged. Extends for lefthook.yml
, lefthook-local.yml
, and remotes
configs are handled separately, so you can have different extends in these files.
You can use asterisk to make a glob.
Example
# lefthook.yml
extends:
- /home/user/work/lefthook-extend.yml
- /home/user/work/lefthook-extend-2.yml
- lefthook-extends/file.yml
- ../extend.yml
- projects/*/specific-lefthook-config.yml
The extends will be merged to the main configuration in your file. Here is the order of settings applied:
lefthook.yml
– main config fileextends
– configs specified in extends optionremotes
– configs specified in remotes optionlefthook-local.yml
– local config fileSo,
extends
override settings fromlefthook.yml
,remotes
overrideextends
, andlefthook-local.yml
can override everything.
min_version
If you want to specify a minimum version for lefthook binary (e.g. if you need some features older versions don't have) you can set this option.
Example
# lefthook.yml
min_version: 1.1.3
output
You can manage verbosity using the output
config. You can specify what to print in your output by setting these values, which you need to have
Possible values are meta,summary,success,failure,execution,execution_out,execution_info,skips
.
By default, all output values are enabled
You can also disable all output with setting output: false
. In this case only errors will be printed.
This config quiets all outputs except for errors.
output
is enabled if there is no skip_output
and LEFTHOOK_QUIET
.
Example
# lefthook.yml
output:
- meta # Print lefthook version
- summary # Print summary block (successful and failed steps)
- empty_summary # Print summary heading when there are no steps to run
- success # Print successful steps
- failure # Print failed steps printing
- execution # Print any execution logs
- execution_out # Print execution output
- execution_info # Print `EXECUTE > ...` logging
- skips # Print "skip" (i.e. no files matched)
You can also extend this list with an environment variable LEFTHOOK_OUTPUT
:
LEFTHOOK_OUTPUT="meta,success,summary" lefthook run pre-commit
skip_output
DEPRECATED This feature is deprecated and might be removed in future versions. Please, use
output
instead for managing verbosity.
You can manage the verbosity using the skip_output
config. You can set whether lefthook should print some parts of its output.
Possible values are meta,summary,success,failure,execution,execution_out,execution_info,skips
.
You can also disable all output with setting skip_output: true
. In this case only errors will be printed.
This config quiets all outputs except for errors.
Example
# lefthook.yml
skip_output:
- meta # Skips lefthook version printing
- summary # Skips summary block (successful and failed steps) printing
- empty_summary # Skips summary heading when there are no steps to run
- success # Skips successful steps printing
- failure # Skips failed steps printing
- execution # Skips printing any execution logs (but prints if the execution failed)
- execution_out # Skips printing execution output (but still prints failed commands output)
- execution_info # Skips printing `EXECUTE > ...` logging
- skips # Skips "skip" printing (i.e. no files matched)
You can also extend this list with an environment variable LEFTHOOK_QUIET
:
LEFTHOOK_QUIET="meta,success,summary" lefthook run pre-commit
source_dir
Default: .lefthook/
Change a directory for script files. Directory for script files contains folders with git hook names which contain script files.
Example of directory tree:
.lefthook/
├── pre-commit/
│ ├── lint.sh
│ └── test.py
└── pre-push/
└── check-files.rb
source_dir_local
Default: .lefthook-local/
Change a directory for local script files (not stored in VCS).
This option is useful if you have a lefthook-local.yml
config file and want to reference different scripts there.
rc
Provide an rc file, which is actually a simple sh
script. Currently it can be used to set ENV variables that are not accessible from non-shell programs.
Example
Use cases:
- You have a GUI program that runs git hooks (e.g., VSCode)
- You reference executables that are accessible only from a tweaked $PATH environment variable (e.g., when using rbenv or nvm, fnm)
- Or even if your GUI program cannot locate the
lefthook
executable :scream: - Or if you want to use ENV variables that control the executables behavior in
lefthook.yml
# An npm executable which is managed by nvm
$ which npm
/home/user/.nvm/versions/node/v15.14.0/bin/npm
# lefthook.yml
pre-commit:
commands:
lint:
run: npm run eslint {staged_files}
Provide a tweak to access npm
executable the same way you do it in your ~/
# lefthook-local.yml
# You can choose whatever name you want.
# You can share it between projects where you use lefthook.
# Make sure the path is absolute.
rc: ~/.lefthookrc
Or
# lefthook-local.yml
# If the path contains spaces, you need to quote it.
rc: '"${XDG_CONFIG_HOME:-$HOME/.config}/lefthookrc"'
In the rc file, export any new environment variables or modify existing ones.
# ~/.lefthookrc
# An nvm way
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
# An fnm way
export FNM_DIR="$HOME/.fnm"
[ -s "$FNM_DIR/fnm.sh" ] && \. "$FNM_DIR/fnm.sh"
# Or maybe just
PATH=$PATH:$HOME/.nvm/versions/node/v15.14.0/bin
# Make sure you updated git hooks. This is important.
$ lefthook install -f
Now any program that runs your hooks will have a tweaked PATH environment variable and will be able to get nvm
:wink:
remotes
You can provide multiple remote configs if you want to share yours lefthook configurations across many projects. Lefthook will automatically download and merge configurations into your local lefthook.yml
.
You can use extends
but the paths must be relative to the remote repository root.
If you provide scripts
in a remote config file, the scripts folder must also be in the root of the repository.
Note
The configuration from remotes
will be merged to the local config using the following priority:
- Local main config (
lefthook.yml
) - Remote configs (
remotes
) - Local overrides (
lefthook-local.yml
)
This priority may be changed in the future. For convenience, if you use remotes
, please don't configure any hooks.
git_url
A URL to Git repository. It will be accessed with privileges of the machine lefthook runs on.
Example
# lefthook.yml
remotes:
- git_url: git@github.com:evilmartians/lefthook
Or
# lefthook.yml
remotes:
- git_url: https://github.com/evilmartians/lefthook
ref
An optional branch or tag name.
Note: If you initially had
ref
option, ranlefthook install
, and then removed it, lefthook won't decide which branch/tag to use as a ref. So, if you added it once, please, use it always to avoid issues in local setups.
Example
# lefthook.yml
remotes:
- git_url: git@github.com:evilmartians/lefthook
ref: v1.0.0
refetch
Default: false
Force remote config refetching on every run. Lefthook will be refetching the specified remote every time it is called.
Example
# lefthook.yml
remotes:
- git_url: https://github.com/evilmartians/lefthook
refetch: true
refetch_frequency
Default: Not set
Specifies how frequently Lefthook should refetch the remote configuration. This can be set to always
, never
or a time duration like 24h
, 30m
, etc.
- When set to
always
, Lefthook will always refetch the remote configuration on each run. - When set to a duration (e.g.,
24h
), Lefthook will check the last fetch time and refetch the configuration only if the specified amount of time has passed. - When set to
never
or not set, Lefthook will not fetch from remote.
Example
# lefthook.yml
remotes:
- git_url: https://github.com/evilmartians/lefthook
refetch_frequency: 24h # Refetches once every 24 hours
WARNING If
refetch
is set totrue
, it overrides any setting inrefetch_frequency
.
configs
Default: [lefthook.yml]
An optional array of config paths from remote's root.
Example
# lefthook.yml
remotes:
- git_url: git@github.com:evilmartians/lefthook
ref: v1.0.0
configs:
- examples/ruby-linter.yml
- examples/test.yml
Example with multiple remotes merging multiple configurations.
# lefthook.yml
remotes:
- git_url: git@github.com:org/lefthook-configs
ref: v1.0.0
configs:
- examples/ruby-linter.yml
- examples/test.yml
- git_url: https://github.com/org2/lefthook-configs
configs:
- lefthooks/pre_commit.yml
- lefthooks/post_merge.yml
- git_url: https://github.com/org3/lefthook-configs
ref: feature/new
configs:
- configs/pre-push.yml
Git hook
Contains settings for the git hook (commands, scripts, skip rules, etc.). You can specify any Git hook or your own custom, e.g. test
Hook options
files
(global)
A custom git command for files to be referenced in {files}
template. See run
and files
.
If the result of this command is empty, the execution of commands will be skipped.
Example
# lefthook.yml
pre-commit:
files: git diff --name-only master # custom list of files
commands:
...
parallel
Default: false
Note: Lefthook runs commands and scripts sequentially by default
Run commands and scripts concurrently.
piped
Default: false
Note: Lefthook will return an error if both
piped: true
andparallel: true
are set
Stop running commands and scripts if one of them fail.
Example
# lefthook.yml
database:
piped: true # Stop if one of the steps fail
commands:
1_create:
run: rake db:create
2_migrate:
run: rake db:migrate
3_seed:
run: rake db:seed
follow
Default: false
Follow the STDOUT of the running commands and scripts.
Example
# lefthook.yml
pre-push:
follow: true
commands:
backend-tests:
run: bundle exec rspec
frontend-tests:
run: yarn test
Note: If used with
parallel
the output can be a mess, so please avoid setting both options totrue
exclude_tags
Tags or command names that you want to exclude. This option can be overwritten with LEFTHOOK_EXCLUDE
env variable.
Example
# lefthook.yml
pre-commit:
exclude_tags: frontend
commands:
lint:
tags: frontend
...
test:
tags: frontend
...
check-syntax:
tags: documentation
lefthook run pre-commit # will only run check-syntax command
Notes
This option is good to specify in lefthook-local.yml
when you want to skip some execution locally.
# lefthook.yml
pre-push:
commands:
packages-audit:
tags:
- frontend
- security
run: yarn audit
gems-audit:
tags:
- backend
- security
run: bundle audit
You can skip commands by tags:
# lefthook-local.yml
pre-push:
exclude_tags:
- frontend
skip
You can skip all or specific commands and scripts using skip
option. You can also skip when merging, rebasing, or being on a specific branch. Globs are available for branches.
Possible skip values:
rebase
- when in rebase git statemerge
- when in merge git statemerge-commit
- when current HEAD commit is the merge commitref: main
- when on amain
branchrun: test ${SKIP_ME} -eq 1
- whentest ${SKIP_ME} -eq 1
is successful (return code is 0)
Example
Always skipping a command:
# lefthook.yml
pre-commit:
commands:
lint:
skip: true
run: yarn lint
Skipping on merging and rebasing:
# lefthook.yml
pre-commit:
commands:
lint:
skip:
- merge
- rebase
run: yarn lint
Or
# lefthook.yml
pre-commit:
commands:
lint:
skip: merge
run: yarn lint
Skipping when your are on a merge commit:
# lefthook.yml
pre-push:
commands:
lint:
skip: merge-commit
run: yarn lint
Skipping the whole hook on main
branch:
# lefthook.yml
pre-commit:
skip:
- ref: main
commands:
lint:
run: yarn lint
test:
run: yarn test
Skipping hook for all dev/*
branches:
# lefthook.yml
pre-commit:
skip:
- ref: dev/*
commands:
lint:
run: yarn lint
test:
run: yarn test
Skipping hook by running a command:
# lefthook.yml
pre-commit:
skip:
- run: test "${NO_HOOK}" -eq 1
commands:
lint:
run: yarn lint
test:
run: yarn test
TIP
Always skipping is useful when you have a
lefthook-local.yml
config and you don't want to run some commands locally. So you just overwrite theskip
option for them to betrue
.# lefthook.yml pre-commit: commands: lint: run: yarn lint
# lefthook-local.yml pre-commit: commands: lint: skip: true
only
You can force a command, script, or the whole hook to execute only in certain conditions. This option acts like the opposite of skip
. It accepts the same values but skips execution only if the condition is not satisfied.
Note:
skip
option takes precedence overonly
option, so if you have conflicting conditions the execution will be skipped.
Example
Execute a hook only for dev/*
branches.
# lefthook.yml
pre-commit:
only:
- ref: dev/*
commands:
lint:
run: yarn lint
test:
run: yarn test
When rebasing execute quick linter but skip usual linter and tests.
# lefthook.yml
pre-commit:
commands:
lint:
skip: rebase
run: yarn lint
test:
skip: rebase
run: yarn test
lint-on-rebase:
only: rebase
run: yarn lint-quickly
commands
Commands to be executed for the hook. Each command has a name and associated run options.
Example
# lefthook.yml
pre-commit:
commands:
lint:
... # command options
Command options
run
skip
only
tags
glob
files
file_types
env
root
exclude
fail_text
stage_fixed
interactive
use_stdin
priority
run
This is a mandatory option for a command. This is actually a command that is executed for the hook.
You can use files templates that will be substituted with the appropriate files on execution:
{files}
- customfiles
command result.{staged_files}
- staged files which you try to commit.{push_files}
- files that are committed but not pushed.{all_files}
- all files tracked by git.{cmd}
- shorthand for the command fromlefthook.yml
.{0}
- shorthand for the single space-joint string of git hook arguments.{N}
- shorthand for the N-th git hook argument.
Example
Run yarn lint
on pre-commit
hook.
# lefthook.yml
pre-commit:
commands:
lint:
run: yarn lint
{files}
template
Run go vet
only on files listed with git ls-files -m
command with .go
extension.
# lefthook.yml
pre-commit:
commands:
govet:
files: git ls-files -m
glob: "*.go"
run: go vet {files}
{staged_files}
template
Run yarn eslint
only on staged files with .js
, .ts
, .jsx
, and .tsx
extensions.
# lefthook.yml
pre-commit:
commands:
eslint:
glob: "*.{js,ts,jsx,tsx}"
run: yarn eslint {staged_files}
{push_files}
template
If you want to lint files only before pushing them.
# lefthook.yml
pre-push:
commands:
eslint:
glob: "*.{js,ts,jsx,tsx}"
run: yarn eslint {push_files}
{all_files}
template
Simply run bundle exec rubocop
on all files with .rb
extension excluding application.rb
and routes.rb
files.
Note:
--force-exclusion
will applyExclude
configuration setting of Rubocop
# lefthook.yml
pre-commit:
commands:
rubocop:
tags:
- backend
- style
glob: "*.rb"
exclude:
- config/application.rb
- config/routes.rb
run: bundle exec rubocop --force-exclusion {all_files}
{cmd}
template
# lefthook.yml
pre-commit:
commands:
lint:
run: yarn lint
scripts:
"good_job.js":
runner: node
You can wrap it in docker runner locally:
# lefthook-local.yml
pre-commit:
commands:
lint:
run: docker run -it --rm <container_id_or_name> {cmd}
scripts:
"good_job.js":
runner: docker run -it --rm <container_id_or_name> {cmd}
Git arguments
Make sure commits are signed.
# lefthook.yml
# Note: commit-msg hook takes a single parameter,
# the name of the file that holds the proposed commit log message.
# Source: https://git-scm.com/docs/githooks#_commit_msg
commit-msg:
commands:
multiple-sign-off:
run: 'test $(grep -c "^Signed-off-by: " {1}) -lt 2'
Rubocop
If using {all_files}
with RuboCop, it will ignore RuboCop's Exclude
configuration setting. To avoid this, pass --force-exclusion
.
Quotes
If you want to have all your files quoted with double quotes "
or single quotes '
, quote the appropriate shorthand:
# lefthook.yml
pre-commit:
commands:
lint:
glob: "*.js"
# Quoting with double quotes `"` might be helpful for Windows users
run: yarn eslint "{staged_files}" # will run `yarn eslint "file1.js" "file2.js" "[strange name].js"`
test:
glob: "*.{spec.js}"
run: yarn test '{staged_files}' # will run `yarn eslint 'file1.spec.js' 'file2.spec.js' '[strange name].spec.js'`
format:
glob: "*.js"
# Will quote where needed with single quotes
run: yarn test {staged_files} # will run `yarn eslint file1.js file2.js '[strange name].spec.js'`
skip
You can skip all or specific commands and scripts using skip
option. You can also skip when merging, rebasing, or being on a specific branch. Globs are available for branches.
Possible skip values:
rebase
- when in rebase git statemerge
- when in merge git statemerge-commit
- when current HEAD commit is the merge commitref: main
- when on amain
branchrun: test ${SKIP_ME} -eq 1
- whentest ${SKIP_ME} -eq 1
is successful (return code is 0)
Example
Always skipping a command:
# lefthook.yml
pre-commit:
commands:
lint:
skip: true
run: yarn lint
Skipping on merging and rebasing:
# lefthook.yml
pre-commit:
commands:
lint:
skip:
- merge
- rebase
run: yarn lint
Or
# lefthook.yml
pre-commit:
commands:
lint:
skip: merge
run: yarn lint
Skipping when your are on a merge commit:
# lefthook.yml
pre-push:
commands:
lint:
skip: merge-commit
run: yarn lint
Skipping the whole hook on main
branch:
# lefthook.yml
pre-commit:
skip:
- ref: main
commands:
lint:
run: yarn lint
test:
run: yarn test
Skipping hook for all dev/*
branches:
# lefthook.yml
pre-commit:
skip:
- ref: dev/*
commands:
lint:
run: yarn lint
test:
run: yarn test
Skipping hook by running a command:
# lefthook.yml
pre-commit:
skip:
- run: test "${NO_HOOK}" -eq 1
commands:
lint:
run: yarn lint
test:
run: yarn test
TIP
Always skipping is useful when you have a
lefthook-local.yml
config and you don't want to run some commands locally. So you just overwrite theskip
option for them to betrue
.# lefthook.yml pre-commit: commands: lint: run: yarn lint
# lefthook-local.yml pre-commit: commands: lint: skip: true
only
You can force a command, script, or the whole hook to execute only in certain conditions. This option acts like the opposite of skip
. It accepts the same values but skips execution only if the condition is not satisfied.
Note:
skip
option takes precedence overonly
option, so if you have conflicting conditions the execution will be skipped.
Example
Execute a hook only for dev/*
branches.
# lefthook.yml
pre-commit:
only:
- ref: dev/*
commands:
lint:
run: yarn lint
test:
run: yarn test
When rebasing execute quick linter but skip usual linter and tests.
# lefthook.yml
pre-commit:
commands:
lint:
skip: rebase
run: yarn lint
test:
skip: rebase
run: yarn test
lint-on-rebase:
only: rebase
run: yarn lint-quickly
tags
You can specify tags for commands and scripts. This is useful for excluding. You can specify more than one tag using comma.
Example
# lefthook.yml
pre-commit:
commands:
lint:
tags:
- frontend
- js
run: yarn lint
test:
tags:
- backend
- ruby
run: bundle exec rspec
glob
You can set a glob to filter files for your command. This is only used if you use a file template in run
option or provide your custom files
command.
Example
# lefthook.yml
pre-commit:
commands:
lint:
glob: "*.{js,ts,jsx,tsx}"
run: yarn eslint {staged_files}
Notes
For patterns that you can use see this reference. We use glob library.
If you've specified glob
but don't have a files template in run
option, lefthook will check {staged_files}
for pre-commit
hook and {push_files}
for pre-push
hook and apply filtering. If no files left, the command will be skipped.
# lefthook.yml
pre-commit:
commands:
lint:
glob: "*.js"
run: npm run lint # skipped if no .js files staged
files
A custom git command for files or directories to be referenced in {files}
template for run
setting.
If the result of this command is empty, the execution of commands will be skipped.
This option overwrites the hook-level files
option.
Example
Provide a git command to list files.
# lefthook.yml
pre-push:
commands:
stylelint:
tags:
- frontend
- style
files: git diff --name-only master
glob: "*.js"
run: yarn stylelint {files}
Call a custom script for listing files.
# lefthook.yml
pre-push:
commands:
rubocop:
tags: backend
glob: "**/*.rb"
files: node ./lefthook-scripts/ls-files.js # you can call your own scripts
run: bundle exec rubocop --force-exclusion --parallel {files}
file_types
Filter files in a run
templates by their type. Supported types:
File type | Exlanation |
---|---|
text | Any file that contains text. Symlinks are not followed. |
binary | Any file that contains non-text bytes. Symlinks are not followed. |
executable | Any file that has executable bits set. Symlinks are not followed. |
not executable | Any file without executable bits in file mode. Symlinks included. |
symlink | A symlink file. |
not symlink | Any non-symlink file. |
Important: When passed multiple file types all constraints will be applied to the resulting list of files
Examples
Apply some different linters on text and binary files.
# lefthook.yml
pre-commit:
commands:
lint-code:
run: yarn lint {staged_files}
file_types: text
check-hex-codes:
run: yarn check-hex {staged_files}
file_types: binary
Skip symlinks.
# lefthook.yml
pre-commit:
commands:
lint:
run: yarn lint --fix {staged_files}
file_types:
- not symlink
Lint executable scripts.
# lefthook.yml
pre-commit:
commands:
lint:
run: yarn lint --fix {staged_files}
file_types:
- executable
- text
env
You can specify some ENV variables for the command or script.
Example
# lefthook.yml
pre-commit:
commands:
test:
env:
RAILS_ENV: test
run: bundle exec rspec
Extending PATH
If your hook is run by GUI program, and you use some PATH tweaks in your ~/.lefthook-local.yml
configuration the following way.
# lefthook.yml
pre-commit:
commands:
test:
run: yarn test
# lefthook-local.yml
pre-commit:
commands:
test:
env:
PATH: $PATH:/home/me/path/to/yarn
Notes
This option is useful when using lefthook on different OSes or shells where ENV variables are set in different ways.
root
You can change the CWD for the command you execute using root
option.
This is useful when you execute some npm
or yarn
command but the package.json
is in another directory.
For pre-push
and pre-commit
hooks and for the custom files
command root
option is used to filter file paths. If all files are filtered the command will be skipped.
Example
Format and stage files from a client/
folder.
# Folders structure
$ tree .
.
├── client/
│ ├── package.json
│ ├── node_modules/
| ├── ...
├── server/
| ...
# lefthook.yml
pre-commit:
commands:
lint:
root: "client/"
glob: "*.{js,ts}"
run: yarn eslint --fix {staged_files} && git add {staged_files}
exclude
For the exclude
option two variants are supported:
- A list of globs to be excluded
- A single regular expression (deprecated)
Note: The regular expression is matched against full paths to files in the repo, relative to the repo root, using
/
as the directory separator on all platforms. File paths do not begin with the separator or any other prefix.
Example
Run Rubocop on staged files with .rb
extension except for application.rb
, routes.rb
, rails_helper.rb
, and all Ruby files in config/initializers/
.
# lefthook.yml
pre-commit:
commands:
lint:
glob: "*.rb"
exclude:
- config/routes.rb
- config/application.rb
- config/initializers/*.rb
- spec/rails_helper.rb
run: bundle exec rubocop --force-exclusion {staged_files}
The same example using a regular expression.
# lefthook.yml
pre-commit:
commands:
lint:
glob: "*.rb"
exclude: '(^|/)(application|routes|rails_helper|initializers/\w+)\.rb$'
run: bundle exec rubocop --force-exclusion {staged_files}
Important
Be careful with the config file format's string quoting and escaping rules when writing regexps in it. For YAML, single quotes are often the simplest choice.
If you've specified exclude
but don't have a files template in run
option, lefthook will check {staged_files}
for pre-commit
hook and {push_files}
for pre-push
hook and apply filtering. If no files left, the command will be skipped.
# lefthook.yml
pre-commit:
commands:
lint:
exclude: '(^|/)application\.rb$'
run: bundle exec rubocop # skipped if only application.rb was staged
fail_text
You can specify a text to show when the command or script fails.
Example
# lefthook.yml
pre-commit:
commands:
lint:
run: yarn lint
fail_text: Add node executable to $PATH
$ git commit -m 'fix: Some bug'
Lefthook v1.1.3
RUNNING HOOK: pre-commit
EXECUTE > lint
SUMMARY: (done in 0.01 seconds)
🥊 lint: Add node executable to $PATH env
stage_fixed
Default: false
Works only for
pre-commit
hook
When set to true
lefthook will automatically call git add
on files after running the command or script. For a command if files
option was specified, the specified command will be used to retrieve files for git add
. For scripts and commands without files
option {staged_files}
template will be used. All filters (glob
, exclude
) will be applied if specified.
Example
# lefthook.yml
pre-commit:
commands:
lint:
run: npm run lint --fix {staged_files}
stage_fixed: true
interactive
Default: false
Note: If you want to pass stdin to your command or script but don't need to get the input from CLI, use
use_stdin
option instead.
Whether to use interactive mode. This applies the certain behavior:
- All
interactive
commands/scripts are executed after non-interactive. Exception:piped
option is set totrue
. - When executing, lefthook tries to open /dev/tty (Linux/Unix only) and use it as stdin.
- When
no_tty
option is set,interactive
is ignored.
use_stdin
Note: With many commands or scripts having
use_stdin: true
, only one will receive the data. The others will have nothing. If you need to pass the data from stdin to every command or script, please, submit a feature request.
Pass the stdin from the OS to the command/script.
Example
Use this option for the pre-push
hook when you have a script that does while read ...
. Without this option lefthook will hang: lefthook uses pseudo TTY by default, and it doesn't close stdin when all data is read.
# .lefthook/pre-push/do-the-magic.sh
remote="$1"
url="$2"
while read local_ref local_oid remote_ref remote_oid; do
# ...
done
# lefthook.yml
pre-push:
scripts:
"do-the-magic.sh":
runner: bash
use_stdin: true
priority
Default: 0
Note: This option makes sense only when
parallel: false
orpiped: true
is set.Value
0
is considered an+Infinity
, so commands or scripts withpriority: 0
or without this setting will be run at the very end.
Set priority from 1 to +Infinity. This option can be used to configure the order of the sequential steps.
Example
# lefthook.yml
post-checkout:
piped: true
commands:
db-create:
priority: 1
run: rails db:create
db-migrate:
priority: 2
run: rails db:migrate
db-seed:
priority: 3
run: rails db:seed
scripts:
"check-spelling.sh":
runner: bash
priority: 1
"check-grammar.rb":
runner: ruby
priority: 2
Scripts
Scripts are stored under <source_dir>/<hook-name>/
folder. These scripts are your own executables which are being run in the project root.
To add a script for a pre-commit
hook:
- Run
lefthook add -d pre-commit
- Edit
.lefthook/pre-commit/my-script.sh
- Add an entry to
lefthook.yml
# lefthook.yml pre-commit: scripts: "my-script.sh": runner: bash
Example
Let's create a bash script to check commit templates .lefthook/commit-msg/template_checker
:
INPUT_FILE=$1
START_LINE=`head -n1 $INPUT_FILE`
PATTERN="^(TICKET)-[[:digit:]]+: "
if ! [[ "$START_LINE" =~ $PATTERN ]]; then
echo "Bad commit message, see example: TICKET-123: some text"
exit 1
fi
Now we can ask lefthook to run our bash script by adding this code to
lefthook.yml
file:
# lefthook.yml
commit-msg:
scripts:
"template_checker":
runner: bash
When you try to commit git commit -m "bad commit text"
script template_checker
will be executed. Since commit text doesn't match the described pattern the commit process will be interrupted.
Script options
runner
You should specify a runner for the script. This is a command that should execute a script file. It will be called the following way: <runner> <path-to-script>
(e.g. ruby .lefthook/pre-commit/lint.rb
).
Example
# lefthook.yml
pre-commit:
scripts:
"lint.js":
runner: node
"check.go":
runner: go run
skip
You can skip all or specific commands and scripts using skip
option. You can also skip when merging, rebasing, or being on a specific branch. Globs are available for branches.
Possible skip values:
rebase
- when in rebase git statemerge
- when in merge git statemerge-commit
- when current HEAD commit is the merge commitref: main
- when on amain
branchrun: test ${SKIP_ME} -eq 1
- whentest ${SKIP_ME} -eq 1
is successful (return code is 0)
Example
Always skipping a command:
# lefthook.yml
pre-commit:
commands:
lint:
skip: true
run: yarn lint
Skipping on merging and rebasing:
# lefthook.yml
pre-commit:
commands:
lint:
skip:
- merge
- rebase
run: yarn lint
Or
# lefthook.yml
pre-commit:
commands:
lint:
skip: merge
run: yarn lint
Skipping when your are on a merge commit:
# lefthook.yml
pre-push:
commands:
lint:
skip: merge-commit
run: yarn lint
Skipping the whole hook on main
branch:
# lefthook.yml
pre-commit:
skip:
- ref: main
commands:
lint:
run: yarn lint
test:
run: yarn test
Skipping hook for all dev/*
branches:
# lefthook.yml
pre-commit:
skip:
- ref: dev/*
commands:
lint:
run: yarn lint
test:
run: yarn test
Skipping hook by running a command:
# lefthook.yml
pre-commit:
skip:
- run: test "${NO_HOOK}" -eq 1
commands:
lint:
run: yarn lint
test:
run: yarn test
TIP
Always skipping is useful when you have a
lefthook-local.yml
config and you don't want to run some commands locally. So you just overwrite theskip
option for them to betrue
.# lefthook.yml pre-commit: commands: lint: run: yarn lint
# lefthook-local.yml pre-commit: commands: lint: skip: true
only
You can force a command, script, or the whole hook to execute only in certain conditions. This option acts like the opposite of skip
. It accepts the same values but skips execution only if the condition is not satisfied.
Note:
skip
option takes precedence overonly
option, so if you have conflicting conditions the execution will be skipped.
Example
Execute a hook only for dev/*
branches.
# lefthook.yml
pre-commit:
only:
- ref: dev/*
commands:
lint:
run: yarn lint
test:
run: yarn test
When rebasing execute quick linter but skip usual linter and tests.
# lefthook.yml
pre-commit:
commands:
lint:
skip: rebase
run: yarn lint
test:
skip: rebase
run: yarn test
lint-on-rebase:
only: rebase
run: yarn lint-quickly
tags
You can specify tags for commands and scripts. This is useful for excluding. You can specify more than one tag using comma.
Example
# lefthook.yml
pre-commit:
commands:
lint:
tags:
- frontend
- js
run: yarn lint
test:
tags:
- backend
- ruby
run: bundle exec rspec
env
You can specify some ENV variables for the command or script.
Example
# lefthook.yml
pre-commit:
commands:
test:
env:
RAILS_ENV: test
run: bundle exec rspec
Extending PATH
If your hook is run by GUI program, and you use some PATH tweaks in your ~/.lefthook-local.yml
configuration the following way.
# lefthook.yml
pre-commit:
commands:
test:
run: yarn test
# lefthook-local.yml
pre-commit:
commands:
test:
env:
PATH: $PATH:/home/me/path/to/yarn
Notes
This option is useful when using lefthook on different OSes or shells where ENV variables are set in different ways.
fail_text
You can specify a text to show when the command or script fails.
Example
# lefthook.yml
pre-commit:
commands:
lint:
run: yarn lint
fail_text: Add node executable to $PATH
$ git commit -m 'fix: Some bug'
Lefthook v1.1.3
RUNNING HOOK: pre-commit
EXECUTE > lint
SUMMARY: (done in 0.01 seconds)
🥊 lint: Add node executable to $PATH env
stage_fixed
Default: false
Works only for
pre-commit
hook
When set to true
lefthook will automatically call git add
on files after running the command or script. For a command if files
option was specified, the specified command will be used to retrieve files for git add
. For scripts and commands without files
option {staged_files}
template will be used. All filters (glob
, exclude
) will be applied if specified.
Example
# lefthook.yml
pre-commit:
commands:
lint:
run: npm run lint --fix {staged_files}
stage_fixed: true
interactive
Default: false
Note: If you want to pass stdin to your command or script but don't need to get the input from CLI, use
use_stdin
option instead.
Whether to use interactive mode. This applies the certain behavior:
- All
interactive
commands/scripts are executed after non-interactive. Exception:piped
option is set totrue
. - When executing, lefthook tries to open /dev/tty (Linux/Unix only) and use it as stdin.
- When
no_tty
option is set,interactive
is ignored.
use_stdin
Note: With many commands or scripts having
use_stdin: true
, only one will receive the data. The others will have nothing. If you need to pass the data from stdin to every command or script, please, submit a feature request.
Pass the stdin from the OS to the command/script.
Example
Use this option for the pre-push
hook when you have a script that does while read ...
. Without this option lefthook will hang: lefthook uses pseudo TTY by default, and it doesn't close stdin when all data is read.
# .lefthook/pre-push/do-the-magic.sh
remote="$1"
url="$2"
while read local_ref local_oid remote_ref remote_oid; do
# ...
done
# lefthook.yml
pre-push:
scripts:
"do-the-magic.sh":
runner: bash
use_stdin: true
priority
Default: 0
Note: This option makes sense only when
parallel: false
orpiped: true
is set.Value
0
is considered an+Infinity
, so commands or scripts withpriority: 0
or without this setting will be run at the very end.
Set priority from 1 to +Infinity. This option can be used to configure the order of the sequential steps.
Example
# lefthook.yml
post-checkout:
piped: true
commands:
db-create:
priority: 1
run: rails db:create
db-migrate:
priority: 2
run: rails db:migrate
db-seed:
priority: 3
run: rails db:seed
scripts:
"check-spelling.sh":
runner: bash
priority: 1
"check-grammar.rb":
runner: ruby
priority: 2
Examples
Commitlint and commitzen
Use lefthook to generate commit messages using commitzen and validate them with commitlint.
Install dependencies
yarn add -D @commitlint/cli @commitlint/config-conventional
# If using commitzen
yarn add -D commitizen cz-conventional-changelog
Configure
Setup commitlint.config.js
. Conventional configuration:
echo "module.exports = {extends: ['@commitlint/config-conventional']};" > commitlint.config.js
If you are using commitzen, make sure to add this in package.json
:
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
Test it
# You can type it without message, if you are using commitzen
git commit
# Or provide a commit message is using only commitlint
git commit -am 'fix: typo'
# lefthook.yml
# Use this to build commit messages
prepare-commit-msg:
commands:
commitzen:
interactive: true
run: yarn run cz --hook # Or npx cz --hook
env:
LEFTHOOK: 0
# Use this to validate commit messages
commit-msg:
commands:
"lint commit message":
run: yarn run commitlint --edit {1}
# commitlint.config.js
module.exports = {extends: ['@commitlint/config-conventional']};
Remotes
Use configurations from other Git repositories via
remotes
feature.Lefthook will automatically download the remote config files and merge them into existing configuration.
remotes:
- git_url: https://github.com/evilmartians/lefthook
configs:
- examples/remote/ping.yml
Stage fixed files
Works only for
pre-commit
Git hook
Sometimes your linter fixes the changes and you usually want to commit them automatically. To enable auto-staging of the fixed files use stage_fixed
option.
# lefthook.yml
pre-commit:
commands:
lint:
run: yarn lint {staged_files} --fix
stage_fixed: true
Filters
Files passed to your hooks can be filtered with the following options
In this example all staged files will pass through these filters.
# lefthook.yml
pre-commit:
commands:
lint:
run: yarn lint {staged_files} --fix
glob: "*.{js,ts}"
root: frontend
exclude:
- *.config.js
- *.config.ts
file_types:
- not executable
Imagine you've staged the following files
backend/asset.js
frontend/src/index.ts
frontend/bin/cli.js # <- executable
frontend/eslint.config.js
frontend/README.md
After all filters applied the lint
command will execute the following:
yarn lint frontend/src/index.ts --fix