Introduction
Lefthook is a Git hooks manager. Here is how to
-
Install lefthook to your project or globally.
-
Configure
lefthook.ymlwith detailed options explanation.
Example: Run your linters on pre-commit hook and forget about the routine.
# lefthook.yml
pre-commit:
parallel: true
jobs:
- run: yarn run stylelint --fix {staged_files}
glob: "*.css"
stage_fixed: true
- run: yarn run eslint --fix "{staged_files}"
glob:
- "*.ts"
- "*.js"
- "*.tsx"
- "*.jsx"
stage_fixed: true
Install lefthook
As a dev dependency
With package managers
Ruby
# Gemfile
group :development do
gem "lefthook", require: false
end
Or globally
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
npm install --save-dev lefthook
yarn add --dev lefthook
pnpm add -D lefthook
Note: If you use
pnpmpackage manager make sure to updatepnpm-workspace.yamlsonlyBuiltDependencieswithlefthookand addlefthooktopnpm.onlyBuiltDependenciesin your rootpackage.json, otherwise thepostinstallscript of thelefthookpackage won't be executed and hooks won't be installed.
Note: lefthook has three NPM packages with different ways to deliver the executables
-
lefthook installs one executable for your system
npm install --save-dev lefthook -
legacy1 @evilmartians/lefthook installs executables for all OS
npm install --save-dev @evilmartians/lefthook -
legacy1 @evilmartians/lefthook-installer fetches the right executable on installation
npm install --save-dev @evilmartians/lefthook-installer
Legacy distributions are still maintained but they will be shut down in the future.
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: "2.0.2"),
Or, with mint:
mint run csjones/lefthook-plugin
Go
The minimum Go version required is 1.25 and you can install
- as global package
go install github.com/evilmartians/lefthook/v2@v2.0.2
- or as a go tool in your project
go get -tool github.com/evilmartians/lefthook/v2
Python
python -m pip install --user lefthook
uv add --dev lefthook
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
- Official AUR package (compiles from sources)
- Community AUR package (delivers pre-compiled binaries)
# To compile from sources
yay -S lefthook
# To install only executable
yay -S lefthook-bin
Mise
mise use lefthook@latest
Note: The mise plugin for lefthook is maintained by the community. While we appreciate their contribution, the lefthook team cannot provide direct support for mise-specific installation issues.
Manual installation with prebuilt executable
Download binaries from latest release and install manually.
Commands
Tip: Use
lefthook helporlefthook <command> -h/--helpto discover available commands and their options
lefthook installlefthook uninstalllefthook addlefthook runlefthook versionlefthook self-updatelefthook validatelefthook dump
lefthook install
Creates an empty lefthook.yml if a configuration file does not exist.
Installs configured hooks to Git hooks.
Note: NPM package
lefthookinstalls the hooks in a postinstall script automatically. For projects not using NPM package runlefthook installafter cloning the repo.
Installing specific hooks
You can install only specific hooks by running lefthook install <hook-1> <hook-2> ....
lefthook uninstall
Clears Git hooks installed by lefthook.
lefthook add
Installs the given hook to Git hook.
With argument --dirs creates a directory .git/hooks/<hook name>/ if it doesn't exist. Use it before adding a script to configuration.
Example
$ lefthook add pre-push --dirs
Describe pre-push commands in lefthook.yml:
pre-push:
jobs:
- script: "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
Executes the commands and scripts configured for a given hook. Installed Git hooks call lefthook run implicitly.
Example
# lefthook.yml
pre-commit:
jobs:
- name: lint
run: yarn lint --fix {staged_files}
test:
jobs:
- name: test
run: yarn test
Install the hook.
$ lefthook install
$ lefthook run test # will run 'yarn test'
$ git commit # will run pre-commit hook ('yarn lint --fix')
$ lefthook run pre-commit # will run pre-commit hook (`yarn lint --fix`)
Run specific jobs
You can specify which jobs to run (also --tag supported).
$ lefthook run pre-commit --job lints --job pretty --tag checks
Specify files
You can force replacing files templates (like {staged_files}) with either all files (will acts as {all_files} template) 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
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.
lefthook validate
Validates your lefthook configuration. Use lefthook dump to see it.
It uses JSON schema from the lefthook Github repo.
lefthook dump
Prints the whole configuration after merging all secondary configs.
This is the actual config lefthook uses, it can be build from the main config (lefthook.yml), remotes, extends, and lefthook-local.yml overrides.
lefthook install
Creates an empty lefthook.yml if a configuration file does not exist.
Installs configured hooks to Git hooks.
Note: NPM package
lefthookinstalls the hooks in a postinstall script automatically. For projects not using NPM package runlefthook installafter cloning the repo.
Installing specific hooks
You can install only specific hooks by running lefthook install <hook-1> <hook-2> ....
lefthook uninstall
Clears Git hooks installed by lefthook.
lefthook run
Executes the commands and scripts configured for a given hook. Installed Git hooks call lefthook run implicitly.
Example
# lefthook.yml
pre-commit:
jobs:
- name: lint
run: yarn lint --fix {staged_files}
test:
jobs:
- name: test
run: yarn test
Install the hook.
$ lefthook install
$ lefthook run test # will run 'yarn test'
$ git commit # will run pre-commit hook ('yarn lint --fix')
$ lefthook run pre-commit # will run pre-commit hook (`yarn lint --fix`)
Run specific jobs
You can specify which jobs to run (also --tag supported).
$ lefthook run pre-commit --job lints --job pretty --tag checks
Specify files
You can force replacing files templates (like {staged_files}) with either all files (will acts as {all_files} template) 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 add
Installs the given hook to Git hook.
With argument --dirs creates a directory .git/hooks/<hook name>/ if it doesn't exist. Use it before adding a script to configuration.
Example
$ lefthook add pre-push --dirs
Describe pre-push commands in lefthook.yml:
pre-push:
jobs:
- script: "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 validate
Validates your lefthook configuration. Use lefthook dump to see it.
It uses JSON schema from the lefthook Github repo.
lefthook dump
Prints the whole configuration after merging all secondary configs.
This is the actual config lefthook uses, it can be build from the main config (lefthook.yml), remotes, extends, and lefthook-local.yml overrides.
lefthook check-install
Checks if Git hooks are installed and synchronized.
Returns:
0if hooks installed and synchronized1if hooks not installed or need a sync
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.
Features
Small features that make lefthook experience nicer
Local config
You can extend and override options of your main configuration with lefthook-local.yml. Don't forget to add the file to .gitignore.
You can also use lefthook-local.yml without a main config file. This is useful when you want to use lefthook locally without imposing it on your teammates.
# lefthook.yml (committed into your repo)
pre-commit:
jobs:
- name: linter
run: yarn lint
- name: tests
run: yarn test
# lefthook-local.yml (ignored by git)
pre-commit:
jobs:
- name: tests
skip: true # don't want to run tests on every commit
- name: linter
run: yarn lint {staged_files} # lint only staged files
Capture ARGS from git in the script
Lefthook passes Git arguments to your commands and scripts.
├── .lefthook
│ └── prepare-commit-msg
│ └── message.sh
└── lefthook.yml
# lefthook.yml
prepare-commit-msg:
jobs:
- script: "message.sh"
runner: bash
- run: echo "Git args: {1} {2} {3}"
# .lefthook/prepare-commit-msg/message.sh
# Arguments get passed from Git
COMMIT_MSG_FILE=$1
COMMIT_SOURCE=$2
SHA1=$3
# ...
Git LFS support
Note: 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.
Git LFS hooks may be slow. Disable them with the global
skip_lfs: truesetting.
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.
To avoid calling LFS hooks set skip_lfs: true in lefthook.yml or lefthook-local.yml
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.
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).
Local config
You can extend and override options of your main configuration with lefthook-local.yml. Don't forget to add the file to .gitignore.
You can also use lefthook-local.yml without a main config file. This is useful when you want to use lefthook locally without imposing it on your teammates.
# lefthook.yml (committed into your repo)
pre-commit:
jobs:
- name: linter
run: yarn lint
- name: tests
run: yarn test
# lefthook-local.yml (ignored by git)
pre-commit:
jobs:
- name: tests
skip: true # don't want to run tests on every commit
- name: linter
run: yarn lint {staged_files} # lint only staged files
Capture ARGS from git in the script
Lefthook passes Git arguments to your commands and scripts.
├── .lefthook
│ └── prepare-commit-msg
│ └── message.sh
└── lefthook.yml
# lefthook.yml
prepare-commit-msg:
jobs:
- script: "message.sh"
runner: bash
- run: echo "Git args: {1} {2} {3}"
# .lefthook/prepare-commit-msg/message.sh
# Arguments get passed from Git
COMMIT_MSG_FILE=$1
COMMIT_SOURCE=$2
SHA1=$3
# ...
Git LFS support
Note: 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.
Git LFS hooks may be slow. Disable them with the global
skip_lfs: truesetting.
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.
To avoid calling LFS hooks set skip_lfs: true in lefthook.yml or lefthook-local.yml
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.
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).
Config file name
Lefthook supports the following file names for the main config:
| Format | File name |
|---|---|
| YAML | lefthook.yml |
| YAML | .lefthook.yml |
| YAML | .config/lefthook.yml |
| YAML | lefthook.yaml |
| YAML | .lefthook.yaml |
| YAML | .config/lefthook.yaml |
| TOML | lefthook.toml |
| TOML | .lefthook.toml |
| TOML | .config/lefthook.toml |
| JSON | lefthook.json |
| JSON | .lefthook.json |
| JSON | .config/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.
Filenames without the leading dot will also be looked up from the .config subdirectory.
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.
The -local config can be used without a main config file. This is useful when you want to use lefthook locally without imposing it on your teammates – just create a lefthook-local.yml file and add it to your global .gitignore.
Options
assert_lefthook_installedcolorsextendslefthookmin_versionno_ttyoutputrcremotessource_dirsource_dir_localskip_lfstemplates- {Git hook name} (e.g.
pre-commit)
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'
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,
extendsoverride settings fromlefthook.yml,remotesoverrideextends, andlefthook-local.ymlcan override everything.
lefthook
Default: null
Added in lefthook
1.10.5
Provide a full path to lefthook executable or a command to run lefthook. Bourne shell (sh) syntax is supported.
Important: This option does not merge from
remotesorextendsfor security reasons. But it gets merged from lefthook local config if specified.
There are three reasons you may want to specify lefthook:
- You want to force using specific lefthook version from your dependencies (e.g. npm package)
- You use PnP loader for your JS/TS project, and your
package.jsonwith lefthook dependency locates in a subfolder - You want to make sure you use concrete lefthook executable path and want to defined it in
lefthook-local.yml
Examples
Specify lefthook executable
# lefthook.yml
lefthook: /usr/bin/lefthook
pre-commit:
jobs:
- run: yarn lint
Specify a command to run lefthook
# lefthook.yml
lefthook: |
cd project-with-lefthook
pnpm lefthook
pre-commit:
jobs:
- run: yarn lint
root: project-with-lefthook
Force using a version from Rubygems
# lefthook.yml
lefthook: bundle exec lefthook
pre-commit:
jobs:
- run: bundle exec rubocop {staged_files}
Enable debug logs
# lefthook-local.yml
lefthook: lefthook --verbose
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
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
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.
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
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
lefthookexecutable :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
refoption, 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
neveror 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
refetchis 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
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.
skip_lfs
Default: false
Skip running LFS hooks even if it exists on your system.
Example
# lefthook.yml
skip_lfs: true
pre-push:
commands:
test:
run: yarn test
templates
Added in lefthook
1.10.8
Provide custom replacement for templates in run values.
With templates you can specify what can be overridden via lefthook-local.yml without a need to overwrite every jobs in your configuration.
Example
Override with lefthook-local.yml
# lefthook.yml
templates:
dip: # empty
pre-commit:
jobs:
# Will run: `bundle exec rubocop file1 file2 file3 ...`
- run: {dip} bundle exec rubocop {staged_files}
# lefthook-local.yml
templates:
dip: dip # Will run: `dip bundle exec rubocop file1 file2 file3 ...`
Reduce redundancy
# lefthook.yml
templates:
wrapper: docker-compose run --rm -v $(pwd):/app service
pre-commit:
jobs:
- run: {wrapper} yarn format
- run: {wrapper} yarn lint
- run: {wrapper} yarn test
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 command executed by the sh shell that returns the files or directories 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: trueandparallel: trueare 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
parallelthe output can be a mess, so please avoid setting both options totrue
fail_on_changes
The behaviour of lefthook when files (tracked by git) are modified can set by modifying the fail_on_changes configuration parameter. The possible values are:
never: never exit with a non-zero status if files were modified (default).always: always exit with a non-zero status if files were modified.ci: exit with a non-zero status only whenCIenvironment variable is set. This can be useful when combined withstage_fixedto ensure a frictionless devX locally, and a robust CI.
# lefthook.yml
pre-commit:
parallel: true
fail_on_changes: "always"
commands:
lint:
run: yarn lint
test:
run: yarn test
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
exclude
This option allows to setup a list of globs for files to be excluded in files template.
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:
jobs:
- name: 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}
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:
exclude:
- "*/application.rb"
jobs:
- name: lint
run: bundle exec rubocop # will skip if only application.rb was staged
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 amainbranchrun: test ${SKIP_ME} -eq 1- whentest ${SKIP_ME} -eq 1is 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.ymlconfig and you don't want to run some commands locally. So you just overwrite theskipoption 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:
skipoption takes precedence overonlyoption, 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
jobs
Added in lefthook
1.10.0
Jobs provide a flexible way to define tasks, supporting both commands and scripts. Jobs can be grouped for advanced flow control.
Basic example
Define jobs in your lefthook.yml file under a specific hook like pre-commit:
# lefthook.yml
pre-commit:
jobs:
- run: yarn lint
- run: yarn test
Differences from Commands and Scripts
Optional Job Names
- Named jobs are merged across
extendsand local config. - Unnamed jobs are appended in the order of their definition.
Job Groups
- Groups can include other jobs.
- Flow within groups can be parallel or piped. Options
glob,root, andexcludeapply to all jobs in the group, including nested ones.
Job options
Below are the available options for configuring jobs.
namerunscriptrunnergroupskiponlytagsglobfilesfile_typesenvrootexcludefail_textstage_fixedinteractiveuse_stdin
Example
Note: Currently, only
root,glob, andexcludeoptions are applied to group jobs. Other options must be set for each job individually. Submit a feature request if this limits your workflow.
A configuration demonstrating a piped group running in parallel with other jobs:
# lefthook.yml
pre-commit:
parallel: true
jobs:
- name: migrate
root: backend/
glob: "db/migrations/*"
group:
piped: true
jobs:
- run: bundle install
- run: rails db:migrate
- run: yarn lint --fix {staged_files}
root: frontend/
stage_fixed: true
- run: bundle exec rubocop
root: backend/
- run: golangci-lint
root: proxy/
- script: verify.sh
runner: bash
This configuration runs migrate jobs in a piped flow while other jobs execute in parallel.
name
Name of a job. Will be printed in summary. If specified, the jobs can be merged with a jobs of the same name in a local config or extends.
Example
# lefthook.yml
pre-commit:
jobs:
- name: lint and fix
run: yarn run eslint --fix {staged_files}
run
This is a mandatory option for a command, which specifies the actual command to be run using the sh shell.
You can use files templates that will be substituted with the appropriate files on execution:
{files}- customfilescommand 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.{lefthook_job_name}- current job/command/script name
Note: Command line length has a limit on every system. If your list of files is quite long, lefthook splits your files list to fit in the limit and runs few commands sequentially.
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-exclusionwill applyExcludeconfiguration 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'`
Scripts
# lefthook.yml
pre-commit:
jobs:
- name: a whole script in a run
run: |
for file in $(ls .); do
yarn lint $file
done
script
Name of a script to execute. The rules are the same as for scripts
Example
# lefthook.yml
pre-commit:
jobs:
- script: linter.sh
runner: bash
# .lefthook/pre-commit/linter.sh
echo "Everything is OK"
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
group
You can define a group of jobs and configure how they should execute using the following options:
parallel: Executes all jobs in the group simultaneously.piped: Executes jobs sequentially, passing output between them.jobs: Specifies the jobs within the group.
Example
# lefthook.yml
pre-commit:
jobs:
- group:
parallel: true
jobs:
- run: echo 1
- run: echo 2
- run: echo 3
If you specify env, root, glob, or exclude on a group, they will be inherited to the underlying jobs.
# lefthook.yml
pre-commit:
jobs:
- env:
E1: hello
glob:
- "*.md"
exclude:
- "README.md"
root: "subdir/"
group:
parallel: true
jobs:
- run: echo $E1
- run: echo $E1
env:
E1: bonjour
Note: To make a group mergeable with settings defined in local config or extends you have to specify the name of the job group belongs to:
pre-commit: jobs: - name: a name of a group group: jobs: - name: lint run: yarn lint - name: test run: yarn test
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: trueandparallel: trueare 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
jobs
Added in lefthook
1.10.0
Jobs provide a flexible way to define tasks, supporting both commands and scripts. Jobs can be grouped for advanced flow control.
Basic example
Define jobs in your lefthook.yml file under a specific hook like pre-commit:
# lefthook.yml
pre-commit:
jobs:
- run: yarn lint
- run: yarn test
Differences from Commands and Scripts
Optional Job Names
- Named jobs are merged across
extendsand local config. - Unnamed jobs are appended in the order of their definition.
Job Groups
- Groups can include other jobs.
- Flow within groups can be parallel or piped. Options
glob,root, andexcludeapply to all jobs in the group, including nested ones.
Job options
Below are the available options for configuring jobs.
namerunscriptrunnergroupskiponlytagsglobfilesfile_typesenvrootexcludefail_textstage_fixedinteractiveuse_stdin
Example
Note: Currently, only
root,glob, andexcludeoptions are applied to group jobs. Other options must be set for each job individually. Submit a feature request if this limits your workflow.
A configuration demonstrating a piped group running in parallel with other jobs:
# lefthook.yml
pre-commit:
parallel: true
jobs:
- name: migrate
root: backend/
glob: "db/migrations/*"
group:
piped: true
jobs:
- run: bundle install
- run: rails db:migrate
- run: yarn lint --fix {staged_files}
root: frontend/
stage_fixed: true
- run: bundle exec rubocop
root: backend/
- run: golangci-lint
root: proxy/
- script: verify.sh
runner: bash
This configuration runs migrate jobs in a piped flow while other jobs execute in parallel.
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 amainbranchrun: test ${SKIP_ME} -eq 1- whentest ${SKIP_ME} -eq 1is 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.ymlconfig and you don't want to run some commands locally. So you just overwrite theskipoption 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:
skipoption takes precedence overonlyoption, 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:
jobs:
- name: lint
run: yarn eslint {staged_files}
glob: "*.{js,ts,jsx,tsx}"
Note: from lefthook version
1.10.10you can also provide a list of globs:# lefthook.yml pre-commit: jobs: - run: yarn lint {staged_files} glob: - "*.ts" - "*.js"
Notes
For patterns that you can use see this reference. We use glob library.
When using root:
Globs are still calculated from the actual root of the git repo, root is ignored.
Behaviour of **
Note that the behaviour of ** is different from typical glob implementations, like ls or tools like lint-staged in that a double-asterisk matches 1+ directories deep, not zero or more directories.
If you want to match both files at the top level and nested, then rather than:
glob: "src/**/*.js"
You'll need:
glob: "src/*.js"
Using glob without a files template inrun
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:
jobs:
- name: lint
run: npm run lint # skipped if no .js files staged
glob: "*.js"
files
A custom command executed by the sh shell that returns the 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. Special file types and MIME types are supported1:
| 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. |
text/html | An HTML file. |
text/xml | An XML file. |
text/javascript | A Javascript file. |
text/x-php | A PHP file. |
text/x-lua | A Lua file. |
text/x-perl | A Perl file. |
text/x-python | A Python file. |
text/x-shellscript | Shell script file. |
text/x-sh | Also shell script file. |
application/json | JSON file. |
Important The following types are applied using AND logic:
- text
- binary
- executable
- not executable
- symlink
- not symlink
The mime types are applied using OR logic. So, you can have both
text/x-luaandtext/x-sh, but you can't specify bothsymlinkandnot symlink.
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
Check typos in scripts.
# lefthook.yml
pre-commit:
jobs:
- run: typos -w {staged_files}
file_types:
- text/x-perl
- text/x-python
- text/x-php
- text/x-lua
- text/x-sh
All supported MIME types can be found here: supported_mimes.md
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}
Notes
When using root:
Globs are still calculated from the actual root of the git repo, root is ignored.
exclude
This option allows to setup a list of globs for files to be excluded in files template.
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:
jobs:
- name: 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}
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:
exclude:
- "*/application.rb"
jobs:
- name: lint
run: bundle exec rubocop # will skip 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-commithook
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_stdinoption instead.
Whether to use interactive mode. This applies the certain behavior:
- All
interactivecommands/scripts are executed after non-interactive. Exception:pipedoption is set totrue. - When executing, lefthook tries to open /dev/tty (Linux/Unix only) and use it as stdin.
- When
no_ttyoption is set,interactiveis 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
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
runskiponlytagsglobfilesfile_typesenvrootexcludefail_textstage_fixedinteractiveuse_stdinpriority
run
This is a mandatory option for a command, which specifies the actual command to be run using the sh shell.
You can use files templates that will be substituted with the appropriate files on execution:
{files}- customfilescommand 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.{lefthook_job_name}- current job/command/script name
Note: Command line length has a limit on every system. If your list of files is quite long, lefthook splits your files list to fit in the limit and runs few commands sequentially.
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-exclusionwill applyExcludeconfiguration 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'`
Scripts
# lefthook.yml
pre-commit:
jobs:
- name: a whole script in a run
run: |
for file in $(ls .); do
yarn lint $file
done
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 amainbranchrun: test ${SKIP_ME} -eq 1- whentest ${SKIP_ME} -eq 1is 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.ymlconfig and you don't want to run some commands locally. So you just overwrite theskipoption 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:
skipoption takes precedence overonlyoption, 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:
jobs:
- name: lint
run: yarn eslint {staged_files}
glob: "*.{js,ts,jsx,tsx}"
Note: from lefthook version
1.10.10you can also provide a list of globs:# lefthook.yml pre-commit: jobs: - run: yarn lint {staged_files} glob: - "*.ts" - "*.js"
Notes
For patterns that you can use see this reference. We use glob library.
When using root:
Globs are still calculated from the actual root of the git repo, root is ignored.
Behaviour of **
Note that the behaviour of ** is different from typical glob implementations, like ls or tools like lint-staged in that a double-asterisk matches 1+ directories deep, not zero or more directories.
If you want to match both files at the top level and nested, then rather than:
glob: "src/**/*.js"
You'll need:
glob: "src/*.js"
Using glob without a files template inrun
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:
jobs:
- name: lint
run: npm run lint # skipped if no .js files staged
glob: "*.js"
files
A custom command executed by the sh shell that returns the 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. Special file types and MIME types are supported1:
| 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. |
text/html | An HTML file. |
text/xml | An XML file. |
text/javascript | A Javascript file. |
text/x-php | A PHP file. |
text/x-lua | A Lua file. |
text/x-perl | A Perl file. |
text/x-python | A Python file. |
text/x-shellscript | Shell script file. |
text/x-sh | Also shell script file. |
application/json | JSON file. |
Important The following types are applied using AND logic:
- text
- binary
- executable
- not executable
- symlink
- not symlink
The mime types are applied using OR logic. So, you can have both
text/x-luaandtext/x-sh, but you can't specify bothsymlinkandnot symlink.
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
Check typos in scripts.
# lefthook.yml
pre-commit:
jobs:
- run: typos -w {staged_files}
file_types:
- text/x-perl
- text/x-python
- text/x-php
- text/x-lua
- text/x-sh
All supported MIME types can be found here: supported_mimes.md
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}
Notes
When using root:
Globs are still calculated from the actual root of the git repo, root is ignored.
exclude
This option allows to setup a list of globs for files to be excluded in files template.
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:
jobs:
- name: 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}
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:
exclude:
- "*/application.rb"
jobs:
- name: lint
run: bundle exec rubocop # will skip 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-commithook
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_stdinoption instead.
Whether to use interactive mode. This applies the certain behavior:
- All
interactivecommands/scripts are executed after non-interactive. Exception:pipedoption is set totrue. - When executing, lefthook tries to open /dev/tty (Linux/Unix only) and use it as stdin.
- When
no_ttyoption is set,interactiveis 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: falseorpiped: trueis set.Value
0is considered an+Infinity, so commands or scripts withpriority: 0or 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
Script options
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.
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 amainbranchrun: test ${SKIP_ME} -eq 1- whentest ${SKIP_ME} -eq 1is 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.ymlconfig and you don't want to run some commands locally. So you just overwrite theskipoption 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:
skipoption takes precedence overonlyoption, 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-commithook
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_stdinoption instead.
Whether to use interactive mode. This applies the certain behavior:
- All
interactivecommands/scripts are executed after non-interactive. Exception:pipedoption is set totrue. - When executing, lefthook tries to open /dev/tty (Linux/Unix only) and use it as stdin.
- When
no_ttyoption is set,interactiveis 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: falseorpiped: trueis set.Value
0is considered an+Infinity, so commands or scripts withpriority: 0or 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
ENV variables
ENV variables control some 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"
When using NPM package lefthook in CI, and your CI sets CI=true automatically, use LEFTHOOK=1 or LEFTHOOK=true to install hooks in the postinstall script:
Example
LEFTHOOK=1 npm install
LEFTHOOK=1 yarn install
LEFTHOOK=1 pnpm install
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
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_VERBOSE
Set LEFTHOOK_VERBOSE=1 or LEFTHOOK_VERBOSE=true to enable verbose printing.
LEFTHOOK_CONFIG
Override the main lefthook config with LEFTHOOK_CONFIG=~/global_lefthook.yml. Note: local config, specified extends, and remotes will still be loaded.
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"
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.
CI
When using NPM package lefthook, set CI=true in your CI (if it does not set it automatically) to prevent lefthook from installing hooks in the postinstall script:
CI=true npm install
CI=true yarn install
CI=true pnpm install
Note: Set
LEFTHOOK=1orLEFTHOOK=trueto override this behavior and install hooks in the postinstall script (despiteCI=true).
LEFTHOOK
Use LEFTHOOK=0 git ... or LEFTHOOK=false git ... to disable lefthook when running git commands.
Example
LEFTHOOK=0 git commit -am "Lefthook skipped"
When using NPM package lefthook in CI, and your CI sets CI=true automatically, use LEFTHOOK=1 or LEFTHOOK=true to install hooks in the postinstall script:
Example
LEFTHOOK=1 npm install
LEFTHOOK=1 yarn install
LEFTHOOK=1 pnpm install
LEFTHOOK_VERBOSE
Set LEFTHOOK_VERBOSE=1 or LEFTHOOK_VERBOSE=true to enable verbose printing.
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_CONFIG
Override the main lefthook config with LEFTHOOK_CONFIG=~/global_lefthook.yml. Note: local config, specified extends, and remotes will still be loaded.
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"
CLICOLOR_FORCE
Set CLICOLOR_FORCE=true to force colored output in lefthook and all subcommands.
NO_COLOR
Set NO_COLOR=true to disable colored output in lefthook and all subcommands that lefthook calls.
CI
When using NPM package lefthook, set CI=true in your CI (if it does not set it automatically) to prevent lefthook from installing hooks in the postinstall script:
CI=true npm install
CI=true yarn install
CI=true pnpm install
Note: Set
LEFTHOOK=1orLEFTHOOK=trueto override this behavior and install hooks in the postinstall script (despiteCI=true).
lefthook-local.yml
Tip: You can put
lefthook-local.ymlinto your~/.gitignore, so in every project you can have your local-only overrides.
lefthook-local.yml overrides and extends the configuration of your main lefthook.yml.
# lefthook.yml
pre-commit:
commands:
lint:
run: bundle exec rubocop {staged_files}
glob: "*.rb"
check-links:
run: lychee {staged_files}
# lefthook-local.yml
pre-commit:
parallel: true # run all commands concurrently
commands:
lint:
run: docker-compose run backend {cmd} # wrap the original command with docker-compose
check-links:
skip: true # skip checking links
# Add another hook
post-merge:
files: "git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD"
commands:
dependencies:
glob: "Gemfile*"
run: docker-compose run backend bundle install
The merged config lefthook will use
pre-commit:
parallel: true
commands:
lint:
run: docker-compose run backend bundle exec rubocop {staged_files}
glob: "*.rb"
check-links:
run: lychee {staged_files}
skip: true
post-merge:
files: "git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD"
commands:
dependencies:
glob: "Gemfile*"
run: docker-compose run backend bundle install
Wrap commands in local config
Wrapping some commands defined in a main config with dip1.
# lefthook.yml
pre-commit:
jobs:
- name: rubocop
run: bundle exec rubocop -A {staged_files}
# lefthook-local.yml
pre-commit:
jobs:
- name: rubocop
run: dip {cmd}
dip – dockerized dev experience with, similar to docker-compose run
Stage fixed files
Works only for
pre-commitGit 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
Skip or run on condition
Here are two hooks.
pre-commit hook will only be executed when you're committing something on a branch starting with def/ prefix.
In pre-push hook:
testcommand will be skipped ifNO_TESTenv variable is set to1lintcommand will only be executed if you're pushing themainbranch
# lefthook.yml
pre-commit:
only:
- ref: dev/*
commands:
lint:
run: yarn lint {staged_files} --fix
glob: "*.{ts,js}"
test:
run: yarn test
pre-push:
commands:
test:
run: yarn test
skip:
- run: test "$NO_TEST" -eq 1
lint:
run: yarn lint
only:
- ref: main
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
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
# For commitzen
yarn add -D commitizen cz-conventional-changelog
Configure
Setup commitlint.config.js. Conventional configuration:
// commitlint.config.js
module.exports = {extends: ['@commitlint/config-conventional']};
If you are using commitzen, make sure to add this in package.json:
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
Configure lefthook:
# lefthook.yml
# Build commit messages
prepare-commit-msg:
commands:
commitzen:
interactive: true
run: yarn run cz --hook # Or npx cz --hook
env:
LEFTHOOK: 0
# Validate commit messages
commit-msg:
commands:
"lint commit message":
run: yarn run commitlint --edit {1}
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'
Contributors
- Arkweid
- Envek
- mrexox
- skryukov
- scop
- hyperupcall
- MartijnCuppens
- palkan
- markovichecha
- technicalpickles
- aminya
- prog-supdex
- HellSquirrel
- Evilweed
- PikachuEXE
- jsmestad
- DmitryTsepelev
- pmirecki
- 0legovich
- zachahn
- sitiom
- spearmootz
- pwinckles
- pablobirukov
- nihalgonsalves
- nesk
- jaydorsey
- fantua
- orsinium
- fabn
If you feel you’re missing from this list, feel free to add yourself in a PR.