如何在JGit中执行“cat”文件操作?

31

前段时间我在寻找一个Java中可嵌入的分布式版本控制系统,我认为我在JGit中找到了它,这是git的纯Java实现。然而,缺少示例代码或教程。

我该如何使用JGit检索某个文件的HEAD版本(就像svn cathg cat一样)?

我想这涉及一些rev-tree-walking,并正在寻找代码示例。


1
开发人员在邮件列表上回复非常迅速:https://dev.eclipse.org/mailman/listinfo/egit-dev。我建议你试一试。 - Robert Munteanu
7个回答

23

很遗憾,Thilo的答案在最新的JGit API中不起作用。这是我找到的解决方法:

File repoDir = new File("test-git");
// open the repository
Repository repository = new Repository(repoDir);
// find the HEAD
ObjectId lastCommitId = repository.resolve(Constants.HEAD);
// now we have to get the commit
RevWalk revWalk = new RevWalk(repository);
RevCommit commit = revWalk.parseCommit(lastCommitId);
// and using commit's tree find the path
RevTree tree = commit.getTree();
TreeWalk treeWalk = new TreeWalk(repository);
treeWalk.addTree(tree);
treeWalk.setRecursive(true);
treeWalk.setFilter(PathFilter.create(path));
if (!treeWalk.next()) {
  return null;
}
ObjectId objectId = treeWalk.getObjectId(0);
ObjectLoader loader = repository.open(objectId);

// and then one can use either
InputStream in = loader.openStream()
// or
loader.copyTo(out)

我希望它更简单。


46
这个 API 是谁设计的? - Jochen
3
你知道treeWalk.getObjectId(nth)中的第n个值是做什么用的吗?也就是说,我们将一个大于0的值传递给treeWalk.getObjectId的情况是什么? - Dinis Cruz
@DinisCruz的TreeWalk可以遍历多个树(通过多次调用addTree)。在这种情况下,您可以使用getObjectId(N)从树N中获取对象ID(根据树的不同可能相同或不同)。 - robinst
请参考creinig的答案,了解如何通过使用TreeWalk.forPath来简化此过程。 - robinst
我该如何将loader的输出保存到一个变量中? - Evgenij Reznik

18

这是 @morisil 的答案的一个更简单的版本,使用了 @directed laugh 的一些概念,并使用 JGit 2.2.0 进行了测试:

private String fetchBlob(String revSpec, String path) throws MissingObjectException, IncorrectObjectTypeException,
        IOException {

    // Resolve the revision specification
    final ObjectId id = this.repo.resolve(revSpec);

    // Makes it simpler to release the allocated resources in one go
    ObjectReader reader = this.repo.newObjectReader();

    try {
        // Get the commit object for that revision
        RevWalk walk = new RevWalk(reader);
        RevCommit commit = walk.parseCommit(id);

        // Get the revision's file tree
        RevTree tree = commit.getTree();
        // .. and narrow it down to the single file's path
        TreeWalk treewalk = TreeWalk.forPath(reader, path, tree);

        if (treewalk != null) {
            // use the blob id to read the file's data
            byte[] data = reader.open(treewalk.getObjectId(0)).getBytes();
            return new String(data, "utf-8");
        } else {
            return "";
        }
    } finally {
        reader.close();
    }
}

repo是在其他答案中创建的Repository对象。


1
看起来不错,除了返回类型是String而返回的是getBytes()。请注意,您还必须在释放资源时调用walktreeWalk上的release方法。为了只需要执行一次,可以调用ObjectReader reader = repo.newObjectReader()并将其传递给Revwalk和Treewalk,而不是仓库。然后在finally块中调用reader.release() - robinst
RevWalk也需要被释放,将调用newObjectReader的位置上移,并使用new RevWalk(reader)代替。 - robinst
另一个注释(抱歉:)),this.repo.open 也应该被替换为 reader.open - robinst
@robinst:已修复。如有需要,请随意编辑答案 ;) - creinig

12

