-
1. 起步
-
2. Git 基础
-
3. Git 分支
-
4. 服务器上的 Git
- 4.1 协议
- 4.2 在服务器上部署 Git
- 4.3 生成 SSH 公钥
- 4.4 架设服务器
- 4.5 Git Daemon
- 4.6 Smart HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 第三方托管服务
- 4.10 小结
-
5. 分布式 Git
-
A1. 附录 A: Git 在其他环境
- A1.1 图形界面
- A1.2 Visual Studio 中的 Git
- A1.3 Visual Studio Code 中的 Git
- A1.4 IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine 中的 Git
- A1.5 Sublime Text 中的 Git
- A1.6 Bash 中的 Git
- A1.7 Zsh 中的 Git
- A1.8 PowerShell 中的 Git
- A1.9 小结
-
A2. 附录 B: 在应用程序中嵌入 Git
-
A3. 附录 C: Git 命令
A2.2 附录 B:在您的应用程序中嵌入 Git - Libgit2
Libgit2
您还可以选择使用 Libgit2。Libgit2 是 Git 的一个无依赖实现,侧重于为其他程序提供一个优秀的 API。您可以在 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 用于创建全新的仓库。
第二段代码使用 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 调用,该调用很可能会返回一个整数错误代码。
0表示成功;任何小于 0 的值都表示错误。 -
如果 Libgit2 为您填充了一个指针,您负责释放它。
-
如果 Libgit2 从调用中返回一个
const指针,您不必释放它,但当它所属的对象被释放时,该指针将失效。 -
编写 C 有点痛苦。
最后一点意味着在使用 Libgit2 时,您不太可能用 C 编写代码。幸运的是,有许多特定语言的绑定可供使用,它们可以轻松地从您特定的语言和环境中处理 Git 仓库。让我们来看一下使用 Libgit2 的 Ruby 绑定的上述示例,这些绑定名为 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) # (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)
-
创建一个新的 blob,其中包含新文件的内容。
-
使用 head commit 的 tree 填充索引,并在
newfile.txt路径下添加新文件。 -
这会在 ODB 中创建一个新的 tree,并将其用于新提交。
-
我们对作者和提交者字段使用相同的签名。
-
提交消息。
-
创建提交时,您必须指定新提交的父提交。这使用了 HEAD 的尖端作为单个父提交。
-
Rugged(和 Libgit2)可以在提交时选择性地更新引用。
-
返回值是新提交对象(commit object)的 SHA-1 哈希,然后您可以使用它来获取一个
Commit对象。
Ruby 代码干净整洁,但由于 Libgit2 负责繁重的工作,所以代码运行速度也会很快。如果您不是 Ruby 开发者,我们将在 其他绑定 中介绍一些其他的绑定。
高级功能
Libgit2 有一些核心 Git 范围之外的功能。一个例子是可插拔性:Libgit2 允许您为多种操作提供自定义“后端”,因此您可以以不同于标准 Git 的方式存储内容。Libgit2 允许使用配置、引用存储和对象数据库等自定义后端。
让我们看看这是如何工作的。下面的代码摘自 Libgit2 团队提供的后端示例集(可以在 https://github.com/libgit2/libgit2-backends 找到)。以下是如何设置自定义对象数据库(ODB)后端。
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)
请注意,错误被捕获但未处理。我们希望您的代码比我们的好。
-
初始化一个空的“前端”对象数据库(ODB),它将作为执行实际工作的“后端”的容器。
-
初始化一个自定义 ODB 后端。
-
将后端添加到前端。
-
打开一个仓库,并设置它使用我们的 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# 编写的,并且花费了大量精力来用原生感觉的 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)是 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
Libgit2 在 Python 中的绑定称为 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 和测试;通常会有小型教程和进一步阅读的提示。