今回は "10. Handling repository events with hooks" を読みます。
Mercurial では、 構成管理におけるイベントの発生を 「フック(hook)」と呼ばれる仕組みで通知することで、 イベント発生そのものを通知する以外にも、 イベント契機となった処理そのものの継続の可否を制御することができます。
CVS のイベント通知の un-documented っぷりで苦労した身としては、 フックの API (起動契機と情報受け渡し方法) がきちんと文書化されているのはありがたい限りです。
フックの実行は、 チェンジセットのコミッターやリポジトリの所有者の権限ではなく、 Mercurial プロセスの実行者の権限で行われます。
特に、ローカルホスト上の未知のリポジトリに対してアクセスした場合、
他リポジトリに対する hg push といった改変操作以外にも、
自リポジトリに対する hg pull や hg clone、
hg bundle といった参照操作でもフックが起動されますので、
「参照系だから大丈夫」と安心してはいけません。
これはちょっと怖いですね。
見知らぬリポジトリにアクセスする際には、
まずはフック設定の確認をしましょう
(原文では hg config hooks となっていますが、
正しくは hg showconfig hooks です)。
% hg showconfig hooks
hook.hooktype=hookname
:
フック設定は構成管理下に無いのでリポジトリ間では伝播しない、 というお話。
尤も、うっかり伝播してしまうと、 セキュリティの弱体化を招きかねない可能性があるので、 止むを得ないと言えるでしょう。
フック設定は利用者により適宜上書き(=「取り消し」含む)できるため、 特にセキュリティ的な防御をフックで実現しようとした場合、 注意が必要です。
重要なフックの実行は、 利用者側のフック設定に期待するのではなく、 リポジトリ(サーバ)構成を工夫するなどの運用的な強制が必要、 というお話。
Mercurial は、 平行性を確保するために排他制御を最小限にしており、 参照系アクセスの際にはロックを必要としてないのだそうです。
その代償として、 他のトランザクションによる確定前書き込みデータが見えてしまう (= Dirty Reads)、 つまり DBMS の隔離レベルで言うところの 「Read uncommitted」 程度の隔離しか得られません。
トランザクション完了前に実行されるフックが必要以上に長時間実行されると、
例えば hg pull が、
最終的に拒否されてしまう
hg push と平行して実行されている場合、
リポジトリに永続化されないチェンジセットを取り込んでしまう、
といった問題が発生してしまいます。
この節では簡単なフック実装に関して、順を追って説明します。
hg commit の際に実行されるフックの設定は:
[hooks] commit=hookname
となりますが、
これでは hg commit
の際に複数のフックを起動することができません。
そこで Mercurial では、 フックイベント名に任意の接尾辞を付与できるようにすることで、 この問題を回避しています。
[hooks] commit.任意の接尾辞=hookname
「接尾辞も含めた同一のフック種別」に対して設定をしない限り、 フック設定の上書き(ないし無効化)は行われません。
幾つかのフックは「制御用(controlling)」と呼ばれ、 その戻り値で「フック実行失敗」を示すことで、 Mercurial のコマンド実行そのものを中断し、 トランザクションを巻き戻させることができます。
この仕組みを用いることで、 チェンジセット追加の際に、 受け入れ前の内容検証を行ったり、 そもそも受け入れそのものを拒否(= リポジトリの凍結) したりすることができます。
この節では、いよいよフック実装の詳細について説明します。
Mercurial のフック実装は、
(1) hg コマンドとは別なプロセスとして実行される
「外部フック」形式と、
(2) hg コマンドと同一のプロセス内で実行される
「プロセス内フック」形式が選択可能です。
外部フック形式なら、任意の実装言語が選択可能ですが、 プロセス内フック形式なら、実装言語は Python に限定されます。
原著者は「性能が気にならない簡単なフックなら、 シェルスクリプトによる外部フックで十分」としていますが、 ここはやはりプロセス内フックを選択し、 構成管理の履歴情報を参照しつつ機能するような Mercurial らしいフックを実装したいところです。
パラメータの渡し方(Python のキーワード引数 or 環境変数)と、 その命名形式についてのお話。
「フック実行が成功した場合は false を返すこと」 と書かれているのですが、 Python は「戻り値無し(= 末尾に return を持たない)」 を None (論理値的には false と等価)と見做すため、 明示的に true を返さない限り、 フックの実行は成功したものと見做されます。
外部フックの場合、 フック設定で書いた内容が一旦シェルにより評価される、 とのことなので、 パイプによるコマンド群連携やワイルドカード・環境変数置換ができます。
また、 環境変数もそのまま引き継がれますから、 HOME や USERNAME を用いたユーザ毎の挙動変更を可能です。
プロセス内フックの場合、
当該フックの実装が Python から見えるように、
PYTHONPATH 環境変数を適宜設定する等の対応が必要です。
プロセス内フックの引数に関する説明。
一定の長さ以下のコミットメッセージでのコミットを拒否するフックの例。
こういったフックは、 空のメッセージでコミットする輩を戒めるには便利ですね。
行末に空白文字(space ないし tab) を持つファイルを含むコミットを拒否するフックの例。
改行文字を表示しないエディタを利用している場合、 うっかりすると末尾空白文字が紛れ込みますので、 こういったフックもなかなかに有用です。
この節では Mercurial の配布に含まれるフックを紹介しています。
「所有者・グループ・その他に対する権限設定」という、 所謂 UNIX パーミッションでは承認設定の粒度が荒い場合、 昨今では ACL(Access Control List) によるアクセス制限という手段がありますが、 階層構造における設定が面倒だったりと、 (個人的には)二の足を踏んでしまいます。
Mercurial では、 構成要素と利用者の間の、 UNIX パーミッションでは実現できない複雑な承認設定を、 acl 拡張によって実現することができます。
原文の記述はアクセス可否判定の手順が不明瞭に思えるので、
acl 拡張の実装を元に判定手順を整理したものを以下に示します。
なお「禁止設定がある」および「許可設定がある」とは、
それぞれ "acl.allow" および
"acl.deny"
セクションが設定ファイルに記述されていることを表します。
- 禁止設定があり、当該ユーザが明示的に禁止されている場合は「禁止」
- 許可設定があり、当該ユーザが明示的に許可されていない場合は「禁止」
- それ以外の場合は「許可」
2番目の手順を別な角度で見た場合、 「空の許可設定」は全てのユーザに対する禁止となります。
コミット時の Bugzilla 連携(コメント投稿)を行うフックです。
原文の「Bugzilla が元々この種の統合を念頭に置いていない」 という記述と、 設定に関する解説量が、 フック実装者の苦労を偲ばせます。
リポジトリ状態更新時のメール通知を行うフックです。
メール通知フックのくせに、 デフォルトが「メールを送信しない」設定というあたりに、 安全対策への意識(失敗の経験?)が感じられます。
プロセス内フックの実装に関する幾つかの注意点が書かれています。
プロセス内フックの実装に関しては、 Mercurial API(例: 特定のリビジョンに関する情報の取り出し) の解説が是非とも欲しいところですが、 どうやら BOS Book ではその辺には触れないようです。 "Hack it by yourself" ということなのでしょう。
この点に関しては、 個人的な備忘録代わりに know-how を別途まとめてみたいと思っています。
外部フックの実装に関する幾つかの注意点が書かれています。
Mercurial によってフックに伝えられる 「チェンジセット由来(source of changeset)」と「遠隔リポジトリ URL」 について説明されています。
「チェンジセット由来」には以下の種類があります。
- serve
- http ないし ssh 経由によるチェンジセット転送
- pull
- ローカルでの
pullによるチェンジセット転送 - push
- ローカルでの
pushによるチェンジセット転送 - bundle
- ローカルでの
bundleによるチェンジセット転送
Mercurial が提供するフック毎の API と起動契機、 異常終了時における処理の継続性等について説明されています。
フックと拡張(extension)の自由な追加 (および必要な情報の開示)は、 個人的には Mercurial を使う楽しさの一部と言っても過言ではないです。
構成管理に「楽しさ」というのは変な感じがするかもしれませんが、 これは是非とも体験して欲しい「楽しさ」です。
"Distributed revision control with Mercurial" 関連エントリの一覧は、BOSBook(Bryan O’Sullivan Book)タグで参照できます。