我该如何扩展以下logCommand,以使git log
命令的--follow
选项正常工作?
Git git = new Git(myRepository);
Iterable<RevCommit> log = git.log().addPath("com/mycompany/myclass.java").call();
这个选项在jGit中已经实现,但我不知道怎么使用它。logCommand的方法似乎并没有什么用处。谢谢!
我该如何扩展以下logCommand,以使git log
命令的--follow
选项正常工作?
Git git = new Git(myRepository);
Iterable<RevCommit> log = git.log().addPath("com/mycompany/myclass.java").call();
这个选项在jGit中已经实现,但我不知道怎么使用它。logCommand的方法似乎并没有什么用处。谢谢!
在一些半夜的工作中,我得到了以下信息:
LogCommand 的最后一个提交将被检查,以查找所有旧提交中的重命名操作。这个循环会持续进行,直到找不到任何重命名操作为止。
然而,那个搜索可能需要一些时间,尤其是如果它遍历所有提交直到结束,并且再也找不到任何重命名操作的时候。所以,我希望能有任何改进。我猜 git 通常使用索引来执行更短时间内的 follow 选项。
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;
}
}
我记得之前尝试过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}”操作)。
然而,它无法处理更混乱的情况,比如当同事在 repo 历史中执行以下一系列操作时:
在这种情况下,JGit 的跟踪能力只能让我到达第二次提交,并在那里停止。
然而,真正的“git log --follow”命令似乎足够聪明,可以找出以下内容:
所以,与真正的Git相比,JGit的跟踪功能似乎有点弱。嗯,无论如何。
但无论如何,我可以确认使用JGit的FollowFilter技术比先前建议的技术要快得多。
看起来很少有人遇到这个问题。我看过各种解决方案,但都不是理想的。
最后,我使用了Java子进程来调用dos命令并分析结果以获取特定的提交,然后分析该提交。这种方法避免了搜索重命名文件,并完全依赖于git本身的能力。我希望它能帮助有这种需求的人!
String command ="cmd /c cd path/to/dir && git log --follow file_you_want";
Process p = Runtime.getRuntime().exec(command);
BufferedReader input = new BufferedReader(new
InputStreamReader(p.getInputStream()));
String line;
String text = command +"";
System.out.println(text);
while ((line = input.readLine()) != null) {
text += line;
System.out.println("Line:" + line);
}