Add Lefthook to your system or build it from sources.
go get github.com/evilmartians/lefthook
npm i @arkweid/lefthook --save-dev
# or yarn:
yarn add -D @arkweid/lefthook
NOTE: if you install it this way you should call it with npx
or yarn
for all listed examples below. (for example: lefthook install
-> npx lefthook install
)
You can also install lefthook as a global dependency
npm install -g @arkweid/lefthook
gem install lefthook
brew install lefthook
You can install lefthook package from AUR
snap install --classic lefthook
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
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
Or take it from binaries and install manually
We have a directory with few examples. You can check it here.
Initialize lefthook with the following command
lefthook install
It creates lefthook.yml
in the project root directory
Register your hook (You can choose any hook from this list)
In our example it pre-push
githook:
lefthook add pre-push
Describe pre-push commands in lefthook.yml
:
pre-push: # githook name
commands: # list of commands
packages-audit: # command name
run: yarn audit # command for execution
That's all! Now on git push
the yarn audit
command will be run.
If it fails the git push
will be interrupted.
Just initialize lefthook to make it work :)
lefthook install
# lefthook.yml
pre-commit:
commands:
lint:
glob: "*.{js,ts,jsx,tsx}"
run: yarn eslint
In some cases you want to run checks only against some specific file group. For example: you may want to run eslint for staged files only.
There are two shorthands for such situations:
{staged_files}
- staged git files which you try to commit
{all_files}
- all tracked files by git
# lefthook.yml
pre-commit:
commands:
frontend-linter:
glob: "*.{js,ts,jsx,tsx}" # glob filter for list of files
run: yarn eslint {staged_files} # {staged_files} - list of files
backend-linter:
glob: "*.rb" # glob filter for list of files
exclude: "application.rb|routes.rb" # regexp filter for list of files
run: bundle exec rubocop --force-exclusion {all_files} # {all_files} - list of files
Note: If using all_files
with RuboCop, it will ignore RuboCop's Exclude
configuration setting. To avoid this, pass --force-exclusion
.
Lefthook can be even more specific in selecting files. If you want to choose diff of all changed files between the current branch and master branch you can do it this way:
# lefthook.yml
pre-push:
commands:
frontend-style:
files: git diff --name-only master # custom list of files
glob: "*.js"
run: yarn stylelint {files}
{files}
- shorthand for a custom list of files
If you want to use the original Git hook arguments in a command you can do it using the indexed shorthands:
# 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'
{0}
- shorthand for the single space-joint string of Git hook arguments
{i}
- shorthand for the i-th Git hook argument
If you run lefthook add
command with -d
flag, lefthook will create two directories where you can put scripts and reference them from lefthook.yml
file.
Example:
Let's create commit-msg
hook with -d
flag
lefthook add -d commit-msg
This command will create .lefthook/commit-msg
and .lefthook-local/commit-msg
dirs.
The first one is for common project level scripts.
The second one is for personal scripts. It would be a good idea to add dir.lefthook-local
to .gitignore
.
Create scripts .lefthook/commit-msg/hello.js
and .lefthook/commit-msg/hi.rb
# lefthook.yml
commit-msg:
scripts:
"hello.js":
runner: node
"hi.rb":
runner: ruby
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 "haha bad commit text"
script template_checker
will be executed. Since commit text doesn't match the described pattern the commit process will be interrupted.
We can use lefthook-local.yml
as local config. Options in this file will overwrite options in lefthook.yml
. (Don't forget to add this file to .gitignore
)
You can skip commands by skip
option:
# lefthook-local.yml
pre-push:
commands:
packages-audit:
skip: true
You can skip commands during rebase and/or merge by the same skip
option:
pre-push:
commands:
packages-audit:
skip: merge
# or
pre-push:
commands:
packages-audit:
skip:
- merge
- rebase
If we have a lot of commands and scripts we can tag them and run skip commands with a specific tag.
For example, if we have lefthook.yml
like this:
# 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
If any command in the sequence fails, the other will not be executed.
# lefthook.yml
database:
piped: true
commands:
1_create:
run: rake db:create
2_migrate:
run: rake db:migrate
3_seed:
run: rake db:seed
If you need to extend config from some another place, just add top level:
# lefthook.yml
extends:
- $HOME/work/lefthook-extend.yml
- $HOME/work/lefthook-extend-2.yml
NOTE: Files for extend should have name NOT a "lefthook.yml" and should be unique.
If you have the following config
# lefthook.yml
pre-commit:
scripts:
"good_job.js":
runner: node
You can wrap it in docker runner locally:
# lefthook-local.yml
pre-commit:
scripts:
"good_job.js":
runner: docker run -it --rm <container_id_or_name> {cmd}
{cmd}
- shorthand for the command from lefthook.yml
lefthook run pre-commit
You can enable parallel execution if you want to speed up your checks. Lets get example from discourse project.
bundle exec rubocop --parallel && \
bundle exec danger && \
yarn eslint --ext .es6 app/assets/javascripts && \
yarn eslint --ext .es6 test/javascripts && \
yarn eslint --ext .es6 plugins/**/assets/javascripts && \
yarn eslint --ext .es6 plugins/**/test/javascripts && \
yarn eslint app/assets/javascripts test/javascripts
Rewrite it in lefthook custom group. We call it lint
:
# lefthook.yml
lint:
parallel: true
commands:
rubocop:
run: bundle exec rubocop --parallel
danger:
run: bundle exec danger
eslint-assets:
run: yarn eslint --ext .es6 app/assets/javascripts
eslint-test:
run: yarn eslint --ext .es6 test/javascripts
eslint-plugins-assets:
run: yarn eslint --ext .es6 plugins/**/assets/javascripts
eslint-plugins-test:
run: yarn eslint --ext .es6 plugins/**/test/javascripts
eslint-assets-tests:
run: yarn eslint app/assets/javascripts test/javascripts
Then call this group directly:
lefthook run lint
# lefthook.yml
color: false
extends: $HOME/work/lefthook-extend.yml
pre-commit:
commands:
eslint:
glob: "*.{js,ts,jsx,tsx}"
run: yarn eslint {staged_files}
rubocop:
tags: backend style
glob: "*.rb"
exclude: "application.rb|routes.rb"
run: bundle exec rubocop --force-exclusion {all_files}
govet:
tags: backend style
files: git ls-files -m
glob: "*.go"
run: go vet {files}
scripts:
"hello.js":
runner: node
"any.go":
runner: go run
parallel: true
# lefthook-local.yml
pre-commit:
exclude_tags:
- backend
scripts:
"hello.js":
runner: docker run -it --rm <container_id_or_name> {cmd}
commands:
govet:
skip: true
We can set env variable LEFTHOOK
to zero for that
LEFTHOOK=0 git commit -am "Lefthook skipped"
Use LEFTHOOK_EXCLUDE={list of tags to be excluded} for that
LEFTHOOK_EXCLUDE=ruby,security git commit -am "Skip some tag checks"
To prevent concurrent problems with read/write files try flock
utility.
# lefthook.yml
graphql-schema:
glob: "{Gemfile.lock,app/graphql/**/*}"
run: flock webpack/application/typings/graphql-schema.json yarn typings:update && git diff --exit-code --stat HEAD webpack/application/typings
frontend-tests:
glob: "**/*.js"
run: flock -s webpack/application/typings/graphql-schema.json yarn test --findRelatedTests {files}
frontend-typings:
glob: "**/*.js"
run: flock -s webpack/application/typings/graphql-schema.json yarn run flow focus-check {files}
Example script for prepare-commit-msg
hook:
COMMIT_MSG_FILE=$1
COMMIT_SOURCE=$2
SHA1=$3
# ...
You can do this through this config keys:
# lefthook.yml
source_dir: ".lefthook"
source_dir_local: ".lefthook-local"
You can manage the verbosity using the skip_output
config.
Possible values are meta,success,failure,summary
.
This config quiets all outputs except failures:
# lefthook.yml
skip_output:
- meta
- success
- summary
You can also do this with an environment variable:
export LEFTHOOK_QUIET="meta,success,summary"
Enable CI
env variable if it doens't exists on your service by default.
By agrs:
lefthook --no-colors run pre-commit
By config lefthook.yml
, just add the option:
colors: false
lefthook version
lefthook uninstall
Have a question? Check the wiki.