我按照@Thilo和@morisil的答案进行了操作,这个方法兼容JGit 1.2.0:

File repoDir = new File("test-git/.git");
// open the repository
Repository repo = new Repository(repoDir);
// find the HEAD
Commit head = repo.mapCommit(Constants.HEAD);
// retrieve the tree in HEAD
Tree tree = head.getTree();

// 1.2.0 api version here
// find a file (as a TreeEntry, which contains the blob object id)
TreeWalk treewalk = TreeWalk.forPath(repo, "b/test.txt", tree);
// use the blob id to read the file's data
byte[] data = repo.open(treewalk.getObjectId(0)).getBytes();
我没测试Java版本,但应该可以正常工作。它从...翻译。
(.getBytes (.open repo (.getObjectId (TreeWalk/forPath repo "b/test.txt" tree) 0)))

在Clojure中(遵循与前面部分相同的设置),这确实起作用。


非常好!我直接使用以下代码:FileOutputStream fileOS = new FileOutputStream(path); if (treewalk != null){repo.open(treewalk.getObjectId(0)).copyTo(fileOS);} 然后 fileOS.close; - Mark Mikofski
应该用Clojure回答更多的Java问题。 - user12341234

4

我自己找到了答案。这个API属于比较底层的,但是也不太难:

File repoDir = new File("test-git/.git");
// open the repository
Repository repo = new Repository(repoDir);
// find the HEAD
Commit head = repo.mapCommit(Constants.HEAD);
// retrieve the tree in HEAD
Tree tree = head.getTree();
// find a file (as a TreeEntry, which contains the blob object id)
TreeEntry entry = tree.findBlobMember("b/test.txt");
// use the blob id to read the file's data
byte[] data = repo.openBlob(entry.getId()).getBytes();

4
这个例子看起来与当前的 JGit 发布版本不符,API 已经有了一些变化,请注意。 - Jonathan Dumaine
1
@Jonathan Dumaine:如果需要更新帖子(而且你知道如何操作),请进行更新。 - Thilo

4
我已经开始编写一个名为 gitective 的库,其中包含许多使用 JGit 处理 blob、commit 和 tree 的帮助程序,并且该库是 MIT 许可证的,在 GitHub 上可以找到。
获取 HEAD commit 中文件的内容
Repository repo = new FileRepository("/repos/project/.git");
String content = BlobUtils.getHeadContent(repo, "src/Buffer.java");

获取分支上文件的内容

Repository repo = new FileRepository("/repos/project/.git");
String content = BlobUtils.getContent(repo, "master", "src/Buffer.java");

比较两个文件的差异

Repository repo = new FileRepository("/repos/project/.git");
ObjectId current = BlobUtils.getId(repo, "master", "Main.java");
ObjectId previous = BlobUtils.getId(repo, "master~1", "Main.java");
Collection<Edit> edit = BlobUtils.diff(repo, previous, current);

更多提供的实用工具示例详见README

1
看起来不错。如果我还没有写过我的jGit项目,我肯定会使用它。 - Jason Wheeler

3
您可以按以下方式读取给定文件路径的内容。请注意,如果在给定的树中找不到路径,则TreeWalk可能为null。因此,需要进行一些特定的处理。
public String readFile(RevCommit commit, String filepath) throws IOException {
    try (TreeWalk walk = TreeWalk.forPath(repo, filepath, commit.getTree())) {
        if (walk != null) {
            byte[] bytes = repo.open(walk.getObjectId(0)).getBytes();
            return new String(bytes, StandardCharsets.UTF_8);
        } else {
            throw new IllegalArgumentException("No path found.");
        }
    }
}

例如:

ObjectId head = repo.resolve(Constants.HEAD);
RevCommit last = repo.parseCommit(head);
readFile(last, "docs/README.md")

这个答案是使用JGit 4.8.0编写的。

3

JGit教程中有一些信息(但这也并不是真正有用或完整的,而且可能已过时,因为他们转向了eclipse,目前还没有文档可用)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接