Git
Chapters ▾ 2nd Edition

A2.2 Appendix B: Gitをあなたのアプリケーションに組み込む - Libgit2を使う方法

Libgit2を使う方法

あなたが取れる2つ目のオプションは、Libgit2を使用することです。 Libgit2は、他のプログラムへの依存性のないGitの実装であり、プログラムから使いやすいAPIを提供することにフォーカスしています。 Libgit2は http://libgit2.github.com から取得できます。

まずは、C言語用のAPIがどのようなものか見てみましょう。 ここは駆け足で行きます。

// リポジトリを開く
git_repository *repo;
int error = git_repository_open(&repo, "/path/to/repository");

// HEADへの参照を解決してコミットを取得
git_object *head_commit;
error = git_revparse_single(&head_commit, repo, "HEAD^{commit}");
git_commit *commit = (git_commit*)head_commit;

// コミットのプロパティのうちいくつかを出力
printf("%s", git_commit_message(commit));
const git_signature *author = git_commit_author(commit);
printf("%s <%s>\n", author->name, author->email);
const git_oid *tree_id = git_commit_tree_id(commit);

// クリーンアップ
git_commit_free(commit);
git_repository_free(repo);

最初の2行で、Gitのリポジトリを開いています。 git_repository 型は、メモリにキャッシュされているリポジトリへのハンドルを表しています。 リポジトリの作業ディレクトリか、または .git フォルダの正確なパスが分かっている場合、これがリポジトリを開く最もシンプルな方法です。 他の方法としては、 git_repository_open_ext を使って検索オプション付きで開く方法、 git_clone とその仲間を使ってリモートリポジトリのローカルなクローンを作る方法、 git_repository_init を使って全く新規にリポジトリを作る方法があります。

2番目のコードのかたまりは、 rev-parse 文法(詳細は ブランチの参照 を参照)を使って、HEADが最終的に指しているコミットを取得しています。 戻り値は git_object 型のポインタで、これはリポジトリのGitオブジェクトデータベースに存在する何かを表しています。 git_object 型は、実際には数種類のオブジェクトの “親” にあたります。 “子” にあたる型のメモリレイアウトは git_object 型と同じになっているので、正しい型へのキャストは安全に行えます。 上記の場合では、 git_object_type(commit)GIT_OBJ_COMMIT を返すので、 git_commit 型のポインタへ安全にキャストできます。

次のかたまりは、コミットのプロパティにアクセスする方法を示しています。 ここの最後の行では git_oid 型を使用しています。これは、 Libgit2 において SHA-1 ハッシュを表現する型です。

このサンプルからは、いくつかのパターンが見て取れます。

  • ポインタを宣言して、 Libgit2 の呼び出しにそのポインタへの参照を渡すと、その呼び出しは多くの場合 int 型のエラーコードを返す。 値 0 は成功を表す。それより小さい値はエラーを表す。

  • Libgit2 がポインタへ値を入れて返したら、解放は自前で行わなければならない。

  • Libgit2 の呼び出しが const ポインタを返した場合、開放する必要はない。ただし、それがそれが属するオブジェクトが解放されたら、ポインタは無効になる。

  • Cでコードを書くのはちょっとキツい。

最後の1つは、 Libgit2 を使用するときに、C言語でコードを書こうということはまずないだろう、というくらいの意味です。 幸いなことに、様々な言語用のバインディングが利用可能です。これを使えば、あなたの使っている特定の言語や環境から、Gitリポジトリに対する作業を非常に簡単に行えます。 Libgit2 の Ruby 向けバインディングを使って上記の例を書いたものを見てみましょう。Libgit2 の Ruby 向けバインディングは Rugged という名前で、 https://github.com/libgit2/rugged から取得できます。

repo = Rugged::Repository.new('path/to/repository')
commit = repo.head.target
puts commit.message
puts "#{commit.author[:name]} <#{commit.author[:email]}>"
tree = commit.tree

ご覧のように、コードがだいぶすっきりしました。 第一に、 Rugged は例外を使用します。エラーの状態を知らせるのに、 ConfigErrorObjectError のような例外を raise できます。 第二に、リソースの明示的な解放処理がありません。これは、 Ruby がガベージコレクションをしてくれるためです。 それではもう少し複雑な例を見てみましょう。次の例では、コミットをゼロから作成しています。

blob_id = repo.write("Blob contents", :blob) (1)

index = repo.index
index.read_tree(repo.head.target.tree)
index.add(:path => 'newfile.txt', :oid => blob_id) (2)

sig = {
    :email => "bob@example.com",
    :name => "Bob User",
    :time => Time.now,
}

commit_id = Rugged::Commit.create(repo,
    :tree => index.write_tree(repo), (3)
    :author => sig,
    :committer => sig, (4)
    :message => "Add newfile.txt", (5)
    :parents => repo.empty? ? [] : [ repo.head.target ].compact, (6)
    :update_ref => 'HEAD', (7)
)
commit = repo.lookup(commit_id) (8)
  1. 新しいファイルの内容を含む新しい blob を作成します。

  2. インデックスとHEADのコミットのツリーを取得し、パス newfile.txt にある新しいファイルを追加します。

  3. ODBに新しいツリーを作成し、それを新しいコミット用に使用しています。

  4. author フィールドと committer フィールドに同じ署名を使います。

  5. コミットメッセージです。

  6. コミットを作成するときには、新しいコミットの親を指定する必要があります。 ここではHEADの先端を単一の親として指定しています。

  7. Rugged (およびLibgit2)では、コミットを作成する際に、必要に応じて参照を更新することもできます。

  8. 戻り値は新しいコミットオブジェクトの SHA-1 ハッシュです。これは後で Commit オブジェクトを取得するために使用できます。

このRubyのコードは単純明快です。また、重い処理はLibgit2が行っているので、非常に高速に実行できます。 Rubyist でない方のために、 その他のバインディング では他のバインディングにも触れています。

高度な機能

Libgit2 には、Git のコアがスコープ外としている機能がいくつか備わっています。 一つの例がプラグイン機能です。 Libgit2 では、一部の機能に対し、カスタム “バックエンド” を指定できます。これにより、Git が行うのとは別の方法でデータを保存することができます。 Libgit2 では設定、refストレージ、オブジェクトデータベースなどに対してカスタムバックエンドを指定できます。

