Git commit それぞれに対してスクリプトを実行する
created: 2024-04-19
まとめ
以下のシェルコマンドで、最新2コミットに対してスクリプトを実行できる。
for c in $(git log --pretty=format:"%h" | head -2); do
git checkout $c && do-script
done
# ただし、実行後はもとのブランチではなく最新のコミットにcheckoutした状態になる
まとめ2
調べていたらgit rebaseにある --exec(-x)
オプションで同じことができるとわかった。
--exec
オプションを使う方が良いだろう。
最新2コミットにスクリプトを実行したければ、
git rebase HEAD~2 --exec 'do-script'
と実行する。
実際にはコミットをまとめるrebase処理もあるので、
git rebase --interactive HEAD~4 --exec 'do-script'
のようになりそうだ。
詳細
ソフトウェア開発でGitを使っていることは多い。
その中で、Git hookを利用して自動的にテスト等のスクリプトを実行することも多い。もし使ったことがなければ、便利なので使ってみてほしい。 gitリポジトリの.git/hooksディレクトリには各hookのexampleファイルがあるし、pre-commitやHuskyを使うと楽に設定できるだろう。
さて他方で、毎回のコミットでのpre-commit処理をするには向かないGit運用もある。 ごくごく小さな変更を細かくコミットしておいて、一段落ついた段階やpushする直前にrebase(squash, fixup)する運用だ。 僕はこの運用に近いのでpre-commitを避けたいことが多い。
たとえば
以下のgit logのように、add_inc_fブランチで機能およびテストを実装していて、細かい修正が入る度にコミットしている。
$ git log --abbrev-commit
commit 006 (HEAD -> add_inc_f)
Date: 2024-04-19T00:32:47+0900
fix test of increment function
commit 005
Date: 2024-04-19T00:32:31+0900
fix implementation of increment function
commit 004
Date: 2024-04-19T00:31:13+0900
add test for increment function
commit 003
Date: 2024-04-19T00:30:51+0900
implement increment function for u8
commit 002 (master)
Date: 2024-04-19T00:30:32+0900
fixup (I forget to add .gitignore)
commit 001
Date: 2024-04-19T00:29:51+0900
init
そして、一段落ついた時に、最新のコミット(006)はテスト実装のコミット(004)に、修正のコミット(005)は実装のコミット(003)に、それぞれsquash(fixup)する。
しかし、この時、pre-commitでテストが実行されることになっていると、コミット005を作る際にテスト実行が失敗してコミットができないはずだ。
なので、あまり良くないけれど、コミット005を作る時に git commit --no-verify
でpre-commitを無視している。
さて、コミットを整理したあとのgit logは以下になっている。
$ git log --abbrev-commit
commit 008 (HEAD -> add_inc_f)
Date: 2024-04-19T00:31:13+0900
add test for increment function
commit 007
Date: 2024-04-19T00:30:51+0900
implement increment function for u8
commit 002 (master)
Date: 2024-04-19T00:30:32+0900
fixup (I forget to add .gitignore)
commit 001
Date: 2024-04-19T00:29:51+0900
init
このgit logにおいて、コミット007とコミット008はpre-commitが実行されていない。
pre-commitの目的は、各コミット単位で単体テストだとかコードフォーマットだとかをチェックしておくことなので、そういったpre-commitが実行されていないコミットが存在することは良くない。
そこで、これらのコミットに対してまとめてpre-commitを実行しておきたい。
そういうわけで、以下のスクリプトで各コミットにcheckoutしてpre-commit-scriptを実行することにした。
for c in $(git log --pretty=format:"%h" | head -2); do
git checkout $c && pre-commit-script
done
追記
ここまで書いたところで git rebase --exec
の存在を知った。
git rebase --interactive HEAD~4 -x 'do-script'
すると以下のようなinteractive rebaseの表示になる。
pick 003 implement increment function for u8
exec do-script
pick 004 add test for increment function
exec do-script
pick 005 fix implementation of increment function
exec do-script
pick 006 fix test of increment function
exec do-script
あとは通常のrebase時と同様にコミットを移動/squash/fixして、実行したい箇所にexecを置いておくだけのようだ。
普段Gitを使っているにもかかわらず、認識していない機能はあるものだなあ