Installation
Add Lefthook to your system or build it from sources.
go
go get github.com/Arkweid/lefthooknpm
npm 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)
Rubygems
gem install lefthookHomebrew for macOS
brew install Arkweid/lefthook/lefthookAUR for Arch
You can install lefthook package from AUR
Or take it from binaries and install manually
Scenarios
Examples
We have a directory with few examples. You can check it here.
First time user
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.
If you already have a lefthook config file
Just initialize lefthook to make it work :)
lefthook installMore options
Use glob patterns to choose what files you want to check
# lefthook.yml
pre-commit:
commands:
lint:
glob: "*.{js,ts}"
run: yarn eslintSelect specific file groups
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}" # 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.
Custom file list
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
Git hook argument shorthands in commands
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
Managing scripts
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: rubyBash script 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
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.
Local config
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)
Skipping commands
You can skip commands by skip option:
# lefthook-local.yml
pre-push:
commands:
packages-audit:
skip: trueSkipping commands by tags
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 auditYou can skip commands by tags:
# lefthook-local.yml
pre-push:
exclude_tags:
- frontendPiped option
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:seedExtends option
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.ymlNOTE: Files for extend should have name NOT a "lefthook.yml" and should be unique.
Referencing commands from lefthook.yml
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
Run githook group directly
lefthook run pre-commitParallel execution
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/javascriptsThen call this group directly:
lefthook run lint
Complete example
# 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: trueSkip lefthook execution
We can set env variable LEFTHOOK to zero for that
LEFTHOOK=0 git commit -am "Lefthook skipped"Skip some tags on the fly
Use LEFTHOOK_EXCLUDE={list of tags to be excluded} for that
LEFTHOOK_EXCLUDE=ruby,security git commit -am "Skip some tag checks"Concurrent files overrides
To prevent concurrent problems with read/write files try flock
utility.
# lefthook.yml
graphql-schema:
glob: "{Gemfile.lock,app/graphql/**/*}"
run: flock webpack/application/typings/graphql-schema.json yarn typings:update && git diff --exit-code --stat HEAD webpack/application/typings
frontend-tests:
glob: "**/*.js"
run: flock -s webpack/application/typings/graphql-schema.json yarn test --findRelatedTests {files}
frontend-typings:
glob: "**/*.js"
run: flock -s webpack/application/typings/graphql-schema.json yarn run flow focus-check {files}Capture ARGS from git in the script
Example script for prepare-commit-msg hook:
COMMIT_MSG_FILE=$1
COMMIT_SOURCE=$2
SHA1=$3
# ...Change directory for script files
You can do this through this config keys:
# lefthook.yml
source_dir: ".lefthook"
source_dir_local: ".lefthook-local"CI integration
Enable CI env variable if it doens't exists on your service by default.
Disable colors
By agrs:
lefthook --no-colors run pre-commitBy config lefthook.yml, just add the option:
colors: falseVersion
lefthook versionUninstall
lefthook uninstallMore info
Have a question? Check the wiki.