How do I have to extend the following logCommand, to get the --follow
option of the git log
command working?
Git git = new Git(myRe
During some midnight work I got the following:
The last commit of a LogCommand will get checked for renames against all older commits until a rename operation is found. This cycle will continue until no rename was found.
However, that search can take some time, especially if it iterates over all commits until the end and doesn't find any rename operation anymore. So, I am open for any improvement. I guess git normally uses indexes to perform the follow option in shorter time.
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;
}
}
I recall trying OneWorld's solution on a previous occasion, and while it worked, it was very slow. I thought I'd google around to see if there were any other possibilities out there.
Yes, in this Eclipse thread, there was a suggestion of using org.eclipse.jgit.revwalk.FollowFilter and to look for a use-example in RevWalkFollowFilterTest.java.
So thought I'd give that a try, resulting in code like that looks like this:
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(...
The results were, erm, ok I guess... The RevWalk did manage to walk through a simple rename of a file in the git-repo's history (performed by a "git mv {filename}" action).
However, it was unable to handle messier situations, such as when a colleague performed this set of actions in the repo's history:
In this scenario, JGit's follow capabilities will only get me the from the head to that 2nd commit, and stop there.
The real "git log --follow" command, however, seems to have enough smarts to figure out that:
So JGit's follow capabilities seem a little weaker compared to real Git. Ah well.
But anyway, I can confirm that using JGit's FollowFilter technique did work a lot faster than the previously suggested technique.