## 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)
~~~
[](http://git-scm.com/book/zh/v2/ch00/co___git________CO1-1)
創建一個新的 blob ,它包含了一個新文件的內容。
[](http://git-scm.com/book/zh/v2/ch00/co___git________CO1-2)
將 HEAD 提交樹填入索引,并在路徑?`newfile.txt`?增加新文件。
[](http://git-scm.com/book/zh/v2/ch00/co___git________CO1-3)
這就在 ODB 中創建了一個新的樹,并在一個新的提交中使用它。
[](http://git-scm.com/book/zh/v2/ch00/co___git________CO1-4)
我們在 author 欄和 committer 欄使用相同的簽名。
[](http://git-scm.com/book/zh/v2/ch00/co___git________CO1-5)
提交的信息。
[](http://git-scm.com/book/zh/v2/ch00/co___git________CO1-6)
當創建一個提交時,你必須指定這個新提交的父提交。 這里使用了 HEAD 的末尾作為單一的父提交。
[](http://git-scm.com/book/zh/v2/ch00/co___git________CO1-7)
在做一個提交的過程中, Rugged (和 Libgit2 )能在需要時更新引用。
[](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);
~~~
*(注意:這個錯誤被捕獲了,但是沒有被處理。我們希望你的代碼比我們的更好。)*
[](http://git-scm.com/book/zh/v2/ch00/co___git________CO2-1)
初始化一個空的對象數據庫( ODB ) “前端”,它將被作為一個用來做真正的工作的 “后端” 的容器。
[](http://git-scm.com/book/zh/v2/ch00/co___git________CO2-2)
初始化一個自定義 ODB 后端。
[](http://git-scm.com/book/zh/v2/ch00/co___git________CO2-3)
為這個前端增加一個后端。
[](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 和測試文件,那里通常有簡易教程,以及指向拓展閱讀的鏈接。
- 前言
- Scott Chacon 序
- Ben Straub 序
- 獻辭
- 貢獻者
- 引言
- 1. 起步
- 1.1 關于版本控制
- 1.2 Git 簡史
- 1.3 Git 基礎
- 1.4 命令行
- 1.5 安裝 Git
- 1.6 初次運行 Git 前的配置
- 1.7 獲取幫助
- 1.8 總結
- 2. Git 基礎
- 2.1 獲取 Git 倉庫
- 2.2 記錄每次更新到倉庫
- 2.3 查看提交歷史
- 2.4 撤消操作
- 2.5 遠程倉庫的使用
- 2.6 打標簽
- 2.7 Git 別名
- 2.8 總結
- 3. Git 分支
- 3.1 分支簡介
- 3.2 分支的新建與合并
- 3.3 分支管理
- 3.4 分支開發工作流
- 3.5 遠程分支
- 3.6 變基
- 3.7 總結
- 4. 服務器上的 Git
- 4.1 協議
- 4.2 在服務器上搭建 Git
- 4.3 生成 SSH 公鑰
- 4.4 配置服務器
- 4.5 Git 守護進程
- 4.6 Smart HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 第三方托管的選擇
- 4.10 總結
- 5. 分布式 Git
- 5.1 分布式工作流程
- 5.2 向一個項目貢獻
- 5.3 維護項目
- 5.4 總結
- 6. GitHub
- 6.1 賬戶的創建和配置
- 6.2 對項目做出貢獻
- 6.3 維護項目
- 6.4 管理組織
- 6.5 腳本 GitHub
- 6.6 總結
- 7. Git 工具
- 7.1 選擇修訂版本
- 7.2 交互式暫存
- 7.3 儲藏與清理
- 7.4 簽署工作
- 7.5 搜索
- 7.6 重寫歷史
- 7.7 重置揭密
- 7.8 高級合并
- 7.9 Rerere
- 7.10 使用 Git 調試
- 7.11 子模塊
- 7.12 打包
- 7.13 替換
- 7.14 憑證存儲
- 7.15 總結
- 8. 自定義 Git
- 8.1 配置 Git
- 8.2 Git 屬性
- 8.3 Git 鉤子
- 8.4 使用強制策略的一個例子
- 8.5 總結
- 9. Git 與其他系統
- 9.1 作為客戶端的 Git
- 9.2 遷移到 Git
- 9.3 總結
- 10. Git 內部原理
- 10.1 底層命令和高層命令
- 10.2 Git 對象
- 10.3 Git 引用
- 10.4 包文件
- 10.5 引用規格
- 10.6 傳輸協議
- 10.7 維護與數據恢復
- 10.8 環境變量
- 10.9 總結
- A. 其它環境中的 Git
- A1.1 圖形界面
- A1.2 Visual Studio 中的 Git
- A1.3 Eclipse 中的 Git
- A1.4 Bash 中的 Git
- A1.5 Zsh 中的 Git
- A1.6 Powershell 中的 Git
- A1.7 總結
- B. 將 Git 嵌入你的應用
- A2.1 命令行 Git 方式
- A2.2 Libgit2
- A2.3 JGit
- C. Git 命令
- A3.1 設置與配置
- A3.2 獲取與創建項目
- A3.3 快照基礎
- A3.4 分支與合并
- A3.5 項目分享與更新
- A3.6 檢查與比較
- A3.7 調試
- A3.8 補丁
- A3.9 郵件
- A3.10 外部系統
- A3.11 管理
- A3.12 底層命令