以前次の記事で変更されたファイルについてリンターを使う例を紹介しました。
【JavaScript】【Git】Git 管理下の変更されたファイルのみに対して任意のコマンドを実行する – 株式会社シーポイントラボ | 浜松のシステム・RTK-GNSS開発
これは確かに変更されたファイルについて処理を行えるコードなのですが、変更さえされていればコミット対象外のファイルにたいしても処理を行ってしまうコードでした。これはコミット前フックに使う際には問題となります。不意にコミットする気のなかった tmp.js などがコミットされ、そのままプッシュされてしまうなどといった事故が起きます。この記事では純粋にコミット対象のファイルのみに対して処理を行うコードを紹介します。
実際のコードが次です。基本的な処理の流れは【JavaScript】【Git】Git 管理下の変更されたファイルのみに対して任意のコマンドを実行する – 株式会社シーポイントラボ | 浜松のシステム・RTK-GNSS開発同様で Git コマンドを元にファイルパスを抜き出してそのファイルパスを用いてなんやかんやできるようにする(例では php-cs-fixer に渡しています)、という処理です。
// eslint-disable no-console const childProcess = require('child_process'); const fs = require('fs'); const path = require('path'); main(); function main() { console.log('---run preCommit.js---'); // このあたりに他のコミット前処理関数を色々入れると便利 gitLint(); console.log('---end preCommit.js---'); process.exit(0); } function gitLint() { // gitのコマンドを実行してコミットされようとしているファイルパスを抽出 const filePaths = childProcess .execSync('git diff --staged --diff-filter=ACMR --name-only -z').toString() // git diff --staged --diff-filter=ACMR --name-only -z で次の様な文字列が標準出力されます //.php-cs-fixer.php^@app/Http/Controllers/MemberAPI/ClientErrorLoggerController.php^@gitHooks/preCommit.js^@resources/js/@types/not-js-files.d.ts^@ // ^@は \u0000 のヌル文字です // この実行結果を整形して有効なファイルパスの配列にします .replace(/\u0000$/, '') .split("\u0000") .filter(v => !!v) ; // 対象のファイル一覧を表示。このファイルパスを元に色々できます。 console.log(filePaths) // package.json の中からこのスクリプト用の実行スクリプトセット app-lint-staged を抜き出す const p = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json')).toString()) if (!p['app-lint-staged']) { return; } // app-lint-staged中の設定に従ってファイルを振り分けて処理 // ex. // "app-lint-staged": { // ".*\\.php": [ // "docker-compose run --rm app ./vendor/bin/php-cs-fixer fix -vvv --config ./.php-cs-fixer.php", // "git add" // ] // }, Object.keys(p['app-lint-staged']).forEach(regexStr => { const regex = new RegExp(regexStr.replace('*', '.*').replace(/([^\\])\./, '$1\\.')); filePaths.forEach(filePath => { if (!regex.test(filePath)) { return; } p['app-lint-staged'][regexStr].forEach(cmdStr => { console.log(`${cmdStr} "${filePath}"`) // package.json 内で定義されたコマンドの末尾にファイルパスを追記してコマンドを実行 const res = childProcess.execSync(`${cmdStr} "${filePath}"`); console.log(res.toString()) }) }) }) }
上記コード使っている Git コマンドが次です。
git diff --staged --diff-filter=ACMR --name-only -z
このコマンドで使用されている要素を列挙すると次の様になります。
git diff | 差分取得コマンド |
---|---|
–staged | ステージングされた変更点と何かを比較するためのオプション。デフォルトでは最新コミットと比較 |
–diff-filter=ACMR | Add(追加)Copy(複製)Modify(ファイル内容の変更)Rename(名前の変更)のいずれかをされたファイルのみ表示 |
–name-only | .git のあるディレクトリをルートとした相対パスのみを表示 |
-z | 区切り文字を null 文字にする。これを使わないとパスが”で括られたりエスケープされたりする場合がある |
これらを合わせて整形する余地のある変更されたファイル群のファイルパスそのままを null 文字区切りで出力する、といったコマンドになります。これにより以前とは異なり、正しく整形すべきファイル、コミットすべきファイルのみを扱えるようになりました。