【Git】mainブランチへのコミットを禁止する
はじめに
対象
- ローカル環境で特定のブランチへのマージを禁止したい方向け。
検証環境
- macOS 15.5
- git version 2.49.0
手順
.git/hooks/pre-commit
ファイルを作成する
以下のコマンドを実行し、pre-commit
ファイルを作成します。
touch .git/hooks/pre-commit
chmod a+x .git/hooks/pre-commit
pre-commit
ファイルを編集する
vim .git/hooks/pre-commit
#!/bin/bash
if git rev-parse --verify HEAD >/dev/null 2>&1
then
against=HEAD
else
against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi
exec 1>&2
branch=$(git rev-parse --abbrev-ref "$against")
if test "$branch" = "main"
then
cat <<\EOF
mainへのコミット禁止ぃ~!(゚Д゚#)
EOF
exit 1
fi
動作確認する
GUIクライアントForkからコミットした場合
実際にファイルを編集し、main
ブランチにコミットを試みます。GUIクライアントの場合は、上画像のようにダイアログが表示されます。CLIで実行した場合は以下のように出力されます。
% git commit -m "mainにコミットしてやる"
mainへのコミット禁止ぃ~!(゚Д゚#)
色々な環境のことを考えると顔文字が無難そうです。煽りAAはフォントによって崩れがち。自分だけが使う環境なら愛着が湧くもの優先でよいかもしれません。
解説
main
ブランチにコミットする
Git初心者あるある リモートのmain
ブランチはバッチリ保護してあるので、プッシュしてしまう心配はありません。
問題はローカル環境での開発作業です。プロジェクトにはGitに不慣れなメンバーも参画することがあります。間違ってmain
ブランチにコミットしてしまうこともあります。
コミットとプッシュはできるけれど、リセットやチェリーピックはまだよく分からない。でも直ぐに覚えらえるものでも、上手く使えるものでもない。Git怖い。でも他のITエンジニアも毎回お世話を焼く訳にもゆかず。
リセットすることは容易いですが、作業も思考も途切れがち。考えてみれば熟練の開発者でさえ、極限状態に陥ればやらかしそうなもの。そもそも特定のブランチにコミットできなければいいのですから、pre-commit
で何とかできないかと思ったのが始まりです。
git rev-parse --verify HEAD
Shebang(シバン)などシェルスクリプトの説明は、趣旨から逸れるので割愛します。
# HEADが存在するか = コミットが存在するか
if git rev-parse --verify HEAD >/dev/null 2>&1
then
# HEADが存在する場合は、HEADに対して
against=HEAD
else
# HEADが存在しない場合は、空のツリーに対して
against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi
4b825dc642cb6eb9a060e54bf8d69288fbee4904
は特殊な値で、空のツリーオブジェクトのハッシュ値です。UNIX時間の「1970年1月1日午前0時0分1秒」や、トイレットペーパーの芯のような。
git rev-parse --abbrev-ref "$against"
branch=$(git rev-parse --abbrev-ref "$against")
if test "$branch" = "main"
then
cat <<\EOF
mainへのコミット禁止ぃ~!(゚Д゚#)
EOF
exit 1
fi
Pick out and massage parameters
massageという単語にはマッサージ以外にも、
- 事実や数字を良く見えるように改ざんする。
- 単純に好ましい状態に操作・変換する。
意味もあるようです。
つまりこのコマンドはGitの履歴をあれこれして、特定の情報を選んだり整形したりできるコマンドのようです。実際にコマンドを叩いてみた方、早いと思います。
% git branch
* main
% git rev-parse --abbrev-ref HEAD
main
% git rev-parse --symbolic-full-name HEAD
refs/heads/main
% git rev-parse --symbolic HEAD
HEAD
% git rev-parse --short HEAD
9c506d1
% git rev-parse --not HEAD
^9c506d17ccb34ce3e430aa4b75d4021ef9d866a2
abbrev (abbreviation) は略称、ref (reference) は参照という意味を持っています。HEADは通常、現在のブランチの先頭のコミットを指すため、ここではコミット対象のブランチの略称を取ってくることになります。
バリエーションをご用意しました
ブランチ名を取得するまでは同じです。
# 複数のブランチを禁止対象とする
branches=("main" "staging" "develop")
for deny in "${branches[@]}"; do
if [ "$branch" = "$deny" ]; then
cat <<EOF
${branch} へのコミット禁止ぃ~!(゚Д゚#)
EOF
exit 1
fi
done
# ブランチ名がassets/*の場合を禁止対象とする
if echo "$branch" | grep -q '^assets/'; then
cat <<EOF
assets/* へのコミット禁止ぃ~!(゚Д゚#)
EOF
exit 1
fi
おわりに
なるべく早くエラーとして処理を終了するのが理想という意味では、このタイミングがベストかなと。ローカル環境のGitにもブランチの保護機能があればいいのに。
Discussion