阅读(1429) (0)

A2.2 Libgit2

2016-02-24 15:23:07 更新

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 ,它包含了一个新文件的内容。