Looping over commits for a file with jGit

Andy Jarrett picture Andy Jarrett · Jan 24, 2011 · Viewed 12.9k times · Source

I've managed to get to grips with the basics of jGit file in terms of connecting to a repos and adding, commiting, and even looping of the commit messages for the files.

File gitDir = new File("/Users/myname/Sites/helloworld/.git");

RepositoryBuilder builder = new RepositoryBuilder();
Repository repository;
repository = builder.setGitDir(gitDir).readEnvironment()
        .findGitDir().build();

Git git = new Git(repository);
RevWalk walk = new RevWalk(repository);
RevCommit commit = null;

// Add all files
// AddCommand add = git.add();
// add.addFilepattern(".").call();

// Commit them
// CommitCommand commit = git.commit();
// commit.setMessage("Commiting from java").call();

Iterable<RevCommit> logs = git.log().call();
Iterator<RevCommit> i = logs.iterator();

while (i.hasNext()) {
    commit = walk.parseCommit( i.next() );
    System.out.println( commit.getFullMessage() );

}

What I want to do next is be able to get all the commit message for a single file and then be able revert the single file back to a specific reference/point in time.

Answer

treffer picture treffer · May 2, 2011

Here is how to find the changes of a commit based on all parent commits

        var tree = new TreeWalk(repository)
        tree.addTree(commit.getTree)
        commit.getParents foreach {
            parent => tree.addTree(parent.getTree)
        }
        tree.setFilter(TreeFilter.ANY_DIFF)

(scala code)

Note that TreeFilter.ANY_DIFF works for a single tree walker and will return all elements available in a root commit.

You would then have to iterate over the tree to see if your file is in the given delta (this is quite easy).

    while (tree.next)
            if (tree.getDepth == cleanPath.size) {
                // we are at the right level, do what you want
            } else {
                if (tree.isSubtree &&
                    name == cleanPath(tree.getDepth)) {
                    tree.enterSubtree
                }
            }
    }

(cleanPath is the pure in repo path, split by '/')

Now wrap that code into a RevWalk.next loop and you'll get the commits and files altered by the commit.

You may want to use a different filter than ANY_DIFF, because ANY_DIFF is true if one tree differs. This is a bit counter-intuitive in case of a merge where the blob didn't change compared to all parent trees. So here is the core of an ALL_DIFF, that will only show elements that differ from all parent trees:

override def include(walker: TreeWalk): Boolean = {
    val n = walker.getTreeCount();
    if (n == 1) {
        return true;
    }
    val m = walker.getRawMode(0)
    var i = 1
    while (i < n) {
        if (walker.getRawMode(i) == m && walker.idEqual(i, 0)) {
            return false
        }
        i += 1
    }
    true
}

(scala code, derived from AnyDiffFilter)