バックエンドがどのように機能するか見てみましょう。 次のコードは、Libgit2チームが提供しているサンプル( https://github.com/libgit2/libgit2-backends から取得できます)から拝借しています。 オブジェクトデータベース用のカスタムバックエンドを設定する方法を示しています。

git_odb *odb;
int error = git_odb_new(&odb); (1)

git_odb_backend *my_backend;
error = git_odb_backend_mine(&my_backend, /*…*/); (2)

error = git_odb_add_backend(odb, my_backend, 1); (3)

git_repository *repo;
error = git_repository_open(&repo, "some-path");
error = git_repository_set_odb(odb); (4)

(ここで、エラーの捕捉はしていますが、エラー処理は行っていないことに注意してください。あなたのコードが私たちのものより優れていることを願っています。)

  1. 空のオブジェクトデータベース(ODB) “フロントエンド” を初期化します。これは、実際の処理を行う “バックエンド” のコンテナとして機能します。

  2. カスタムODBバックエンドを初期化します。

  3. フロントエンドにバックエンドを追加します。

  4. リポジトリを開きます。作成したODBを、オブジェクトの検索に使うように設定します。

さて、この git_odb_backend_mine というのは何でしょうか? そう、これは自作のODB実装のコンストラクタです。この中では、 git_odb_backend 構造体へ適切に値を設定しさえしていれば、どんな処理でも行えます。 処理は 例えば 以下のようになります。

typedef struct {
    git_odb_backend parent;

    // Some other stuff
    void *custom_context;
} my_backend_struct;

int git_odb_backend_mine(git_odb_backend **backend_out, /*…*/)
{
    my_backend_struct *backend;

    backend = calloc(1, sizeof (my_backend_struct));

    backend->custom_context = …;

    backend->parent.read = &my_backend__read;
    backend->parent.read_prefix = &my_backend__read_prefix;
    backend->parent.read_header = &my_backend__read_header;
    // …

    *backend_out = (git_odb_backend *) backend;

    return GIT_SUCCESS;
}

ここで、非常に分かりにくい制約として、 my_backend_struct の最初のメンバ変数は git_odb_backend 構造体である必要があります。これによって、Libgit2 のコードが期待している通りのメモリレイアウトになることが保証されます。 構造体の残りの部分は任意です。この構造体は必要に合わせて大きくしたり小さくしたりして構いません。

この初期化関数では、構造体にメモリを割り当て、カスタムコンテキストを設定し、それがサポートしている parent 構造体のメンバーへデータを設定しています。 その他の呼び出しのシグネチャについては、Libgit2のソースの include/git2/sys/odb_backend.h ファイルを見てみてください。ユースケースがはっきりしていれば、シグネチャのうちどれをサポートすればよいかを判断するのに役立つでしょう。

その他のバインディング

Libgit2 には各種の言語向けのバインディングがあります。 ここでは、これを書いている時点で利用できるバインディングの中から、その一部を使用して、小さなサンプルプログラムを示していきます。他にも、C++、Go、Node.js、Erlang、JVMなど多くの言語向けのライブラリがあり、成熟度合いも様々です。 バインディングの公式なコレクションは、 https://github.com/libgit2 にあるリポジトリを探せば見つかります。 以降で示すコードはいずれも、最終的にHEADが指しているコミットのコミットメッセージを返します(git log -1 のようなものです)。

LibGit2Sharp

NET や Mono でアプリケーションを書いているなら、 LibGit2Sharp (https://github.com/libgit2/libgit2sharp) をお探しでしょう。

バインディングは C# で書かれていて、生の Libgit2 の呼び出しを、ネイティブ感のある CLR API でラップすることに細心の注意が払われています。 サンプルプログラムは次のようになります。

new Repository(@"C:\path\to\repo").Head.Tip.Message;

Windows向けのデスクトップアプリケーション向けにはNuGetパッケージもあります。これは、すぐに作業を始めようという時に役立ちます。

objective-git

Apple のプラットフォーム向けのアプリケーションを書いているなら、おそらく実装には Objective-C を使用しているものと思います。 Objective-Git (https://github.com/libgit2/objective-git) は、そういった環境向けの Libgit2 のバインディングです。 サンプルプログラムは次のようになります。

GTRepository *repo =
    [[GTRepository alloc] initWithURL:[NSURL fileURLWithPath: @"/path/to/repo"] error:NULL];
NSString *msg = [[[repo headReferenceWithError:NULL] resolvedTarget] message];

Objective-git は Swift に対しても完全な相互運用性があるので、 Objective-C を捨てたとしても怖くありません。

pygit2

Libgit2 の Python 向けバインディングは Pygit2 という名前で、 http://www.pygit2.org/ から取得できます。 サンプルプログラムは次のようになります。

pygit2.Repository("/path/to/repo") # リポジトリを開く
    .head                          # 現在のブランチを取得
    .peel(pygit2.Commit)           # HEADが指すコミットまで移動
    .message                       # メッセージを読む

参考文献

もちろん、 Libgit2 の機能の扱い方すべてを取り上げるのは、本書の範囲外です。 Libgit2 自体についてより多くの情報が必要な場合は、 API ドキュメントが https://libgit2.github.com/libgit2 にあります。また、ガイドが https://libgit2.github.com/docs にあります。 他のバインディングについては、同梱されている README やテストを見てみてください。ちょっとしたチュートリアルや、参考文献へのポインタが書かれていることがあります。