稀にGit管理下にあるファイルを書き換えるべきかつ書き換えた結果をコミットすべきでない時があります。具体的な例としては認証情報、メールアドレスの参照設定があります。これらがソースコードに埋め込まれている時にしばしば「手元の開発環境ではこの部分を書き換える必要がある。しかしこの部分はGitに上がっているステージング環境や本番環境の定義でもあるから書き換えた内容をコミットしてはいけない」という制約が課されます。IPアドレスやホスト等の具体的な部分を設定ファイルに分離して設定ファイルを .gitignore に含むのが王道ですが、やんごとなき事情によりそれができない時もあります。そういった時に便利なGitフックスクリプトを紹介します。
作るべきスクリプトのイメージは .gitignore のそれです。通常の .gitignore は Git 管理下からファイルやフォルダ以下を除外します。作るべきスクリプトでは任意のファイルやフォルダ以下を指定してそれをコミットしない様にするという挙動によって目的を達成します。
使うのは pre-commit フックで、スクリプトは以下です。
Git – Git フック
#!/bin/sh # このファイルは .git/hooks/pre-commit の位置に配置。pre-commit は拡張子なしのファイル # 後述の JavaScript プログラムを Node.js で実行してコミットの状態を操作する node gitHooks/preCommit.js
// gitHooks/preCommit.js
// ↑の bash を介して Git フックから実行される JavaScript
const childProcess = require('child_process');
const fs = require('fs');
const path = require('path');
main();
function main() {
console.log('---run preCommit.js---');
gitReset();
console.log('---end preCommit.js---');
process.exit(0);
}
function gitReset() {
// 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)
filePaths.forEach(p => {
// もしコミット対象ファイルパスの中からコミットしたくないファイルの名前が含まれていたならばの if
// この例でコミットしたくないファイルは infra.properties です。複数ファイルにするならば配列にするなり正規表現を"|"で拡張するなりします
if(p.match(/.*\/infra.properties/)){
// git reset コマンドによってコミット対象から除去
const msg = childProcess.execSync(`git reset ${p}`).toString();
console.log(msg);
console.log('pscm-infra.properties のコミットを防ぎました。');
// やたらと git reset を実行したくないためこのループの形にしていますが、
// なんなら毎回特定パス相手に git reset するだけ、というのもありです。
}
})
}
この様にして Git 管理下にあるがローカルからはコミットされない、という動作を任意のファイルに対して行えます。もしコミットする必要がある時は JavaScript のコードを一時的にコメントアウトするなどします。
紹介したコードでは pre-commit を使っていますが、仮にコミットメッセージを元に処理を分けるならば、commit-msg フックを用いてコミットメッセージをスクリプトに渡し、コミットメッセージを元にした分岐の先の終了コードによってコミット処理の続行と終了を切り替えるのがよいでしょう。