From c1393d93f474dcbc171b7b27d99b67f947fbd5f8 Mon Sep 17 00:00:00 2001 From: Valentin Kiselev Date: Tue, 3 Dec 2024 11:45:45 +0300 Subject: [PATCH] chore!: replace viper with koanf (#813) * chore: replace viper with koanf * fix: refactor load.go * fix: rewrite basic load of configs in koanf * fix: load with koanf (no complicated load yet) * fix: load with koanf (no complicated load yet) * fix: add kaonf provider * fix: merge local extends too * fix: add special handling for {cmd} * perf: allocate destCommands when we know the len of commands * fix: remove ToUpper for envs, since koanf respects the registry * chore: go mod tidy * chore: remove any reference to viper * docs: fix typo * docs: avoid comma separated tags --- docs/configuration.md | 30 +- go.mod | 24 +- go.sum | 56 +-- internal/config/command.go | 83 +--- internal/config/config.go | 12 +- internal/config/hook.go | 68 +--- internal/config/load.go | 361 ++++++++++-------- internal/config/load_test.go | 8 +- internal/config/remote.go | 12 +- internal/config/script.go | 110 ------ internal/lefthook/run.go | 4 +- internal/lefthook/runner/exec/execute_unix.go | 3 +- .../lefthook/runner/exec/execute_windows.go | 2 +- 13 files changed, 288 insertions(+), 485 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 84eae07a..5a5fa833 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -581,13 +581,13 @@ pre-commit: exclude_tags: frontend commands: lint: - tag: frontend + tags: frontend ... test: - tag: frontend + tags: frontend ... check-syntax: - tag: documentation + tags: documentation ``` ```bash @@ -604,10 +604,14 @@ This option is good to specify in `lefthook-local.yml` when you want to skip som pre-push: commands: packages-audit: - tags: frontend security + tags: + - frontend + - security run: yarn audit gems-audit: - tags: backend security + tags: + - backend + - security run: bundle audit ``` @@ -747,7 +751,9 @@ Simply run `bundle exec rubocop` on all files with `.rb` extension excluding `ap pre-commit: commands: rubocop: - tags: backend style + tags: + - backend + - style glob: "*.rb" exclude: - config/application.rb @@ -1012,10 +1018,14 @@ You can specify tags for commands and scripts. This is useful for [excluding](#e pre-commit: commands: lint: - tags: frontend,js + tags: + - frontend + - js run: yarn lint test: - tags: backend,ruby + tags: + - backend + - ruby run: bundle exec rspec ``` @@ -1069,7 +1079,9 @@ Provide a git command to list files. pre-push: commands: stylelint: - tags: frontend style + tags: + - frontend + - style files: git diff --name-only master glob: "*.js" run: yarn stylelint {files} diff --git a/go.mod b/go.mod index bc9e6ebe..21931c2e 100644 --- a/go.mod +++ b/go.mod @@ -10,15 +10,19 @@ require ( github.com/charmbracelet/lipgloss v1.0.0 github.com/creack/pty v1.1.24 github.com/gobwas/glob v0.2.3 + github.com/knadh/koanf/maps v0.1.1 + github.com/knadh/koanf/parsers/json v0.1.0 + github.com/knadh/koanf/parsers/toml/v2 v2.1.0 + github.com/knadh/koanf/parsers/yaml v0.1.0 + github.com/knadh/koanf/providers/fs v0.1.0 + github.com/knadh/koanf/v2 v2.1.1 github.com/mattn/go-tty v0.0.7 github.com/mitchellh/mapstructure v1.5.0 github.com/rogpeppe/go-internal v1.13.1 github.com/schollz/progressbar/v3 v3.17.1 github.com/spf13/afero v1.11.0 github.com/spf13/cobra v1.8.1 - github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 gopkg.in/alessio/shellescape.v1 v1.0.0-20170105083845-52074bc9df61 ) @@ -26,36 +30,30 @@ require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/charmbracelet/x/ansi v0.4.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/go-viper/mapstructure/v2 v2.1.0 // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/sagikazarmark/locafero v0.4.0 // indirect - github.com/sagikazarmark/slog-shim v0.1.0 // indirect - github.com/sourcegraph/conc v0.3.0 // indirect - go.uber.org/atomic v1.9.0 // indirect - go.uber.org/multierr v1.9.0 // indirect golang.org/x/tools v0.22.0 // indirect + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect ) require ( github.com/alessio/shellescape v1.4.1 // indirect github.com/fatih/color v1.14.1 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 github.com/mattn/go-runewidth v0.0.16 // indirect github.com/muesli/termenv v0.15.2 // indirect github.com/pelletier/go-toml/v2 v2.2.3 github.com/rivo/uniseg v0.4.7 // indirect - github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/subosito/gotenv v1.6.0 // indirect golang.org/x/sys v0.27.0 // indirect golang.org/x/term v0.26.0 // indirect golang.org/x/text v0.14.0 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 2b7ce187..016b2c8d 100644 --- a/go.sum +++ b/go.sum @@ -13,34 +13,37 @@ github.com/charmbracelet/x/ansi v0.4.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoC github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM= github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= -github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= -github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/go-viper/mapstructure/v2 v2.1.0 h1:gHnMa2Y/pIxElCH2GlZZ1lZSsn6XMtufpGyP1XxdC/w= +github.com/go-viper/mapstructure/v2 v2.1.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs= +github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= +github.com/knadh/koanf/parsers/json v0.1.0 h1:dzSZl5pf5bBcW0Acnu20Djleto19T0CfHcvZ14NJ6fU= +github.com/knadh/koanf/parsers/json v0.1.0/go.mod h1:ll2/MlXcZ2BfXD6YJcjVFzhG9P0TdJ207aIBKQhV2hY= +github.com/knadh/koanf/parsers/toml/v2 v2.1.0 h1:EUdIKIeezfDj6e1ABDhIjhbURUpyrP1HToqW6tz8R0I= +github.com/knadh/koanf/parsers/toml/v2 v2.1.0/go.mod h1:0KtwfsWJt4igUTQnsn0ZjFWVrP80Jv7edTBRbQFd2ho= +github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w= +github.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPuccrNBS2bps8asS0CwY= +github.com/knadh/koanf/providers/fs v0.1.0 h1:9Hln9GS3bWTItAnGVFYyfkoAIxAFq7pvlF64pTNiDdQ= +github.com/knadh/koanf/providers/fs v0.1.0/go.mod h1:Cva1yH8NBxkEeVZx8CUmF5TunbgO72E+GwqDbqpP2sE= +github.com/knadh/koanf/v2 v2.1.1 h1:/R8eXqasSTsmDCsAyYj+81Wteg8AqrV9CP6gvsTsOmM= +github.com/knadh/koanf/v2 v2.1.1/go.mod h1:4mnTRbZCK+ALuBXHZMjDfG9y714L7TykVnZkXbMU3Es= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -52,51 +55,36 @@ github.com/mattn/go-tty v0.0.7 h1:KJ486B6qI8+wBO7kQxYgmmEFDaFEE96JMBQ7h400N8Q= github.com/mattn/go-tty v0.0.7/go.mod h1:f2i5ZOvXBU/tCABmLmOfzLz9azMo5wdAaElRNnJKr+k= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= -github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= -github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= -github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/schollz/progressbar/v3 v3.17.1 h1:bI1MTaoQO+v5kzklBjYNRQLoVpe0zbyRZNK6DFkVC5U= github.com/schollz/progressbar/v3 v3.17.1/go.mod h1:RzqpnsPQNjUyIgdglUjRLgD7sVnxN1wpmBMV+UiEbL4= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= -github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= -github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= -go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= @@ -112,7 +100,5 @@ gopkg.in/alessio/shellescape.v1 v1.0.0-20170105083845-52074bc9df61/go.mod h1:IfM gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/config/command.go b/internal/config/command.go index 8a2ca4da..4f51dd5c 100644 --- a/internal/config/command.go +++ b/internal/config/command.go @@ -2,9 +2,6 @@ package config import ( "errors" - "strings" - - "github.com/spf13/viper" "github.com/evilmartians/lefthook/internal/git" "github.com/evilmartians/lefthook/internal/system" @@ -21,21 +18,17 @@ type Command struct { Tags []string `json:"tags,omitempty" mapstructure:"tags" toml:"tags,omitempty" yaml:",omitempty"` Env map[string]string `json:"env,omitempty" mapstructure:"env" toml:"env,omitempty" yaml:",omitempty"` - FileTypes []string `json:"file_types,omitempty" mapstructure:"file_types" toml:"file_types,omitempty" yaml:"file_types,omitempty"` + FileTypes []string `json:"file_types,omitempty" koanf:"file_types" mapstructure:"file_types" toml:"file_types,omitempty" yaml:"file_types,omitempty"` Glob string `json:"glob,omitempty" mapstructure:"glob" toml:"glob,omitempty" yaml:",omitempty"` Root string `json:"root,omitempty" mapstructure:"root" toml:"root,omitempty" yaml:",omitempty"` Exclude interface{} `json:"exclude,omitempty" mapstructure:"exclude" toml:"exclude,omitempty" yaml:",omitempty"` Priority int `json:"priority,omitempty" mapstructure:"priority" toml:"priority,omitempty" yaml:",omitempty"` - FailText string `json:"fail_text,omitempty" mapstructure:"fail_text" toml:"fail_text,omitempty" yaml:"fail_text,omitempty"` + FailText string `json:"fail_text,omitempty" koanf:"fail_text" mapstructure:"fail_text" toml:"fail_text,omitempty" yaml:"fail_text,omitempty"` Interactive bool `json:"interactive,omitempty" mapstructure:"interactive" toml:"interactive,omitempty" yaml:",omitempty"` UseStdin bool `json:"use_stdin,omitempty" mapstructure:"use_stdin" toml:"use_stdin,omitempty" yaml:",omitempty"` - StageFixed bool `json:"stage_fixed,omitempty" mapstructure:"stage_fixed" toml:"stage_fixed,omitempty" yaml:"stage_fixed,omitempty"` -} - -type commandRunReplace struct { - Run string `mapstructure:"run"` + StageFixed bool `json:"stage_fixed,omitempty" koanf:"stage_fixed" mapstructure:"stage_fixed" toml:"stage_fixed,omitempty" yaml:"stage_fixed,omitempty"` } func (c Command) Validate() error { @@ -54,73 +47,3 @@ func (c Command) DoSkip(state func() git.State) bool { func (c Command) ExecutionPriority() int { return c.Priority } - -func mergeCommands(base, extra *viper.Viper) (map[string]*Command, error) { - if base == nil && extra == nil { - return nil, nil - } - - if base == nil { - return unmarshalCommands(extra.Sub("commands")) - } - - if extra == nil { - return unmarshalCommands(base.Sub("commands")) - } - - commandsOrigin := base.Sub("commands") - commandsOverride := extra.Sub("commands") - if commandsOrigin == nil { - return unmarshalCommands(commandsOverride) - } - if commandsOverride == nil { - return unmarshalCommands(commandsOrigin) - } - - runReplaces := make(map[string]*commandRunReplace) - for key := range commandsOrigin.AllSettings() { - var replace commandRunReplace - - substructure := commandsOrigin.Sub(key) - if substructure == nil { - continue - } - - if err := substructure.Unmarshal(&replace); err != nil { - return nil, err - } - - runReplaces[key] = &replace - } - - err := commandsOrigin.MergeConfigMap(commandsOverride.AllSettings()) - if err != nil { - return nil, err - } - - commands, err := unmarshalCommands(commandsOrigin) - if err != nil { - return nil, err - } - - for key, replace := range runReplaces { - if replace.Run != "" { - commands[key].Run = strings.ReplaceAll(commands[key].Run, CMD, replace.Run) - } - } - - return commands, nil -} - -func unmarshalCommands(v *viper.Viper) (map[string]*Command, error) { - if v == nil { - return nil, nil - } - - commands := make(map[string]*Command) - if err := v.Unmarshal(&commands); err != nil { - return nil, err - } - - return commands, nil -} diff --git a/internal/config/config.go b/internal/config/config.go index aab7e5e9..7aa1dc38 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -27,15 +27,15 @@ const ( ) type Config struct { - MinVersion string `mapstructure:"min_version,omitempty"` - SourceDir string `mapstructure:"source_dir"` - SourceDirLocal string `mapstructure:"source_dir_local"` + MinVersion string `koanf:"min_version" mapstructure:"min_version,omitempty"` + SourceDir string `koanf:"source_dir" mapstructure:"source_dir"` + SourceDirLocal string `koanf:"source_dir_local" mapstructure:"source_dir_local"` Rc string `mapstructure:"rc,omitempty"` - SkipOutput interface{} `mapstructure:"skip_output,omitempty"` + SkipOutput interface{} `koanf:"skip_output" mapstructure:"skip_output,omitempty"` Output interface{} `mapstructure:"output,omitempty"` Extends []string `mapstructure:"extends,omitempty"` - NoTTY bool `mapstructure:"no_tty,omitempty"` - AssertLefthookInstalled bool `mapstructure:"assert_lefthook_installed,omitempty"` + NoTTY bool `koanf:"no_tty" mapstructure:"no_tty,omitempty"` + AssertLefthookInstalled bool `koanf:"assert_lefthook_installed" mapstructure:"assert_lefthook_installed,omitempty"` Colors interface{} `mapstructure:"colors,omitempty"` SkipLFS bool `mapstructure:"skip_lfs,omitempty"` diff --git a/internal/config/hook.go b/internal/config/hook.go index 4e970b22..15278f62 100644 --- a/internal/config/hook.go +++ b/internal/config/hook.go @@ -2,10 +2,6 @@ package config import ( "errors" - "os" - "strings" - - "github.com/spf13/viper" "github.com/evilmartians/lefthook/internal/git" "github.com/evilmartians/lefthook/internal/system" @@ -16,23 +12,16 @@ const CMD = "{cmd}" var errPipedAndParallelSet = errors.New("conflicting options 'piped' and 'parallel' are set to 'true', remove one of this option from hook group") type Hook struct { - // Should be unmarshalled with `mapstructure:"commands"` - // But replacing '{cmd}' is still an issue - // Unmarshalling it manually, so omit auto unmarshalling Commands map[string]*Command `json:"commands,omitempty" mapstructure:"-" toml:"commands,omitempty" yaml:",omitempty"` - - // Should be unmarshalled with `mapstructure:"scripts"` - // But parsing keys with dots in it is still an issue: https://github.com/spf13/viper/issues/324 - // Unmarshalling it manually, so omit auto unmarshalling - Scripts map[string]*Script `json:"scripts,omitempty" mapstructure:"-" toml:"scripts,omitempty" yaml:",omitempty"` - - Files string `json:"files,omitempty" mapstructure:"files" toml:"files,omitempty" yaml:",omitempty"` - Parallel bool `json:"parallel,omitempty" mapstructure:"parallel" toml:"parallel,omitempty" yaml:",omitempty"` - Piped bool `json:"piped,omitempty" mapstructure:"piped" toml:"piped,omitempty" yaml:",omitempty"` - Follow bool `json:"follow,omitempty" mapstructure:"follow" toml:"follow,omitempty" yaml:",omitempty"` - ExcludeTags []string `json:"exclude_tags,omitempty" mapstructure:"exclude_tags" toml:"exclude_tags,omitempty" yaml:"exclude_tags,omitempty"` - Skip interface{} `json:"skip,omitempty" mapstructure:"skip" toml:"skip,omitempty,inline" yaml:",omitempty"` - Only interface{} `json:"only,omitempty" mapstructure:"only" toml:"only,omitempty,inline" yaml:",omitempty"` + Scripts map[string]*Script `json:"scripts,omitempty" mapstructure:"-" toml:"scripts,omitempty" yaml:",omitempty"` + + Files string `json:"files,omitempty" mapstructure:"files" toml:"files,omitempty" yaml:",omitempty"` + Parallel bool `json:"parallel,omitempty" mapstructure:"parallel" toml:"parallel,omitempty" yaml:",omitempty"` + Piped bool `json:"piped,omitempty" mapstructure:"piped" toml:"piped,omitempty" yaml:",omitempty"` + Follow bool `json:"follow,omitempty" mapstructure:"follow" toml:"follow,omitempty" yaml:",omitempty"` + ExcludeTags []string `json:"exclude_tags,omitempty" koanf:"exclude_tags" mapstructure:"exclude_tags" toml:"exclude_tags,omitempty" yaml:"exclude_tags,omitempty"` + Skip interface{} `json:"skip,omitempty" mapstructure:"skip" toml:"skip,omitempty,inline" yaml:",omitempty"` + Only interface{} `json:"only,omitempty" mapstructure:"only" toml:"only,omitempty,inline" yaml:",omitempty"` } func (h *Hook) Validate() error { @@ -47,42 +36,3 @@ func (h *Hook) DoSkip(state func() git.State) bool { skipChecker := NewSkipChecker(system.Cmd) return skipChecker.check(state, h.Skip, h.Only) } - -func unmarshalHooks(base, extra *viper.Viper) (*Hook, error) { - if base == nil && extra == nil { - return nil, nil - } - - commands, err := mergeCommands(base, extra) - if err != nil { - return nil, err - } - - scripts, err := mergeScripts(base, extra) - if err != nil { - return nil, err - } - - hook := Hook{ - Commands: commands, - Scripts: scripts, - } - - if base == nil { - base = extra - } else if extra != nil { - if err = base.MergeConfigMap(extra.AllSettings()); err != nil { - return nil, err - } - } - - if err := base.Unmarshal(&hook); err != nil { - return nil, err - } - - if tags := os.Getenv("LEFTHOOK_EXCLUDE"); tags != "" { - hook.ExcludeTags = append(hook.ExcludeTags, strings.Split(tags, ",")...) - } - - return &hook, nil -} diff --git a/internal/config/load.go b/internal/config/load.go index e8f0fb98..7946f0ba 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -3,13 +3,20 @@ package config import ( "errors" "fmt" + "io/fs" + "os" "path/filepath" "regexp" "slices" "strings" + "github.com/knadh/koanf/maps" + "github.com/knadh/koanf/parsers/json" + "github.com/knadh/koanf/parsers/toml/v2" + "github.com/knadh/koanf/parsers/yaml" + kfs "github.com/knadh/koanf/providers/fs" + "github.com/knadh/koanf/v2" "github.com/spf13/afero" - "github.com/spf13/viper" "github.com/evilmartians/lefthook/internal/git" "github.com/evilmartians/lefthook/internal/log" @@ -24,148 +31,120 @@ const ( var ( hookKeyRegexp = regexp.MustCompile(`^(?P[^.]+)\.(scripts|commands)`) localConfigNames = []string{"lefthook-local", ".lefthook-local"} + mainConfigNames = []string{"lefthook", ".lefthook"} + extensions = []string{ + ".yml", + ".yaml", + ".json", + ".toml", + } + parsers = map[string]koanf.Parser{ + ".yml": yaml.Parser(), + ".yaml": yaml.Parser(), + ".json": json.Parser(), + ".toml": toml.Parser(), + } ) -// NotFoundError wraps viper.ConfigFileNotFoundError for lefthook. -type NotFoundError struct { +// ConfigNotFoundError. +type ConfigNotFoundError struct { message string } -// Error returns message of viper.ConfigFileNotFoundError. -func (err NotFoundError) Error() string { +func (err ConfigNotFoundError) Error() string { return err.message } -// Loads configs from the given directory with extensions. -func Load(fs afero.Fs, repo *git.Repository) (*Config, error) { - global, err := readOne(fs, repo.RootPath, []string{"lefthook", ".lefthook"}) - if err != nil { - return nil, err - } - - extends, err := mergeAll(fs, repo) - if err != nil { - return nil, err - } - - var config Config +func loadOne(k *koanf.Koanf, filesystem afero.Fs, root string, names []string) error { + for _, extension := range extensions { + for _, name := range names { + config := filepath.Join(root, name+extension) + if ok, _ := afero.Exists(filesystem, config); !ok { + continue + } - config.SourceDir = DefaultSourceDir - config.SourceDirLocal = DefaultSourceDirLocal + if err := k.Load(kfs.Provider(newIOFS(filesystem), config), parsers[extension]); err != nil { + return err + } - err = unmarshalConfigs(global, extends, &config) - if err != nil { - return nil, err + return nil + } } - log.SetColors(config.Colors) - return &config, nil + return ConfigNotFoundError{fmt.Sprintf("No config files with names %q have been found in \"%s\"", names, root)} } -func read(fs afero.Fs, path string, name string) (*viper.Viper, error) { - v := newViper(fs, path) - v.SetConfigName(name) +// Loads configs from the given directory with extensions. +func Load(filesystem afero.Fs, repo *git.Repository) (*Config, error) { + main := koanf.New(".") - if err := v.ReadInConfig(); err != nil { + // Load main (e.g. lefthook.yml) + if err := loadOne(main, filesystem, repo.RootPath, mainConfigNames); err != nil { return nil, err } - return v, nil -} - -func newViper(fs afero.Fs, path string) *viper.Viper { - v := viper.New() - v.SetFs(fs) - v.AddConfigPath(path) - - // Allow overwriting settings with ENV variables - v.SetEnvPrefix("LEFTHOOK") - v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) - v.AutomaticEnv() - - return v -} - -func readOne(fs afero.Fs, path string, names []string) (*viper.Viper, error) { - for _, name := range names { - v, err := read(fs, path, name) - if err != nil { - var notFoundErr viper.ConfigFileNotFoundError - if ok := errors.As(err, ¬FoundErr); ok { - continue - } + // Save `extends` and `remotes` + extends := main.Strings("extends") + var remotes []*Remote + if err := main.Unmarshal("remotes", &remotes); err != nil { + return nil, err + } - return nil, err - } + // Deprecated + var remote *Remote + if err := main.Unmarshal("remote", &remote); err != nil { + return nil, err + } - return v, nil + // Backward compatibility for `remote`. Will be deleted in future major release + if remote != nil { + remotes = append(remotes, remote) } - return nil, NotFoundError{fmt.Sprintf("No config files with names %q have been found in \"%s\"", names, path)} -} + secondary := koanf.New(".") -// mergeAll merges configs using the following order. -// - lefthook/.lefthook -// - files from `extends` -// - files from `remotes` -// - lefthook-local/.lefthook-local. -func mergeAll(fs afero.Fs, repo *git.Repository) (*viper.Viper, error) { - extends, err := readOne(fs, repo.RootPath, []string{"lefthook", ".lefthook"}) - if err != nil { + // Load main `extends` + if err := extend(secondary, filesystem, repo.RootPath, extends); err != nil { return nil, err } - if err := extend(fs, extends, repo.RootPath); err != nil { + // Load main `remotes` + if err := loadRemotes(secondary, filesystem, repo, remotes); err != nil { return nil, err } - // Save global extends to compare them after merging local config - globalExtends := extends.GetStringSlice("extends") - - if err := mergeRemotes(fs, repo, extends); err != nil { - return nil, err + // Load optional local config (e.g. lefthook-local.yml) + if err := loadOne(secondary, filesystem, repo.RootPath, localConfigNames); err != nil { + var configNotFoundErr ConfigNotFoundError + if ok := errors.As(err, &configNotFoundErr); !ok { + return nil, err + } } - //nolint:nestif - if err := mergeLocal(extends); err == nil { - // Local extends need to be re-applied only if they have different settings - localExtends := extends.GetStringSlice("extends") - if !slices.Equal(globalExtends, localExtends) { - if err = extend(fs, extends, repo.RootPath); err != nil { - return nil, err - } - } - } else { - var notFoundErr viper.ConfigFileNotFoundError - if ok := errors.As(err, ¬FoundErr); !ok { + // Load local `extends` + localExtends := secondary.Strings("extends") + if len(localExtends) > 0 && !slices.Equal(extends, localExtends) { + if err := extend(secondary, filesystem, repo.RootPath, localExtends); err != nil { return nil, err } } - return extends, nil -} + var config Config -// mergeRemotes merges remote configs to the current one. -func mergeRemotes(fs afero.Fs, repo *git.Repository, v *viper.Viper) error { - var remote *Remote // Deprecated - var remotes []*Remote + config.SourceDir = DefaultSourceDir + config.SourceDirLocal = DefaultSourceDirLocal - err := v.UnmarshalKey("remotes", &remotes) - if err != nil { - return err + if err := unmarshalConfigs(main, secondary, &config); err != nil { + return nil, err } - // Deprecated - err = v.UnmarshalKey("remote", &remote) - if err != nil { - return err - } + log.SetColors(config.Colors) - // Backward compatibility - if remote != nil { - remotes = append(remotes, remote) - } + return &config, nil +} +// loadRemotes merges remote configs to the current one. +func loadRemotes(k *koanf.Koanf, filesystem afero.Fs, repo *git.Repository, remotes []*Remote) error { for _, remote := range remotes { if !remote.Configured() { continue @@ -187,23 +166,27 @@ func mergeRemotes(fs afero.Fs, repo *git.Repository, v *viper.Viper) error { log.Debugf("Merging remote config: %s: %s", remote.GitURL, configPath) - _, err = fs.Stat(configPath) - if err != nil { + if ok, err := afero.Exists(filesystem, configPath); !ok || err != nil { continue } - if err = merge("remotes", configPath, v); err != nil { + parser, ok := parsers[filepath.Ext(configPath)] + if !ok { + panic("TODO: unknown extension to parse") + } + + if err := k.Load(kfs.Provider(newIOFS(filesystem), configPath), parser); err != nil { return err } - if err = extend(fs, v, filepath.Dir(configPath)); err != nil { + extends := k.Strings("extends") + if err := extend(k, filesystem, filepath.Dir(configPath), extends); err != nil { return err } } // Reset extends to omit issues when extending with remote extends. - err = v.MergeConfigMap(map[string]interface{}{"extends": nil}) - if err != nil { + if err := k.Set("extends", nil); err != nil { return err } } @@ -212,40 +195,43 @@ func mergeRemotes(fs afero.Fs, repo *git.Repository, v *viper.Viper) error { } // extend merges all files listed in 'extends' option into the config. -func extend(fs afero.Fs, v *viper.Viper, root string) error { - return extendRecursive(fs, v, root, make(map[string]struct{})) +func extend(k *koanf.Koanf, filesystem afero.Fs, root string, extends []string) error { + return extendRecursive(k, filesystem, root, extends, make(map[string]struct{})) } // extendRecursive merges extends. // If extends contain other extends they get merged too. -func extendRecursive(fs afero.Fs, v *viper.Viper, root string, extends map[string]struct{}) error { - for _, pathOrGlob := range v.GetStringSlice("extends") { +func extendRecursive(k *koanf.Koanf, filesystem afero.Fs, root string, extends []string, visited map[string]struct{}) error { + for _, pathOrGlob := range extends { if !filepath.IsAbs(pathOrGlob) { pathOrGlob = filepath.Join(root, pathOrGlob) } - paths, err := afero.Glob(fs, pathOrGlob) + paths, err := afero.Glob(filesystem, pathOrGlob) if err != nil { return fmt.Errorf("bad glob syntax for '%s': %w", pathOrGlob, err) } for _, path := range paths { - if _, contains := extends[path]; contains { + if _, contains := visited[path]; contains { return fmt.Errorf("possible recursion in extends: path %s is specified multiple times", path) } - extends[path] = struct{}{} + visited[path] = struct{}{} - extendV := newViper(fs, root) - extendV.SetConfigFile(path) - if err := extendV.ReadInConfig(); err != nil { + extent := koanf.New(".") + parser, ok := parsers[filepath.Ext(path)] + if !ok { + panic("TODO: unknown extension for extent " + path) + } + if err := extent.Load(kfs.Provider(newIOFS(filesystem), path), parser); err != nil { return err } - if err := extendRecursive(fs, extendV, root, extends); err != nil { + if err := extendRecursive(extent, filesystem, root, extent.Strings("extends"), visited); err != nil { return err } - if err := v.MergeConfigMap(extendV.AllSettings()); err != nil { + if err := k.Merge(extent); err != nil { return err } } @@ -254,34 +240,15 @@ func extendRecursive(fs afero.Fs, v *viper.Viper, root string, extends map[strin return nil } -// merge merges the configuration using viper builtin MergeInConfig. -func merge(name, path string, v *viper.Viper) error { - v.SetConfigName(name) - v.SetConfigFile(path) - return v.MergeInConfig() -} - -func mergeLocal(v *viper.Viper) error { - for _, name := range localConfigNames { - err := merge(name, "", v) - if err == nil { - break - } - - var notFoundErr viper.ConfigFileNotFoundError - if ok := errors.As(err, ¬FoundErr); !ok { - return err - } - } - - return nil -} - -func unmarshalConfigs(base, extra *viper.Viper, c *Config) error { +func unmarshalConfigs(main, secondary *koanf.Koanf, c *Config) error { c.Hooks = make(map[string]*Hook) for hookName := range AvailableHooks { - if err := addHook(hookName, base, extra, c); err != nil { + if !main.Exists(hookName) && !secondary.Exists(hookName) { + continue + } + + if err := addHook(hookName, main, secondary, c); err != nil { return err } } @@ -289,7 +256,7 @@ func unmarshalConfigs(base, extra *viper.Viper, c *Config) error { // For extra non-git hooks. // This behavior may be deprecated in next versions. // Notice that with append we're allowing extra hooks to be added in local config - for _, maybeHook := range append(base.AllKeys(), extra.AllKeys()...) { + for _, maybeHook := range append(main.Keys(), secondary.Keys()...) { if !hookKeyRegexp.MatchString(maybeHook) { continue } @@ -300,17 +267,17 @@ func unmarshalConfigs(base, extra *viper.Viper, c *Config) error { continue } - if err := addHook(hookName, base, extra, c); err != nil { + if err := addHook(hookName, main, secondary, c); err != nil { return err } } // Merge config and unmarshal it - if err := base.MergeConfigMap(extra.AllSettings()); err != nil { + if err := main.Merge(secondary); err != nil { return err } - if err := base.Unmarshal(c); err != nil { + if err := main.Unmarshal("", c); err != nil { return err } @@ -334,20 +301,98 @@ func unmarshalConfigs(base, extra *viper.Viper, c *Config) error { return nil } -func addHook(hookName string, base, extra *viper.Viper, c *Config) error { - baseHook := base.Sub(hookName) - extraHook := extra.Sub(hookName) +func addHook(name string, main, secondary *koanf.Koanf, c *Config) error { + mainHook := main.Cut(name) + overrideHook := secondary.Cut(name) + + // Special merge func to support merging {cmd} templates + options := koanf.WithMergeFunc(func(src, dest map[string]interface{}) error { + var destCommands map[string]string + + switch commands := dest["commands"].(type) { + case map[string]interface{}: + destCommands = make(map[string]string, len(commands)) + for cmdName, command := range commands { + switch cmd := command.(type) { + case map[string]interface{}: + switch run := cmd["run"].(type) { + case string: + destCommands[cmdName] = run + default: + } + default: + } + } + default: + } - resultHook, err := unmarshalHooks(baseHook, extraHook) - if err != nil { - return err - } + maps.Merge(src, dest) + + if len(destCommands) > 0 { + switch commands := dest["commands"].(type) { + case map[string]interface{}: + for cmdName, command := range commands { + switch cmd := command.(type) { + case map[string]interface{}: + switch run := cmd["run"].(type) { + case string: + newRun := strings.ReplaceAll(run, CMD, destCommands[cmdName]) + command.(map[string]interface{})["run"] = newRun + default: + } + default: + } + } + default: + } + } - if resultHook == nil { return nil + }) + + if err := mainHook.Load(koanfProvider{overrideHook}, nil, options); err != nil { + return err + } + var hook Hook + if err := mainHook.Unmarshal("", &hook); err != nil { + return err } - c.Hooks[hookName] = resultHook + if tags := os.Getenv("LEFTHOOK_EXCLUDE"); tags != "" { + hook.ExcludeTags = append(hook.ExcludeTags, strings.Split(tags, ",")...) + } + c.Hooks[name] = &hook return nil } + +// Rewritten from afero.NewIOFS to support opening paths starting with '/'. + +type iofs struct { + fs afero.Fs +} + +func newIOFS(filesystem afero.Fs) iofs { + return iofs{filesystem} +} + +func (iofs iofs) Open(name string) (fs.File, error) { + file, err := iofs.fs.Open(name) + if err != nil { + return nil, fmt.Errorf("open failed: %s: %w", name, err) + } + + return file, nil +} + +type koanfProvider struct { + k *koanf.Koanf +} + +func (k koanfProvider) Read() (map[string]interface{}, error) { + return k.k.Raw(), nil +} + +func (k koanfProvider) ReadBytes() ([]byte, error) { + panic("not implemented") +} diff --git a/internal/config/load_test.go b/internal/config/load_test.go index 30705d6e..99005845 100644 --- a/internal/config/load_test.go +++ b/internal/config/load_test.go @@ -229,13 +229,13 @@ pre-push: ".lefthook.yml": ` pre-push: scripts: - "global-extend": + "global-extend.sh": runner: bash `, ".lefthook-local.yml": ` pre-push: scripts: - "local-extend": + "local-extend.sh": runner: bash `, }, @@ -246,10 +246,10 @@ pre-push: Hooks: map[string]*Hook{ "pre-push": { Scripts: map[string]*Script{ - "global-extend": { + "global-extend.sh": { Runner: "bash", }, - "local-extend": { + "local-extend.sh": { Runner: "bash", }, }, diff --git a/internal/config/remote.go b/internal/config/remote.go index 3cb29637..15f1ca93 100644 --- a/internal/config/remote.go +++ b/internal/config/remote.go @@ -1,13 +1,13 @@ package config type Remote struct { - GitURL string `json:"git_url,omitempty" mapstructure:"git_url" toml:"git_url" yaml:"git_url"` - Ref string `json:"ref,omitempty" mapstructure:"ref,omitempty" toml:"ref,omitempty" yaml:",omitempty"` + GitURL string `json:"git_url,omitempty" koanf:"git_url" mapstructure:"git_url" toml:"git_url" yaml:"git_url"` + Ref string `json:"ref,omitempty" mapstructure:"ref,omitempty" toml:"ref,omitempty" yaml:",omitempty"` // Deprecated - Config string `json:"config,omitempty" mapstructure:"config,omitempty" toml:"config,omitempty" yaml:",omitempty"` - Configs []string `json:"configs,omitempty" mapstructure:"configs,omitempty" toml:"configs,omitempty" yaml:",omitempty"` - Refetch bool `json:"refetch,omitempty" mapstructure:"refetch,omitempty" toml:"refetch,omitempty" yaml:",omitempty"` - RefetchFrequency string `json:"refetch_frequency,omitempty" mapstructure:"refetch_frequency,omitempty" toml:"refetch_frequency,omitempty" yaml:",omitempty"` + Config string `json:"config,omitempty" mapstructure:"config,omitempty" toml:"config,omitempty" yaml:",omitempty"` + Configs []string `json:"configs,omitempty" mapstructure:"configs,omitempty" toml:"configs,omitempty" yaml:",omitempty"` + Refetch bool `json:"refetch,omitempty" mapstructure:"refetch,omitempty" toml:"refetch,omitempty" yaml:",omitempty"` + RefetchFrequency string `json:"refetch_frequency,omitempty" koanf:"refetch_frequency" mapstructure:"refetch_frequency,omitempty" toml:"refetch_frequency,omitempty" yaml:",omitempty"` } func (r *Remote) Configured() bool { diff --git a/internal/config/script.go b/internal/config/script.go index a8c74635..df3e71f0 100644 --- a/internal/config/script.go +++ b/internal/config/script.go @@ -1,11 +1,6 @@ package config import ( - "strings" - - "github.com/mitchellh/mapstructure" - "github.com/spf13/viper" - "github.com/evilmartians/lefthook/internal/git" "github.com/evilmartians/lefthook/internal/system" ) @@ -25,10 +20,6 @@ type Script struct { StageFixed bool `json:"stage_fixed,omitempty" mapstructure:"stage_fixed" toml:"stage_fixed,omitempty" yaml:"stage_fixed,omitempty"` } -type scriptRunnerReplace struct { - Runner string `mapstructure:"runner"` -} - func (s Script) DoSkip(state func() git.State) bool { skipChecker := NewSkipChecker(system.Cmd) return skipChecker.check(state, s.Skip, s.Only) @@ -37,104 +28,3 @@ func (s Script) DoSkip(state func() git.State) bool { func (s Script) ExecutionPriority() int { return s.Priority } - -func mergeScripts(base, extra *viper.Viper) (map[string]*Script, error) { - if base == nil && extra == nil { - return nil, nil - } - - if base == nil { - return unmarshalScripts(extra.GetStringMap("scripts")) - } - - if extra == nil { - return unmarshalScripts(base.GetStringMap("scripts")) - } - - scriptsOrigin := base.GetStringMap("scripts") - scriptsOverride := extra.GetStringMap("scripts") - if scriptsOrigin == nil { - return unmarshalScripts(scriptsOverride) - } - if scriptsOverride == nil { - return unmarshalScripts(scriptsOrigin) - } - - runReplaces := make(map[string]*scriptRunnerReplace) - for key, originConfig := range scriptsOrigin { - var runReplace scriptRunnerReplace - - if err := unmarshal(originConfig, &runReplace); err != nil { - return nil, err - } - - runReplaces[key] = &runReplace - } - - err := base.MergeConfigMap(map[string]interface{}{ - "scripts": scriptsOverride, - }) - if err != nil { - return nil, err - } - - scripts, err := unmarshalScripts(base.GetStringMap("scripts")) - if err != nil { - return nil, err - } - - for key, replace := range runReplaces { - if replace.Runner != "" { - scripts[key].Runner = strings.ReplaceAll(scripts[key].Runner, CMD, replace.Runner) - } - } - - return scripts, nil -} - -func unmarshalScripts(s map[string]interface{}) (map[string]*Script, error) { - if len(s) == 0 { - return nil, nil - } - - scripts := make(map[string]*Script) - for name, scriptConfig := range s { - var script Script - - if err := unmarshal(scriptConfig, &script); err != nil { - return nil, err - } - - scripts[name] = &script - } - - return scripts, nil -} - -// `scripts` are unmarshalled manually because viper -// uses "." as a key delimiter. So, this definition: -// -// ```yaml -// scripts: -// -// "example.sh": -// runner: bash -// -// ``` -// -// Unmarshals into this: -// -// ```yaml -// scripts: -// -// example: -// sh: -// runner: bash -// -// ``` -// -// This is not an expected behavior and cannot be controlled yet -// Working with GetStringMap is the only way to get the structure "as is". -func unmarshal(input, output interface{}) error { - return mapstructure.WeakDecode(input, &output) -} diff --git a/internal/lefthook/run.go b/internal/lefthook/run.go index b27f04e9..ff9fc129 100644 --- a/internal/lefthook/run.go +++ b/internal/lefthook/run.go @@ -55,8 +55,8 @@ func (l *Lefthook) Run(hookName string, args RunArgs, gitArgs []string) error { // Load config cfg, err := config.Load(l.Fs, l.repo) if err != nil { - var notFoundErr config.NotFoundError - if ok := errors.As(err, ¬FoundErr); ok { + var errNotFound config.ConfigNotFoundError + if ok := errors.As(err, &errNotFound); ok { log.Warn(err.Error()) return nil } diff --git a/internal/lefthook/runner/exec/execute_unix.go b/internal/lefthook/runner/exec/execute_unix.go index 45efaab0..32287977 100644 --- a/internal/lefthook/runner/exec/execute_unix.go +++ b/internal/lefthook/runner/exec/execute_unix.go @@ -10,7 +10,6 @@ import ( "os" "os/exec" "path/filepath" - "strings" "github.com/creack/pty" "github.com/mattn/go-isatty" @@ -44,7 +43,7 @@ func (e CommandExecutor) Execute(ctx context.Context, opts Options, in io.Reader for name, value := range opts.Env { envs = append( envs, - fmt.Sprintf("%s=%s", strings.ToUpper(name), os.ExpandEnv(value)), + fmt.Sprintf("%s=%s", name, os.ExpandEnv(value)), ) } switch log.Colors() { diff --git a/internal/lefthook/runner/exec/execute_windows.go b/internal/lefthook/runner/exec/execute_windows.go index 47d72757..ce69cbf8 100644 --- a/internal/lefthook/runner/exec/execute_windows.go +++ b/internal/lefthook/runner/exec/execute_windows.go @@ -40,7 +40,7 @@ func (e CommandExecutor) Execute(ctx context.Context, opts Options, in io.Reader for name, value := range opts.Env { envs = append( envs, - fmt.Sprintf("%s=%s", strings.ToUpper(name), os.ExpandEnv(value)), + fmt.Sprintf("%s=%s", name, os.ExpandEnv(value)), ) } switch log.Colors() {