章節 ▾ 第二版

A2.3 附錄 B:在您的應用程式中嵌入 Git - JGit

JGit

如果您想在 Java 程式中使用 Git,有一個功能齊全的 Git 庫名為 JGit。JGit 是一個用 Java 原生編寫的、功能相對齊全的 Git 實現,並且在 Java 社群中被廣泛使用。JGit 專案隸屬於 Eclipse,其主頁位於 https://projects.eclipse.org/projects/technology.jgit

準備工作

有多種方法可以將您的專案與 JGit 連線起來並開始編寫相關程式碼。最簡單的方法可能是使用 Maven – 透過將以下程式碼片段新增到您 pom.xml 檔案的 <dependencies> 標籤中即可實現整合:

<dependency>
    <groupId>org.eclipse.jgit</groupId>
    <artifactId>org.eclipse.jgit</artifactId>
    <version>3.5.0.201409260305-r</version>
</dependency>

version 很可能在你閱讀本文時已經更新;請檢視 https://mvnrepository.com/artifact/org.eclipse.jgit/org.eclipse.jgit 以獲取最新的儲存庫資訊。完成此步驟後,Maven 將自動獲取並使用您需要的 JGit 庫。

如果您寧願自己管理二進位制依賴項,可以從 https://projects.eclipse.org/projects/technology.jgit/downloads 獲取預編譯的 JGit 二進位制檔案。您可以透過執行類似這樣的命令將其整合到您的專案中:

javac -cp .:org.eclipse.jgit-3.5.0.201409260305-r.jar App.java
java -cp .:org.eclipse.jgit-3.5.0.201409260305-r.jar App

底層 API (Plumbing)

JGit 提供了兩個基本級別的 API:底層 API (plumbing) 和高層 API (porcelain)。這些術語源於 Git 本身,JGit 大致也分為同類區域:高層 API 是常用使用者級別操作(普通使用者使用 Git 命令列工具執行的操作)的友好前端,而底層 API 則用於直接與低級別儲存庫物件互動。

大多數 JGit 會話的起點是 Repository 類,您首先需要建立一個它的例項。對於基於檔案系統的儲存庫(是的,JGit 也支援其他儲存模型),這可以透過 FileRepositoryBuilder 來實現:

// Create a new repository
Repository newlyCreatedRepo = FileRepositoryBuilder.create(
    new File("/tmp/new_repo/.git"));
newlyCreatedRepo.create();

// Open an existing repository
Repository existingRepo = new FileRepositoryBuilder()
    .setGitDir(new File("my_repo/.git"))
    .build();

該構建器具有流暢的 API,可以提供查詢 Git 儲存庫所需的所有資訊,無論您的程式是否確切知道其位置。它可以利用環境變數(.readEnvironment()),從工作目錄的某個位置開始搜尋(.setWorkTree(…).findGitDir()),或者像上面那樣直接開啟一個已知的 .git 目錄。

獲得 Repository 例項後,您可以對其進行各種操作。以下是一個簡短的示例:

// Get a reference
Ref master = repo.getRef("master");

// Get the object the reference points to
ObjectId masterTip = master.getObjectId();

// Rev-parse
ObjectId obj = repo.resolve("HEAD^{tree}");

// Load raw object contents
ObjectLoader loader = repo.open(masterTip);
loader.copyTo(System.out);

// Create a branch
RefUpdate createBranch1 = repo.updateRef("refs/heads/branch1");
createBranch1.setNewObjectId(masterTip);
createBranch1.update();

// Delete a branch
RefUpdate deleteBranch1 = repo.updateRef("refs/heads/branch1");
deleteBranch1.setForceUpdate(true);
deleteBranch1.delete();

// Config
Config cfg = repo.getConfig();
String name = cfg.getString("user", null, "name");

這裡有很多內容,讓我們逐節進行分析。

