章节 ▾ 第二版

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

Plumbing(底层命令)

JGit 具有两个基本的 API 层级:plumbing(底层)和 porcelain(高层)。这些术语源自 Git 本身,JGit 也大致划分为相同的领域:porcelain API 是针对常见用户级操作的友好前端(即普通用户使用 Git 命令行工具时所做的那些事),而 plumbing 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 引用(位于 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 实例使用了我们之前打开的仓库进行本地配置,但它也会自动检测全局和系统配置文件并从中读取值。

这只是完整 plumbing API 的一小部分;还有许多可用的方法和类。这里未展示的是 JGit 处理错误的方式,即通过异常。JGit API 有时会抛出标准的 Java 异常(例如 IOException),但同时也提供了一系列 JGit 特有的异常类型(例如 NoRemoteRepositoryExceptionCorruptObjectExceptionNoMergeBaseException)。

Porcelain(高层命令)

Plumbing 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 远程仓库请求标签,而不是请求分支。还要注意 CredentialsProvider 对象用于身份验证。

通过 Git 类可以使用许多其他命令,包括但不限于 addblamecommitcleanpushrebaserevertreset

延伸阅读

这只是 JGit 全部功能的冰山一角。如果您感兴趣并想了解更多信息,以下是获取信息和灵感的地方: