=== Libgit2

(((libgit2)))(((C)))
另外一种可以供你使用的是 Libgit2。
Libgit2 是一个 Git 的非依赖性的工具，它致力于为其他程序使用 Git 提供更好的 API。
你可以在 http://libgit2.github.com[] 找到它。

首先，让我们来看一下 C API 长啥样。
这是一个旋风式旅行。

[source,c]
-----
// 打开一个版本库
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);
-----

前两行打开一个 Git 版本库。
这个 `git_repository` 类型代表了一个在内存中带有缓存的指向一个版本库的句柄。
这是最简单的方法，只是你必须知道一个版本库的工作目录或者一个 `.git` 文件夹的精确路径。
另外还有 `git_repository_open_ext` ，它包括了带选项的搜索， `git_clone` 及其同类可以用来做远程版本库的本地克隆， `git_repository_init` 则可以创建一个全新的版本库。

第二段代码使用了一种 rev-parse 语法（要了解更多，请看 <<ch07-git-tools#r_branch_references>> ）来得到 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 来写有点蛋疼。

(((Ruby)))
最后一点意味着你应该不会在使用 Libgit2 时编写 C 语言程序。
但幸运的是，有许多可用的各种语言的绑定，能让你在特定的语言和环境中更加容易的操作 Git 版本库。
我们来看一下下面这个用 Libgit2 的 Ruby 绑定写成的例子，它叫 Rugged，你可以在 https://github.com/libgit2/rugged[] 找到它。

[source,ruby]
----
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 使用异常机制，它可以抛出类似于 `ConfigError` 或者 `ObjectError` 之类的东西来告知错误的情况。
其次，不需要明确资源释放，因为 Ruby 是支持垃圾回收的。
我们来看一个稍微复杂一点的例子：从头开始制作一个提交。

[source,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 做了大量工作，所以代码运行起来其实速度也不赖。
如果你不是一个 Ruby 程序员，我们在 <<r_libgit2_bindings>> 有提到其它的一些绑定。


==== 高级功能

Libgit2 有几个超过核心 Git 的能力。
例如它的可定制性：Libgit2 允许你为一些不同类型的操作自定义的``后端''，让你得以使用与原生 Git 不同的方式存储东西。
Libgit2 允许为自定义后端指定配置、引用的存储以及对象数据库，

我们来看一下它究竟是怎么工作的。
下面的例子借用自 Libgit2 团队提供的后端样本集 （可以在 https://github.com/libgit2/libgit2-backends[] 上找到）。
一个对象数据库的自定义后端是这样建立的：

[source,c]
----
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` 结构。
它看起来_应该_是这样的：

[source,c]
----
typedef struct {
    git_odb_backend parent;

    // 其它的一些东西
    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` 源码以了解全部调用签名，你特定的使用环境会帮你决定使用哪一种调用签名。

[[r_libgit2_bindings]]
==== 其它绑定

Libgit2 有很多种语言的绑定。
在这篇文章中，我们展现了一个使用了几个更加完整的绑定包的小例子，这些库存在于许多种语言中，包括 C++、Go、Node.js、Erlang 以及 JVM ，它们的成熟度各不相同。
官方的绑定集合可以通过浏览这个版本库得到：https://github.com/libgit2[] 。
我们写的代码将返回当前 HEAD 指向的提交的提交信息(就像 `git log -1` 那样)。


===== LibGit2Sharp

(((.NET)))(((C#)))(((Mono)))
如果你在编写一个 .NET 或者 Mono 应用，那么 LibGit2Sharp (https://github.com/libgit2/libgit2sharp[]) 就是你所需要的。
这个绑定是用 C# 写成的，并且已经采取许多措施来用令人感到自然的 CLR API 包装原始的 Libgit2 的调用。
我们的例子看起来就像这样：

[source,csharp]
-----
new Repository(@"C:\path\to\repo").Head.Tip.Message;
-----

对于 Windows 桌面应用，一个叫做 NuGet 的包会让你快速上手。

===== objective-git

(((Apple)))(((Objective-C)))(((Cocoa)))
如果你的应用运行在一个 Apple 平台上，你很有可能使用 Objective-C 作为实现语言。
Objective-Git (https://github.com/libgit2/objective-git[]) 是这个环境下的 Libgit2 绑定。
一个例子看起来类似这样：

[source,objc]
-----
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)))
Python 的 Libgit2 绑定叫做 Pygit2 ，你可以在 http://www.pygit2.org/[] 找到它。
我们的示例程序：

[source,python]
----
pygit2.Repository("/path/to/repo") # 打开版本库
    .head                          # get the current branch
    .peel(pygit2.Commit)           # walk down to the commit
    .message                       # read the message
----


==== 扩展阅读

当然，完全阐述 Libgit2 的能力已超出本书范围。
如果你想了解更多关于 Libgit2 的信息，可以浏览它的 API 文档： https://libgit2.github.com/libgit2[], 以及一系列的指南： https://libgit2.github.com/docs[].
对于其它的绑定，检查附带的 README 和测试文件，那里通常有简易教程，以及指向拓展阅读的链接。