第一行獲取指向 master 引用的指標。JGit 會自動獲取實際的 master ref,它位於 refs/heads/master,並返回一個允許您獲取引用資訊的物件。您可以獲取名稱(.getName()),以及直接引用的目標物件(.getObjectId())或符號引用的目標(.getTarget())。Ref 物件也用於表示標籤引用和物件,因此您可以詢問該標籤是否被“剝離”(peeled),這意味著它指向一個(可能很長的)標籤物件鏈的最終目標。

第二行獲取 master 引用的目標,並將其作為 ObjectId 例項返回。ObjectId 表示物件的 SHA-1 雜湊值,該物件可能存在於 Git 的物件資料庫中,也可能不存在。第三行類似,但展示了 JGit 如何處理 rev-parse 語法(更多資訊請參閱 分支引用);您可以傳遞 Git 理解的任何物件說明符,JGit 將返回該物件的有效 ObjectId 或 null

接下來的兩行展示瞭如何載入物件的原始內容。在此示例中,我們呼叫 ObjectLoader.copyTo() 將物件內容直接流式傳輸到 stdout,但 ObjectLoader 還提供了讀取物件型別和大小的方法,以及將其作為位元組陣列返回的方法。對於大型物件(其中 .isLarge() 返回 true),您可以呼叫 .openStream() 來獲取一個類似 InputStream 的物件,該物件可以在不將所有資料載入到記憶體中的情況下讀取原始物件資料。

接下來的幾行展示了建立新分支所需的步驟。我們建立一個 RefUpdate 例項,配置一些引數,然後呼叫 .update() 來觸發更改。緊隨其後的是刪除相同分支的程式碼。請注意,.setForceUpdate(true) 是必需的,否則 .delete() 呼叫將返回 REJECTED,並且不會發生任何事情。

最後一個示例展示瞭如何從 Git 配置檔案中獲取 user.name 值。此 Config 例項使用我們之前開啟的用於本地配置的儲存庫,但也會自動檢測全域性和系統配置檔案並從中讀取值。

這只是底層 API 的一小部分示例;還有更多可用的方法和類。這裡也沒有展示 JGit 如何處理錯誤,它是透過使用異常來處理的。JGit API 有時會丟擲標準的 Java 異常(如 IOException),但也有許多 JGit 特有的異常型別(如 NoRemoteRepositoryExceptionCorruptObjectExceptionNoMergeBaseException)。

高層 API (Porcelain)

底層 API 功能相當完整,但將它們組合起來實現常見目標,例如將檔案新增到索引或進行新提交,可能會很麻煩。JGit 提供了一套更高級別的 API 來提供幫助,這些 API 的入口點是 Git 類:

Repository repo;
// construct repo...
Git git = new Git(repo);

Git 類提供了一組不錯的、高階的構建器式方法,可用於構建相當複雜的功能。讓我們來看一個示例 — 執行類似 git ls-remote 的操作:

CredentialsProvider cp = new UsernamePasswordCredentialsProvider("username", "p4ssw0rd");
Collection<Ref> remoteRefs = git.lsRemote()
    .setCredentialsProvider(cp)
    .setRemote("origin")
    .setTags(true)
    .setHeads(false)
    .call();
for (Ref ref : remoteRefs) {
    System.out.println(ref.getName() + " -> " + ref.getObjectId().name());
}

這是 Git 類的一個常見模式;方法返回一個命令物件,允許您鏈式呼叫方法來設定引數,這些引數在呼叫 .call() 時執行。在這種情況下,我們正在向 origin 遠端請求標籤,但不請求 heads。同時還請注意使用 CredentialsProvider 物件進行身份驗證。

透過 Git 類還可以使用許多其他命令,包括但不限於 addblamecommitcleanpushrebaserevertreset

延伸閱讀

這只是 JGit 全部功能的一小部分示例。如果您對此感興趣並想了解更多,可以在以下位置查詢資訊和靈感: