📙

【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はフォントによって崩れがち。自分だけが使う環境なら愛着が湧くもの優先でよいかもしれません。

解説

Git初心者あるある mainブランチにコミットする

リモートのmainブランチはバッチリ保護してあるので、プッシュしてしまう心配はありません。

https://docs.github.com/ja/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/managing-a-branch-protection-rule

https://docs.gitlab.com/ee/user/project/protected_branches.html

問題はローカル環境での開発作業です。プロジェクトには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

https://git-scm.com/docs/git-rev-parse

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