Git
章 ▾ 第2版

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

Libgit2

利用可能なもう1つのオプションは、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型を使用しています。これはSHA-1ハッシュのLibgit2の表現です。

このサンプルから、いくつかのパターンが出現し始めました。

  • ポインタを宣言して、その参照をLibgit2呼び出しに渡すと、その呼び出しはおそらく整数のエラーコードを返します。 0の値は成功を示します。それより小さい値はエラーです。

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

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

  • C言語を書くのは少し苦痛です。

最後の文は、Libgit2を使用する際にC言語を書く可能性は非常に低いことを意味します。幸いなことに、特定の言語と環境からGitリポジトリをかなり簡単に操作できるようにする、多くの言語固有のバインディングが利用可能です。Libgit2のRubyバインディングであるRuggedを使用して記述された上記の例を見てみましょう。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. インデックスにHEADコミットのツリーを設定し、パスnewfile.txtに新しいファイルを追加します。

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

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

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

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

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

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

Rubyのコードはきれいで簡潔ですが、Libgit2が重い処理を行っているため、このコードもかなり高速に実行されます。Rubyプログラマーでない場合は、「その他のバインディング」で他のバインディングについて触れています。

高度な機能

Libgit2には、コアGitの範囲外のいくつかの機能があります。1つの例はプラグイン性です。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(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