この記事は?
Git Hooks での、コミット生成の制御と push 受け取りの制御について学んだのでメモ。
Git の運用ルール (コミットメッセージやブランチ名のルール等) を、スクリプトで規制したい、という方に役立てられれば幸いです。
Git の基礎的な使い方が分かっていること、シェルスクリプトがある程度読める・書けることを前提に話を進めます。
Git Hooks とは?
Git Hooks というのは、Git で何らかのアクション (例: コミットを作成, マージ, リモートへ Push 等) が発生した際に、特定のプログラム (主にシェルスクリプト) を実行する仕組みです。
検出できるアクションはいくつかあります。
こちらの記事で図に分けて説明してくださっています。
これら全てを活用するのは、本格的で大規模な開発であるときと思います。
この記事では、コミット関係 (クライアントサイド) と、push 受け取り関係 (サーバサイド) を扱います。
Git Hooks スクリプトの置き場所
クライアントサイド (ローカルリポジトリ) では、リポジトリのルート上から.git/hooks/
(隠しフォルダ) に。
サーバサイド (リモートリポジトリ) では、リポジトリのルート上からhooks/
に。
このディレクトリに、各アクションに対応するファイル名で、スクリプトを配置します。
ls .git/hooks/
# applypatch-msg.sample* pre-merge-commit.sample*
# commit-msg.sample* pre-push.sample*
# fsmonitor-watchman.sample* pre-rebase.sample*
# post-update.sample* pre-receive.sample*
# pre-applypatch.sample* prepare-commit-msg.sample*
# pre-commit.sample* update.sample*
デフォルトでは、.sample
と書かれたサンプルのプログラム (シェルスクリプト) が配置されています。
Git Hooks を動作させる際には、対応させるアクションのファイル名で、.sample
を付けず、プログラムを配置します。(実行権限の付与もお忘れなく。)
また、リモートリポジトリ上のhooks/
と、ローカルリポジトリ上の.git/hooks/
は、クローン (同期) されません。
そのため、開発者全員に特定のルールに沿った Git Hooks を使用させるには、別途設定をさせる必要があります。
(一見すると不便な仕様に見えるかもしれませんが、悪意あるスクリプトを実行させないためでしょう。特に、GitHub 等で公開されているオープンなリポジトリでは。)
コミット関係 (クライアントサイド)
コミットに関する Git Hooks は、全部で 4つあります。
Git でのコミット生成の動作と合わせて説明すると、以下のように呼び出されます。
-
git commit
が実行される - ---
pre-commit
が実行される - (1. で
-m "<message>"
オプションがあったなら、<message>
が書き込まれた).git/COMMIT_EDITMSG
が生成される - ---
prepare-commit-msg
が実行される - (1. で
-m <message>
オプションが無かったなら).git/COMMIT_EDITMSG
の編集が開始される - ---
commit-msg
が実行される - コミットが生成される
- ---
post-commit
が実行される
pre-commit
prepare-commit-msg
commit-msg
において、exit 1
等のように、0 以外の返り値が発生した場合、その時点でコミットの動作が中止 (reject) されます。
commit-msg
は、コミット生成の直前に呼び出されますので、入力されたコミットメッセージが指定の書式に沿っているかのチェックに応用できます。
メッセージの書式が正しく無い場合は、コミットを生成できない、というようにすることが可能です。
post-commit
は、コミットが正常に完了したら呼び出されますので、通知等に応用できます。
post-commit
が実行された段階では、既にコミットは完了していますので、返り値が何であろうと中止はされません。
#!/bin/sh
# /.git/hooks/prepare-commit と /.git/hooks/commit-msg で同じ結果が得られる
echo $1 # 結果例: .git/COMMIT_EDITMSG
cat $1 # 結果例: any commit message
exit 0
prepare-commit-msg
commit-msg
が呼び出される際、第一引数には、コミットメッセージの書かれたファイルCOMMIT_EDITMSG
へのパスが入力されます。
pre-commit
post-commit
では、引数は入力されません。
git-log
やgit rev-parse
コマンド等で、リポジトリの情報を取得して、何らかの動作をさせます。
commit-msg
について、実装例を作ってみました:
push 受け取り関係 (サーバサイド)
push に関する Git Hooks は、全部で 4つあります。
Git でのコミット生成の動作と合わせて説明すると、以下のように呼び出されます。
-
git push
が実行される - ---
pre-receive
が実行される - ---
update
が、更新されたブランチ全てに実行される - push が適用される
- ---
post-receive
が実行される
pre-receive
update
において、0 以外の返り値が発生した場合、その時点で push 受け取りが中止 (reject) されます。
post-receive
は、push が正常に適用されたら呼び出されますので、通知等に応用できます。
#!/bin/sh
# /hooks/pre-receive と /hooks/post-receive で同じ結果が得られる
read branch hash_old hash_new
echo $branch # 結果例: refs/heads/master
echo $hash_old # 結果例: aca07435522b6e5ed63481aada5733e421fabbb5
echo $hash_new # 結果例: 90746b18c099aec161434e0c35238db219f8e76e
exit 0
#!/bin/sh
# /hooks/update
echo $1 # 結果例: refs/heads/master
echo $2 # 結果例: aca07435522b6e5ed63481aada5733e421fabbb5
echo $3 # 結果例: 90746b18c099aec161434e0c35238db219f8e76e
exit 0
update
では、引数に値が入力されますが、pre-receive
とpost-receive
では (なぜか) 標準入力 (read
) で入力が来ます。
1つ目の値は、更新前時点で最新のコミットのハッシュが、2つ目の値は、更新後で最新のコミットのハッシュ、3つ目の値は操作が行われたブランチが入力されます。
pre-receive
とupdate
とでは、pre-receive
は 1度の push で 1度だけ実行される点、update
は、更新のかかったブランチ全てに実行される点が異なります。
例えば、branch1
とbranch2
を更新した後、git push --all
とコマンドを実行した場合を考えます。
pre-receive
では、その時点での操作先ブランチについての値が入力され、実行されます。
update
では、refs/heads/branch1
refs/heads/branch2
それぞれについて値が入力され、更新されたブランチの数実行されます。
GitHooks に使えそうなコマンド集
Git コマンドから UNIX 標準コマンドまで紹介。
(詳しい説明は省略。メモ程度にご覧ください。)
sed
grep
テキストの置換や検索等、テキスト処理色々。
(一応書いておくと、) 加えてif then
やtest
等、各種シェルスクリプトでよく使われるコマンドも必須です。
git log -n 1
コミット履歴のうち、最新のコミット 1つのみを表示。
1をその他の数字にすれば、その数字の数だけコミットを表示。
git log -n 1 <HASH>
コミット履歴のうち、<HASH>
と一致するハッシュを持つコミットの情報を表示。
git status -s
git status
の結果をシンプル表示します。
Add や Modified 等を示す英数字とファイル名を各 1行ずつ表示。
行数をカウントすれば、変更のあったファイル数を得られます。
git rev-parse --short <HASH>
Git のコミットのハッシュ<HASH>
を短縮したものを表示。
rev-parse
コマンドは、他にも色々な情報の取得に使えます。