Add Lefthook to your system or build it from sources.
go get github.com/Arkweid/lefthooknpm i @arkweid/lefthook --save-dev
# or yarn:
yarn add -D @arkweid/lefthookNOTE: 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)
gem install lefthookbrew install Arkweid/lefthook/lefthookYou can install lefthook package from AUR
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 installIt 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-pushDescribe pre-push commands in lefthook.yml:
pre-push: # githook name
commands: # list of commands
packages-audit: # command name
run: yarn audit # command for executionThat'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}"
run: yarn eslintIn 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}" # 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 filesNote: 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-msgThis 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: rubyLet'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
fiNow 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: bashWhen 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: trueIf 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 auditYou can skip commands by tags:
# lefthook-local.yml
pre-push:
exclude_tags:
- frontendIf 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:seedIf 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.ymlNOTE: 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: nodeYou 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-commitYou 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/javascriptsThen call this group directly:
lefthook run lint
# lefthook.yml
color: false
extends: $HOME/work/lefthook-extend.yml
pre-commit:
commands:
eslint:
glob: "*.{js,ts}"
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: trueWe 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"Enable CI env variable if it doens't exists on your service by default.
By agrs:
lefthook --no-colors run pre-commitBy config lefthook.yml, just add the option:
colors: falselefthook versionlefthook uninstallHave a question? Check the wiki.