A2.2 Libgit2
最后更新于:2022-04-02 01:36:06
## Libgit2
另外一种可以供你使用的是 Libgit2。 Libgit2 是一个 Git 的非依赖性的工具,它致力于为其他程序使用 Git 提供更好的 API。 你可以在 [*http://libgit2.github.com*](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);
~~~
前两行打开一个 Git 版本库。 这个 `git_repository` 类型代表了一个在内存中带有缓存的指向一个版本库的句柄。 这是最简单的方法,只是你必须知道一个版本库的工作目录或者一个 `.git` 文件夹的精确路径。 另外还有 `git_repository_open_ext` ,它包括了带选项的搜索,`git_clone` 及其同类可以用来做远程版本库的本地克隆, `git_repository_init` 则可以创建一个全新的版本库。
第二段代码使用了一种 rev-parse 语法(要了解更多,请看 [分支引用](http://git-scm.com/book/zh/v2/1-git-tools/_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 来写有点蛋疼。
最后一点意味着你应该不会在使用 Libgit2 时编写 C 语言程序。 但幸运的是,有许多可用的各种语言的绑定,能让你在特定的语言和环境中更加容易的操作 Git 版本库。 我们来看一下下面这个用 Libgit2 的 Ruby 绑定写成的例子,它叫 Rugged,你可以在[*https://github.com/libgit2/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 使用异常机制,它可以抛出类似于`ConfigError` 或者 `ObjectError` 之类的东西来告知错误的情况。 其次,不需要明确资源释放,因为 Ruby 是支持垃圾回收的。 我们来看一个稍微复杂一点的例子:从头开始制作一个提交。
~~~
blob_id = repo.write("Blob contents", :blob)
index = repo.index
index.read_tree(repo.head.target.tree)
index.add(:path => 'newfile.txt', :oid => blob_id)
sig = {
:email => "bob@example.com",
:name => "Bob User",
:time => Time.now,
}
commit_id = Rugged::Commit.create(repo,
:tree => index.write_tree(repo),
:author => sig,
:committer => sig,
:message => "Add newfile.txt",
:parents => repo.empty? ? [] : [ repo.head.target ].compact,
:update_ref => 'HEAD',
)
commit = repo.lookup(commit_id)
~~~
[![](image/561a25142a2cd.png)](http://git-scm.com/book/zh/v2/ch00/co___git________CO1-1)
创建一个新的 blob ,它包含了一个新文件的内容。
[![](image/561a250d39b5c.png)](http://git-scm.com/book/zh/v2/ch00/co___git________CO1-2)
将 HEAD 提交树填入索引,并在路径 `newfile.txt` 增加新文件。
[![](image/561a250a858f0.png)](http://git-scm.com/book/zh/v2/ch00/co___git________CO1-3)
这就在 ODB 中创建了一个新的树,并在一个新的提交中使用它。
[![](image/561a24fed1af7.png)](http://git-scm.com/book/zh/v2/ch00/co___git________CO1-4)
我们在 author 栏和 committer 栏使用相同的签名。
[![](image/561a24fdc1b9c.png)](http://git-scm.com/book/zh/v2/ch00/co___git________CO1-5)
提交的信息。
[![](image/561a24fcb8e13.png)](http://git-scm.com/book/zh/v2/ch00/co___git________CO1-6)
当创建一个提交时,你必须指定这个新提交的父提交。 这里使用了 HEAD 的末尾作为单一的父提交。
[![](image/561a24fb4af80.png)](http://git-scm.com/book/zh/v2/ch00/co___git________CO1-7)
在做一个提交的过程中, Rugged (和 Libgit2 )能在需要时更新引用。
[![](image/561a24f0071f6.png)](http://git-scm.com/book/zh/v2/ch00/co___git________CO1-8)
返回值是一个新提交对象的 SHA-1 哈希,你可以用它来获得一个 `Commit` 对象。
Ruby 的代码很好很简洁,另一方面因为 Libgit2 做了大量工作,所以代码运行起来其实速度也不赖。 如果你不是一个 Ruby 程序员,我们在 [其它绑定](http://git-scm.com/book/zh/v2/ch00/_libgit2_bindings) 有提到其它的一些绑定。
### [高级功能](http://git-scm.com/book/zh/v2/%E5%B0%86-Git-%E5%B5%8C%E5%85%A5%E4%BD%A0%E7%9A%84%E5%BA%94%E7%94%A8-Libgit2#高级功能)
Libgit2 有几个超过核心 Git 的能力。 例如它的可定制性:Libgit2 允许你为一些不同类型的操作自定义的“后端”,让你得以使用与原生 Git 不同的方式存储东西。 Libgit2 允许为自定义后端指定配置、引用的存储以及对象数据库,
我们来看一下它究竟是怎么工作的。 下面的例子借用自 Libgit2 团队提供的后端样本集 (可以在[*https://github.com/libgit2/libgit2-backends*](https://github.com/libgit2/libgit2-backends) 上找到)。 一个对象数据库的自定义后端是这样建立的:
~~~
git_odb *odb;
int error = git_odb_new(&odb);
git_odb_backend *my_backend;
error = git_odb_backend_mine(&my_backend, /*…*/);
error = git_odb_add_backend(odb, my_backend, 1);
git_repository *repo;
error = git_repository_open(&repo, "some-path");
error = git_repository_set_odb(odb);
~~~
*(注意:这个错误被捕获了,但是没有被处理。我们希望你的代码比我们的更好。)*
[![](image/561a24ee65d8b.png)](http://git-scm.com/book/zh/v2/ch00/co___git________CO2-1)
初始化一个空的对象数据库( ODB ) “前端”,它将被作为一个用来做真正的工作的 “后端” 的容器。
[![](image/561a24e791845.png)](http://git-scm.com/book/zh/v2/ch00/co___git________CO2-2)
初始化一个自定义 ODB 后端。
[![](image/561a24e59cad8.png)](http://git-scm.com/book/zh/v2/ch00/co___git________CO2-3)
为这个前端增加一个后端。
[![](image/561a24e425699.png)](http://git-scm.com/book/zh/v2/ch00/co___git________CO2-4)
打开一个版本库,并让它使用我们的 ODB 来寻找对象。
但是 `git_odb_backend_mine` 是个什么东西呢? 嗯,那是一个你自己的 ODB 实现的构造器,并且你能在那里做任何你想做的事,前提是你能正确地填写 `git_odb_backend` 结构。 它看起来*应该*是这样的:
~~~
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` 源码以了解全部调用签名,你特定的使用环境会帮你决定使用哪一种调用签名。
### [其它绑定](http://git-scm.com/book/zh/v2/%E5%B0%86-Git-%E5%B5%8C%E5%85%A5%E4%BD%A0%E7%9A%84%E5%BA%94%E7%94%A8-Libgit2#其它绑定)
Libgit2 有很多种语言的绑定。 在这篇文章中,我们展现了一个使用了几个更加完整的绑定包的小例子,这些库存在于许多种语言中,包括 C++、Go、Node.js、Erlang 以及 JVM ,它们的成熟度各不相同。 官方的绑定集合可以通过浏览这个版本库得到:https://github.com/libgit2[] 。 我们写的代码将返回当前 HEAD 指向的提交的提交信息(就像 `git log -1` 那样)。
LibGit2Sharp
如果你在编写一个 .NET 或者 Mono 应用,那么 LibGit2Sharp ([*https://github.com/libgit2/libgit2sharp*](https://github.com/libgit2/libgit2sharp)) 就是你所需要的。 这个绑定是用 C# 写成的,并且已经采取许多措施来用令人感到自然的 CLR API 包装原始的 Libgit2 的调用。 我们的例子看起来就像这样:
~~~
new Repository(@"C:\path\to\repo").Head.Tip.Message;
~~~
对于 Windows 桌面应用,一个叫做 NuGet 的包会让你快速上手。
objective-git
如果你的应用运行在一个 Apple 平台上,你很有可能使用 Objective-C 作为实现语言。 Objective-Git ([*https://github.com/libgit2/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 ,你可以在 [*http://www.pygit2.org/*](http://www.pygit2.org/) 找到它。 我们的示例程序:
~~~
pygit2.Repository("/path/to/repo") # 打开版本库
.head # get the current branch
.peel(pygit2.Commit) # walk down to the commit
.message # read the message
~~~
### [扩展阅读](http://git-scm.com/book/zh/v2/%E5%B0%86-Git-%E5%B5%8C%E5%85%A5%E4%BD%A0%E7%9A%84%E5%BA%94%E7%94%A8-Libgit2#扩展阅读)
当然,完全阐述 Libgit2 的能力已超出本书范围。 如果你想了解更多关于 Libgit2 的信息,可以浏览它的 API 文档: [*https://libgit2.github.com/libgit2*](https://libgit2.github.com/libgit2), 以及一系列的指南:[*https://libgit2.github.com/docs*](https://libgit2.github.com/docs). 对于其它的绑定,检查附带的 README 和测试文件,那里通常有简易教程,以及指向拓展阅读的链接。
';