LoginSignup
2
1

More than 5 years have passed since last update.

libgit2でclean/smudgeフィルタを実現

Posted at

上記のチケットは「もう入っているよ」でクローズになっていますが、実際、関数リファレンスはあっても、使い方のドキュメントはないし、libgit2のバインディングでも対応しているのは少ないし、どう使えばいいのか分からない状況。

上記のチケットに書かれているファイル群(実際は場所が違ってtests/filter)を見てみます。

フィルタが実際に適用される流れ

crlf.cのtest_filter_crlf__to_worktree()というテストコードを見てみると、

  • git_filter_list_newでフィルタリストを作り
  • git_filter_list_pushでフィルタを登録(ここでは組み込みのCR/LFフィルタを使用)
  • git_filter_list_apply_to*で実際のデータにフィルタリストを適用
  • git_filter_list_freeでフィルタリストを開放

という流れになるみたい。少なくともこのフローが一番プリミティブなところでは使われると。とはいえ、自分でインデックスを作るところでこの処理を挿入していくのか?それはつらいぞ、という疑問が残ります。

フィルタリスト

custom.cを見てみます。このテストコードを見ると、git_filter_list_newは呼ばずに、git_filter_list_loadを使ってフィルタリストを取得しています。

  • 事前にgit_filter_registerを使ってフィルタを登録しておく
  • git_filter_list_loadにファイル名を渡すとgit_filter_listが作成される
  • git_filter_list_apply_to*で実際のデータにフィルタリストを適用
  • git_filter_list_freeでフィルタリストを開放

実際はこんな感じの流れになるみたいですね。git_filter_list_newについては忘れましょう。これは内部処理向け関数です。

git_filter_register()の実装を見ると、グローバル変数(実際にはstaticな静的な変数)のリストに登録する模様。なのでアプリケーションが初期化されたタイミングで登録しておくと良いみたいです。

初期化コードのtest_filter_custom__initializeを見ると、git_filter_list_loadはオリジナルのgit同様、.gitattributesを参照しているんでしょうかね?git_attr_get_manyという関数の中で確認している模様。

だれがgit_filter_list_loadを呼ぶのか?

grepでlibgit2のソースコードを見てみると、git_filter_list_loadは内部で使われている模様です。

  • blob.c: blobオブジェクトをファイルから読み込むときにフィルタを適用
  • checkout.c: 新しいコミットをチェックアウトしてきた時にフィルタを適用
  • diff_file.c/diff.c: ファイルのdiffを取るときにフィルタを適用
  • repository.c: ファイルのハッシュを計算する前にフィルタを適用

これらを見ると、git_filter_list_load/git_filter_list_freeは必要なところで呼び出すようにすでにコード内に書かれているみたいですね。つまり、git_filter_list_load/git_filter_list_freeもgit_filter_list_apply_to*もクライアントコードから明示的に呼び出す必要はなさそうです。

smudge/cleanは?

フィルタ適用については特に心配しなくても適用されそうなことが分かりました。では、smudge/cleanの実際の処理はどこに書くんでしょうか?

フィルタの構造体を見てみます。

include/git2/sys/filter.h
struct git_filter {
    unsigned int           version;

    const char            *attributes;

    git_filter_init_fn     initialize;
    git_filter_shutdown_fn shutdown;
    git_filter_check_fn    check;
    git_filter_apply_fn    apply;
    git_filter_cleanup_fn  cleanup;
};

コールバックが5つありますね。smudgeとcleanのそれぞれのコールバックが・・・ないです。説明を読むと、initializeは初回に使われるときに一度だけ呼ばれ、shutdownはunregisterの時に呼ばれるとのこと。checkはフィルタが適用されるかの確認(0を返せば適用)で呼ばれ、実際にフィルタ処理はapplyで行われ、applyされたら最後にcleanupが呼ばれるらしいです。

その他check、applyのどちらかでもGIT_PASSTHROUGHをreturnすると、フィルタリングは行われずに入力内容がそのまま出力されるらしいです。また、cleanupはcheckとapplyで作られたメモリ(payload属性でやりとりされる)の開放などが仕事とのこと。

smudge/cleanの分岐がどこで行われているか確認するために、smudgeでgrepかけると該当コードが見つかりました。

src/crlf.c
if (git_filter_source_mode(src) == GIT_FILTER_SMUDGE)
    return crlf_apply_to_workdir(*payload, to, from);
else
    return crlf_apply_to_odb(*payload, to, from, src);

check/applyコールバックで呼ばれる関数の中で、git_filter_source_modeを呼ぶと判定できそうです。

まとめ

今日わかったことは以下のとおりです。

  • フィルタを実現したいクライアントで呼ぶべきはgit_filter_register関数のみ
  • smudge/cleanの判定は、git_filter_registerに渡すfilter構造体のcheck/applyコールバックの中でgit_filter_source_modeを使う

libgit2はコード内に書かれた詳細なdoxygen/javadocスタイルのコメントと、網羅されたユニットテストを持ってます。テストは「動作可能なドキュメント」と言われることもありますが、使う側の気持ちで読んでみるとコンテキスト情報が足りなかったり、実際に使うべき関数がどれか分からないという問題がありますね。

libgit2はよく使われる使い方に関してはサンプルも用意してくれているんですが、普段使いのgit用語の感覚で読めなかったり、網羅してない項目もあったり、手探りなことが多かったりします。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1