章节 ▾ 第二版

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 全部功能的一小部分示例。如果您对此感兴趣并想了解更多,可以在以下位置查找信息和灵感: