チームで開発する時に一律でフォーマット揃ってたり、
既存のコードも一律に綺麗にしたりを今後維持できるようにできたらいいなと思ったので
とりあえずやってみました。
Git Hookとは
Gitでコマンドを実行する際に、前後でスクリプトを実行できるGitの機能。
今回使うpre-commit
の場合、commitを行うとその前にスクリプトが実行され、
問題があればcommitを中止し、なければcommitを通すといったことが出来ます。
例としては
- commit時にテストを流す
- commit時にlinterでチェック、あるいはフォーマッターをかける
- 特定のブランチへのcommitを禁止(masterやdevelopなど)
- ...etc
Spotlessとは
Javaのコードチェック、フォーマットが出来るLinterです。
指定したフォーマットに従ってJavaのコードをいい感じにフォーマットしてくれます。
Ktlintとは
こちらはKotlinのコードチェック、フォーマットが出来るLinterです。
公式の規約に沿っていい感じにしてくれるのと先述のSpotlessと連携ができるので、
JavaとKotlin両方をいい感じ(概念)に出来ます。
導入
pre-commit
gitで管理されているプロジェクトであれば .git/hooks
ディレクトリの中に、
pre-commit.sample
というファイルがあるのでそれをリネームして使うでも作成するでもいいと思います。
pre-commit
が.git/hooks
の中にあればcommit時にスクリプトが呼ばれるようになります。
// 今回はとりあえず作りました
$ touch pre-commit
Spotless,Ktlint
build.gradle
にこんな感じで追加すると使えるようになります。
Spotlessの方はここでどのフォーマット形式を使用するかを指定します。
Ktlintは基本的に公式の規約に従うみたいです。
apply plugin: "com.diffplug.spotless"
spotless {
ratchetFrom 'origin/main'
java {
target "**/*.java"
targetExclude("$buildDir/**/*.java")
googleJavaFormat("1.14.0")
}
kotlin {
target '**/*.kt'
targetExclude("$buildDir/**/*.kt")
ktlint("0.44.0").userData(['disabled_rules': 'no-wildcard-imports'])
}
}
この状態だとデフォルトのルールが適用されますが、
コード内の下側に書いてある方法や.editorconfig
を使うことでもルールのカスタムが出来たりします。
これでフォーマットとコードチェックが出来るようになりました。
// コードのフォーマット
$ ./gradlew spotlessApply
// コードのチェック
$ ./gradlew spotlessCheck
pre-commitのスクリプト
pre-commit
に記載するスクリプトですが、
普通に./gradlew spotlessApply
してしまうと、
既存のコード全てにかかってしまう(ちゃんといつか全部やりたい)ので、
差分だけを抽出したものにフォーマットをかけていくことにしたのが以下。
set -e
# ステージングされた変更のあるファイルを抽出
# java,kotlinが対象
formatFileList="$(git --no-pager diff --name-status --no-color --cached | awk '$1 != "D" && $2 ~ /\.kts|\.java|\.kt/ { print $NF}')"
# 抽出したファイルに対してフォーマットを適用する
echo "spotlesscheck start..."
for filePath in $formatFileList
do
./gradlew spotlessApply -PspotlessIdeHook="$(pwd)/$filePath"
echo "git add $filePath"
git add "$filePath"
done;
echo "spotlesscheck finish."
これでcommit時に自動でフォーマッタをかけることはできるのですが、
pre-commit
は.git
配下のためgitで管理することができません。
なのでプロジェクトルートにpre-commitファイルを配置しておき、
gradleのタスクに.git/hooks
へ配置させることでいい感じにならんかなということでこちら。
task installPreCommitScript(type: Copy) {
from new File(rootProject.rootDir, './pre-commit')
into { new File(rootProject.rootDir, '.git/hooks') }
fileMode 0775
}
これで他の人もリポジトリから取得してきた状態で下記を実行すればpre-commit
が使えるようになります。
$ ./gradlew installPreCommitScript
まとめ
今回は自身がAndroidエンジニアだったためKotlinとJava向けの内容でしたが、
他の言語でも同じようなことは出来るのでGitHookは他でもいい感じに使えるんじゃないかなーと思います。