Lefthook supports YAML, JSON, and TOML configuration. In this document lefthook.yml
is used for simplicity.
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
.
These options are not related to git hooks, and they only control lefthook behavior.
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.
Default: true
Whether enable or disable colorful output of Lefthook. This option can be overwritten with --no-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'
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
You can extend your config with another one YAML file. Its content will be merged. Extends for lefthook.yml
, lefthook-local.yml
, and remote
configs are handled separately, so you can have different extends in these files.
Example
# lefthook.yml
extends:
- /home/user/work/lefthook-extend.yml
- /home/user/work/lefthook-extend-2.yml
- lefthook-extends/file.yml
- ../extend.yml
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
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
.
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
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
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.
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)
- Or even if your GUI program cannot locate the
lefthook
executable 😱 - 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 ~/rc.
# 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"
# 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
😉
🧪 This feature is in Beta version
You can provide a remote config if you want to share your lefthook configuration across many projects. Lefthook will automatically download and merge the configuration into your local lefthook.yml
.
You can use extends
related to the config file (not absolute paths).
If you provide scripts
in a remote file, the scripts folder must be in the root of the repository.
Note
Configuration in remote
will be merged to configuration in lefthook.yml
, so the priority will be the following:
lefthook.yml
remote
lefthook-local.yml
This can be changed in the future. For convenience, please use remote
configuration without any hooks configuration in lefthook.yml
.
A URL to Git repository. It will be accessed with privileges of the machine lefthook runs on.
Example
# lefthook.yml
remote:
git_url: [email protected]:evilmartians/lefthook
Or
# lefthook.yml
remote:
git_url: https://github.com/evilmartians/lefthook
An optional branch or tag name.
Example
# lefthook.yml
remote:
git_url: [email protected]:evilmartians/lefthook
ref: v1.0.0
Note
⚠️ If you initially hadref
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.
Default: lefthook.yml
An optional config path from remote's root.
Example
# lefthook.yml
remote:
git_url: [email protected]:evilmartians/remote
ref: v1.0.0
config: examples/ruby-linter.yml
Commands and scripts are defined for git hooks. You can defined a hook for all hooks listed in this file.
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:
...
Default: false
Whether run commands and scripts concurrently.
Default: false
Whether run commands and scripts sequentially. Will stop execution if one of the commands/scripts 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
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
.
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:
tag: frontend
...
test:
tag: frontend
...
check-syntax:
tag: 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
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
Scripts to be executed for the hook. Each script has a name (filename in scripts dir) and associated run options.
<source_dir>/<git-hook-name>/
folder. See source_dir
.
Example
# lefthook.yml
pre-commit:
scripts:
"lint.sh":
... # script options
Correct folders structure:
.lefthook/
└── pre-commit/
└── lint.sh
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
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}
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}
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}
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: '(^|/)(application|routes)\.rb$'
run: bundle exec rubocop --force-exclusion {all_files}
# 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}
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'
Notes
If using {all_files}
with RuboCop, it will ignore RuboCop's Exclude
configuration setting. To avoid this, pass --force-exclusion
.
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'`
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.
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 the whole hook on main
branch:
# lefthook.yml
pre-commit:
skip:
- ref: main
commands:
lint:
run: yarn lint
text:
run: yarn test
Skipping hook for all dev/*
branches:
# lefthook.yml
pre-commit:
skip:
- ref: dev/*
commands:
lint:
run: yarn lint
text:
run: yarn test
Notes
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 the skip
option for them to be true
.
# lefthook.yml
pre-commit:
commands:
lint:
run: yarn lint
# lefthook-local.yml
pre-commit:
commands:
lint:
skip: true
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
You can specify tags for commands and scripts. This is useful for excluding. You can specify more than one tag using comma or space.
Example
# lefthook.yml
pre-commit:
commands:
lint:
tags: frontend,js
run: yarn lint
test:
tags: backend ruby
run: bundle exec rspec
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
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}
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
If your hook is run by GUI program, and you use some PATH tweaks in your ~/.rc, you might see an error saying executable not found. In that case You can extend the $PATH variable with 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.
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}
You can provide a regular expression to exclude some files from being passed to run
command.
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
, and rails_helper.rb
(wherever they are).
# lefthook.yml
pre-commit:
commands:
lint:
glob: "*.rb"
exclude: '(^|/)(application|routes|rails_helper)\.rb$'
run: bundle exec rubocop --force-exclusion {staged_files}
Notes
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
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
Default: false
Used only for
pre-commit
hook. Is ignored for other hooks.
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
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.
Default: 0
Note
This option makes sense only when
parallel: false
orpiped: true
is set.Value
0
is considered an+Infinity
, so commands withpriority: 0
or without this setting will be run at the very end.
Set command priority from 1 to +Infinity. This option can be used to configure the order of the sequential commands.
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 are stored under <source_dir>/<hook-name>/
folder. These scripts are your own executables which are being run in the project root (if you don't specify a root
option).
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.
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
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
We have a directory with few examples. You can check it here.
Have a question?
🧐 Check the wiki
🤔 Or start a discussion