A2.2 Libgit2
Libgit2
另外一种可以供你使用的是 Libgit2。 Libgit2 是一个 Git 的非依赖性的工具,它致力于为其他程序使用 Git 提供更好的 API。 你可以在 找到它。
首先,让我们来看一下 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 语法(要了解更多,请看 分支引用 )来得到 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,你可以在 找到它。
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)
创建一个新的 blob ,它包含了一个新文件的内容。
更多建议: