如何在JGit中“ git log --follow ”? (要检索包括重命名的完整历史记录)

问题描述 投票:2回答:2

为了使--follow命令的git log选项正常工作,我如何扩展以下logCommand?

Git git = new Git(myRepository);
Iterable<RevCommit> log = git.log().addPath("com/mycompany/myclass.java").call();

此选项在jGit中实现,但我不知道如何使用它。 logCommand的方法似乎没有用。谢谢!

git git-log jgit
2个回答
12
投票

在一些午夜工作中,我得到了以下内容:

LogCommand的最后一次提交将针对所有较早的提交进行重命名检查,直到找到重命名操作为止。此循环将一直持续到找不到重命名为止。

但是,该搜索可能需要一些时间,尤其是如果它遍历所有提交直到结束并且不再找到任何重命名操作时,尤其如此。因此,我愿意进行任何改进。我猜git通常使用索引在较短的时间内执行跟随选项。

import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.RenameDetector;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.treewalk.TreeWalk;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * Create a Log command that enables the follow option: git log --follow -- < path >
 * User: OneWorld
 * Example for usage: ArrayList<RevCommit> commits =  new  LogFollowCommand(repo,"src/com/mycompany/myfile.java").call();
 */
public class LogFollowCommand {

    private final Repository repository;
    private String path;
    private Git git;

    /**
     * Create a Log command that enables the follow option: git log --follow -- < path >
     * @param repository
     * @param path
     */
    public LogFollowCommand(Repository repository, String path){
        this.repository = repository;
        this.path = path;
    }

    /**
     * Returns the result of a git log --follow -- < path >
     * @return
     * @throws IOException
     * @throws MissingObjectException
     * @throws GitAPIException
     */
    public ArrayList<RevCommit> call() throws IOException, MissingObjectException, GitAPIException {
        ArrayList<RevCommit> commits = new ArrayList<RevCommit>();
        git = new Git(repository);
        RevCommit start = null;
        do {
            Iterable<RevCommit> log = git.log().addPath(path).call();
            for (RevCommit commit : log) {
                if (commits.contains(commit)) {
                    start = null;
                } else {
                    start = commit;
                    commits.add(commit);
                }
            }
            if (start == null) return commits;
        }
        while ((path = getRenamedPath( start)) != null);

        return commits;
    }

    /**
     * Checks for renames in history of a certain file. Returns null, if no rename was found.
     * Can take some seconds, especially if nothing is found... Here might be some tweaking necessary or the LogFollowCommand must be run in a thread.
     * @param start
     * @return String or null
     * @throws IOException
     * @throws MissingObjectException
     * @throws GitAPIException
     */
    private String getRenamedPath( RevCommit start) throws IOException, MissingObjectException, GitAPIException {
        Iterable<RevCommit> allCommitsLater = git.log().add(start).call();
        for (RevCommit commit : allCommitsLater) {

            TreeWalk tw = new TreeWalk(repository);
            tw.addTree(commit.getTree());
            tw.addTree(start.getTree());
            tw.setRecursive(true);
            RenameDetector rd = new RenameDetector(repository);
            rd.addAll(DiffEntry.scan(tw));
            List<DiffEntry> files = rd.compute();
            for (DiffEntry diffEntry : files) {
                if ((diffEntry.getChangeType() == DiffEntry.ChangeType.RENAME || diffEntry.getChangeType() == DiffEntry.ChangeType.COPY) && diffEntry.getNewPath().contains(path)) {
                    System.out.println("Found: " + diffEntry.toString() + " return " + diffEntry.getOldPath());
                    return diffEntry.getOldPath();
                }
            }
        }
        return null;
    }
}

0
投票

我记得以前曾尝试过OneWorld的解决方案,尽管它起作用了,但是非常慢。我以为我会四处搜寻,看看是否还有其他可能性。

是,在此Eclipse thread中,建议使用org.eclipse.jgit.revwalk.FollowFilter并在RevWalkFollowFilterTest.java中查找用例。

所以我想尝试一下,结果像这样的代码看起来像这样:

private static class DiffCollector extends RenameCallback {
    List<DiffEntry> diffs = new ArrayList<DiffEntry>();

    @Override
    public void renamed(DiffEntry diff) {
        diffs.add(diff);
    }
}

private DiffCollector diffCollector;

private void showFileHistory(String filepath)
{
    try
    {
        Config config = repo.getConfig();
        config.setBoolean("diff", null, "renames", true);

        RevWalk rw = new RevWalk(repo);
        diffCollector = new DiffCollector();

        org.eclipse.jgit.diff.DiffConfig dc = config.get(org.eclipse.jgit.diff.DiffConfig.KEY);
        FollowFilter followFilter =
                 FollowFilter.create(filepath, dc);
        followFilter.setRenameCallback(diffCollector);
        rw.setTreeFilter(followFilter);
        rw.markStart(rw.parseCommit(repo.resolve(Constants.HEAD)));

        for (RevCommit c : rw)
        {
            System.out.println(c.toString());
        }
    }
    catch(...

结果是,嗯,好吧,我想... RevWalk确实设法完成了git-repo历史上文件的简单重命名(由“ git mv {filename}”操作执行)。

但是,它无法处理更混乱的情况,例如,当某位同事在回购协议的历史记录中执行了这一系列操作时:

  • 第一次提交:使用“ git mv”重命名了文件
  • 第二次提交:在新的子文件夹位置添加了该文件的副本
  • 第三次提交:删除了旧位置的副本

在这种情况下,JGit的跟踪功能只会使我从头到第二次提交,然后就此停止。

然而,真正的“ git log --follow”命令似乎有足够的技巧来找出:

  • 第二次提交中添加的文件与第一次提交中的文件相同(即使它们位于不同的位置)
  • 它将为您提供整个历史记录:
    • 从HEAD至第二次提交(在新位置添加了新命名文件的副本)
    • 跳过对第三次提交的任何提及(在旧路径中删除旧文件)
    • 后跟第一次提交及其历史记录(旧位置和文件名)

因此,与真正的Git相比,JGit的跟踪功能似乎有些弱。嗯。

但是无论如何,我可以确认使用JGit的FollowFilter技术确实比以前建议的技术快很多。

© www.soinside.com 2019 - 2024. All rights reserved.