章 ▾ 第2版

A2.2 付録B: アプリケーションへのGitの組み込み - Libgit2

Libgit2

もう一つの選択肢としてLibgit2を使用できます。Libgit2は、他のプログラム内での使用に適したAPIを持つことに重点を置いた、依存関係のないGitの実装です。https://libgit2.orgで見つけることができます。

まず、C APIがどのようなものかを見てみましょう。以下に概要を説明します

// Open a repository
git_repository *repo;
int error = git_repository_open(&repo, "/path/to/repository");

// Dereference HEAD to a commit
git_object *head_commit;
error = git_revparse_single(&head_commit, repo, "HEAD^{commit}");
git_commit *commit = (git_commit*)head_commit;

// Print some of the commit's properties
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);

// Cleanup
git_commit_free(commit);
git_repository_free(repo);

最初の数行で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呼び出しに渡した場合、その呼び出しは整数エラーコードを返す可能性があります。0は成功を示し、それ以外はエラーです。

  • Libgit2がポインタを埋める場合、それを解放する責任はあなたにあります。

  • Libgit2が呼び出しからconstポインタを返す場合、それを解放する必要はありませんが、それが属するオブジェクトが解放されると無効になります。

  • C言語での記述は少々骨が折れます。

最後の項目は、Libgit2を使用する際にC言語で記述する可能性はあまり高くないことを意味します。幸いなことに、特定の言語や環境からGitリポジトリを簡単に操作できる、多数の言語固有のバインディングが利用可能です。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などを発生させることができます。次に、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. 新しいファイルのコンテンツを含む新しいブロブを作成します。

  2. ヘッドコミットのツリーでインデックスを埋め、パスnewfile.txtに新しいファイルを追加します。

  3. これにより、ODBに新しいツリーが作成され、新しいコミットに使用されます。

  4. 著者フィールドとコミッターフィールドの両方に同じ署名を使用します。

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

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

  7. Rugged(およびLibgit2)は、コミットを行う際にオプションで参照を更新できます。

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

Rubyコードはきれいで読みやすいですが、Libgit2が重い処理を行っているため、このコードもかなり高速に実行されます。Rubyistでない場合は、その他のバインディングでいくつかの他のバインディングについて触れています。

高度な機能

Libgit2には、コアGitの範囲外のいくつかの機能があります。その一例がプラグイン性です。Libgit2では、いくつかの種類の操作に対してカスタムの「バックエンド」を提供できるため、通常のGitとは異なる方法でデータを保存できます。Libgit2は、設定、参照ストレージ、オブジェクトデータベースなどに対してカスタムバックエンドを許可しています。

これがどのように機能するかを見てみましょう。以下のコードは、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(repo, 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

Python用のLibgit2バインディングはPygit2と呼ばれ、https://www.pygit2.orgで見つけることができます。例のプログラムは次のようになります。

pygit2.Repository("/path/to/repo") # open repository
    .head                          # get the current branch
    .peel(pygit2.Commit)           # walk down to the commit
    .message                       # read the message

さらに読む

もちろん、Libgit2の全機能の詳細な説明はこの書籍の範囲外です。Libgit2自体に関するより詳しい情報が必要な場合は、https://libgit2.github.com/libgit2にAPIドキュメントがあり、https://libgit2.github.com/docsにガイド集があります。他のバインディングについては、同梱されているREADMEとテストを確認してください。そこには、小さなチュートリアルやさらに読むべき資料へのポインタがよくあります。

scroll-to-top