lefthook で特定フォルダの変更を検知して pre-push フックを実行する際にハマった件
はじめに
lefthook の小ネタです。
所属しているチームで、lefthook で pre-commit 時に静的解析や単体テストを動かしていました。
同じノリで、push 時にも動かすフックを定義したのですが、少しハマったので、記事にしようと思います。
概略
lefthook で、あるディレクトリ配下を変更したコミットを push する際に動かす hook を定義したかった。
そのために、glob にて指定したディレクトリパスに設定したが、うまく動かなかった。
files に files: git diff --name-only HEAD origin/main
設定するとうまくいった。
実践
以下のディレクトリ構成を例に考えます。
※ モノレポでマイクロサービスを運用していて、特定のサービスに対して lefthook を動かしたいイメージ
.
├── lefthook.yml
├── serviceA
│ └── A
│ └── hoge.txt
└── serviceB
└── B
└── fuga.txt
lefthook は以下の通りです。
pre-push:
commands:
testA:
root: serviceA/A
glob: serviceA/A/*.txt
run: echo "run testA!"
testB:
root: serviceB/B
glob: serviceB/B/*.txt
run: echo "run testB!"
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.
https://lefthook.dev/configuration/glob.html#glob
glob は上記の通り hook を動作させるファイルをフィルタリングできます。
各サービスに対応したパスを glob に設定すれば serviceA 配下の変更には、serviceA で指定したコマンドが走ると思っていました。
実際に serviceA/A/hoge.txt
のみ編集したコミットを push
してみます。
$ echo -e "hoge" >> serviceA/A/hoge.txt
$ git diff
diff --git a/serviceA/A/hoge.txt b/serviceA/A/hoge.txt
index e69de29..2262de0 100644
--- a/serviceA/A/hoge.txt
+++ b/serviceA/A/hoge.txt
@@ -0,0 +1 @@
+hoge
$ git add . $ git commit -m "testA"
[main 6c0c72e] testA
1 file changed, 1 insertion(+)
$ git push origin main
╭──────────────────────────────────────╮
│ 🥊 lefthook v1.10.11 hook: pre-push │
╰──────────────────────────────────────╯
┃ testA ❯
run testA!
┃ testB ❯
run testB!
────────────────────────────────────
summary: (done in 0.07 seconds)
✔️ testA
✔️ testB
testA, testB 両方フックが動いていますね。本来は testA だけ実行されて欲しかったのに何故だ。という話です。
原因
実際に lefthook がどのように動いているか確認してみます。
$ lefthook run pre-push --verbose
│ [lefthook] cmd: [git version]
│ [lefthook] stdout: git version 2.39.3 (Apple Git-146)
~~省略~~
╭──────────────────────────────────────╮
│ 🥊 lefthook v1.10.11 hook: pre-push │
╰──────────────────────────────────────╯
│ [lefthook] cmd: [git diff --name-only HEAD @{push}]
│ [lefthook] dir: /Users/g_kawano/Sandbox/lefthooks
│ [lefthook] error: exit status 128
│ [lefthook] stdout:
│ [lefthook] stderr: fatal: no upstream configured for branch 'main'
│ [lefthook] cmd: [git branch --remotes]
│ [lefthook] dir: /Users/g_kawano/Sandbox/lefthooks
│ [lefthook] stdout: origin/feat/hoge
origin/main
│ [lefthook] cmd: [git diff --name-only HEAD 4b825dc642cb6eb9a060e54bf8d69288fbee4904]
│ [lefthook] dir: /Users/g_kawano/Sandbox/lefthooks
│ [lefthook] stdout: lefthook.yml
serviceA/A/hoge.txt
serviceB/B/fuga.txt
│ [lefthook] files before filters:
[lefthook.yml serviceA/A/hoge.txt serviceB/B/fuga.txt]
│ [lefthook] files after filters:
[.//hoge.txt]
┃ testA ❯
run testA!
│ [lefthook] cmd: [git diff --name-only HEAD @{push}]
│ [lefthook] dir: /Users/g_kawano/Sandbox/lefthooks
│ [lefthook] error: exit status 128
│ [lefthook] stdout:
│ [lefthook] stderr: fatal: no upstream configured for branch 'main'
│ [lefthook] cmd: [git diff --name-only HEAD 4b825dc642cb6eb9a060e54bf8d69288fbee4904]
│ [lefthook] dir: /Users/g_kawano/Sandbox/lefthooks
│ [lefthook] stdout: lefthook.yml
serviceA/A/hoge.txt
serviceB/B/fuga.txt
│ [lefthook] files before filters:
[lefthook.yml serviceA/A/hoge.txt serviceB/B/fuga.txt]
│ [lefthook] files after filters:
[.//fuga.txt]
┃ testB ❯
run testB!
────────────────────────────────────
summary: (done in 0.10 seconds)
✔️ testA
✔️ testB
│ [lefthook] cmd: [git diff --name-only HEAD @{push}]
│ [lefthook] dir: /Users/g_kawano/Sandbox/lefthooks
│ [lefthook] error: exit status 128
│ [lefthook] stdout:
│ [lefthook] stderr: fatal: no upstream configured for branch 'main'
プッシュするファイルの差分を取得するために、打っているコマンドが失敗しているようです。
これは、リモート追跡ブランチが設定されていない場合にでるエラーで、git push --set-upstream origin main
を実行すれば解決するやつです。
ただ、私は普段から push origin main のようにブランチを指定して push しているので、リモート追跡ブランチは設定されないことがよくあります。
│ [lefthook] cmd: [git diff --name-only HEAD 4b825dc642cb6eb9a060e54bf8d69288fbee4904]
│ [lefthook] dir: /Users/g_kawano/Sandbox/lefthooks
│ [lefthook] stdout: lefthook.yml
serviceA/A/hoge.txt
serviceB/B/fuga.txt
先ほどのコマンドが失敗したため、lefthook は 4b825dc642cb6eb9a060e54bf8d69288fbee4904
との差分を見ているようです。
このハッシュ値は Git の空のツリーを表しているので、当然今あるすべてのファイルが差分として表示されます。
はい。hoge.txt しか変更していないのに、すべてのファイルが差分として表示されてしまっているのが原因でした。
│ [lefthook] files before filters:
[lefthook.yml serviceA/A/hoge.txt serviceB/B/fuga.txt]
│ [lefthook] files after filters:
[.//hoge.txt]
┃ testA ❯
このあとに glob の設定によってフィルターをしていますが、すべてのファイルが対象なので、すべてのフックでヒットすることは自明です。
解決策
[lefthook] cmd: [git diff --name-only HEAD @{push}]
このコマンドが実行されて失敗しているのを回避すれば解決できそうです。
files (global)
A custom git command for files 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.
https://lefthook.dev/configuration/files-global.html
そのために、files を定義して、pre-push が参照するファイルを指定します。
git diff
diff --git a/lefthook.yml b/lefthook.yml
index b73731f..768fcfe 100644
--- a/lefthook.yml
+++ b/lefthook.yml
@@ -1,5 +1,7 @@
pre-push:
+ files: git diff --name-only HEAD origin/main
+
commands:
testA:
root: serviceA/A
main ブランチが作られている前提として、origin/main ブランチと比較した差分を対象とするように設定しました。
もう一度、hoge.txt のみ変更した commit を作って、以下のコマンドを実行します。
$ lefthook run pre-push --verbose
│ [lefthook] cmd: [git version]
│ [lefthook] stdout: git version 2.39.3 (Apple Git-146)
~~省略~~
╭──────────────────────────────────────╮
│ 🥊 lefthook v1.10.11 hook: pre-push │
╰──────────────────────────────────────╯
│ [lefthook] cmd: [sh -c git diff --name-only HEAD origin/main]
│ [lefthook] dir: /Users/g_kawano/Sandbox/lefthooks/serviceA/A
│ [lefthook] stdout: serviceA/A/hoge.txt
│ [lefthook] files before filters:
[serviceA/A/hoge.txt]
│ [lefthook] files after filters:
[.//hoge.txt]
┃ testA ❯
run testA!
│ [lefthook] cmd: [sh -c git diff --name-only HEAD origin/main]
│ [lefthook] dir: /Users/g_kawano/Sandbox/lefthooks/serviceB/B
│ [lefthook] stdout: serviceA/A/hoge.txt
│ [lefthook] files before filters:
[serviceA/A/hoge.txt]
│ [lefthook] files after filters:
[]
│ testB (skip) no files for inspection
────────────────────────────────────
summary: (done in 0.12 seconds)
✔️ testA
ただしくフィルターされて serviceA で定義したフックのみ動くことが確認できました!
後記
│ [lefthook] cmd: [git diff --name-only HEAD @{push}]
│ [lefthook] dir: /Users/g_kawano/Sandbox/lefthooks
│ [lefthook] error: exit status 128
│ [lefthook] stdout:
│ [lefthook] stderr: fatal: no upstream configured for branch 'main'
│ [lefthook] cmd: [git branch --remotes]
│ [lefthook] dir: /Users/g_kawano/Sandbox/lefthooks
│ [lefthook] stdout: origin/feat/hoge
origin/main
│ [lefthook] cmd: [git diff --name-only HEAD 4b825dc642cb6eb9a060e54bf8d69288fbee4904]
│ [lefthook] dir: /Users/g_kawano/Sandbox/lefthooks
│ [lefthook] stdout: lefthook.yml
serviceA/A/hoge.txt
serviceB/B/fuga.txt
│ [lefthook] files before filters:
[lefthook.yml serviceA/A/hoge.txt serviceB/B/fuga.txt]
│ [lefthook] files after filters:
[.//hoge.txt]
あとから分かったのですが、以前として、files を設定しても上記の実行ログが出力されていました。
おそらくですが、pre-push を設定すると必ず実行されるが、現状うまく動いているため、files を定義するとそちらを優先した結果になるのかなーと推測しています。
Discussion