Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Looping over commits for a file with jGit

Tags:

java

jgit

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.

like image 831
Andy Jarrett Avatar asked Jan 24 '11 00:01

Andy Jarrett


2 Answers

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)

like image 79
treffer Avatar answered Oct 13 '22 01:10

treffer


So I tried to get charlieboy's solution to work, and it mostly did, but it failed for me in the following case (maybe something changed in jgit since that post?)

add fileA, commit as "commit 1" add fileB, commit as "commit 2"

getFileVersionDateList("fileA")

Both commit 1 and commit 2 were found, where I expected only commit 1.

My solution was as follows:

List<Commit> commits = new ArrayList<Commit>();

RevWalk revWalk = new RevWalk(repository);
revWalk.setTreeFilter(
        AndTreeFilter.create(
                PathFilterGroup.createFromStrings(<relative path in question>),
                TreeFilter.ANY_DIFF)
);

RevCommit rootCommit = revWalk.parseCommit(repository.resolve(Constants.HEAD));
revWalk.sort(RevSort.COMMIT_TIME_DESC);
revWalk.markStart(rootCommit);

for (RevCommit revCommit : revWalk) {
    commits.add(new GitCommit(getRepository(), revCommit));
}

Using the LogCommand is even simpler, and looks like this:

List<Commit> commitsList = new ArrayList<Commit>();

Git git = new Git(repository);
LogCommand logCommand = git.log()
        .add(git.getRepository().resolve(Constants.HEAD))
        .addPath(<relative path in question>);

for (RevCommit revCommit : logCommand.call()) {
    commitsList.add(new GitCommit(this, revCommit));
}

Obviously you'd also check the commit dates, etc, as needed.

like image 32
Skip Avatar answered Oct 12 '22 23:10

Skip