14

Using JGit, I want to get a list of changes in files of a commits as is possible with git log --full-history -p -1 <hash-id>.

Is this possible? If so, how do you do it?

I know how to get each commit, and look at the commit message:

//Load repo
FileRepositoryBuilder builder = new FileRepositoryBuilder();
Repository repo = builder.setGitDir(new File("/path/to/repo/.git")).setMustExist(true).build();
Git git = new Git(repo);
//get commits
Iterable<RevCommit> log = git.log().call();
for (RevCommit commit : log) {
    //Shows the hashid
    System.out.println("LogCommit: " + commit);
    //Shows only commit message
    String logMessage = commit.getFullMessage();
    System.out.println("LogMessage: " + logMessage);
}
git.close();

What allows me to get the changes in the files?

For example I write:

git log --full-history -p -1 8309c1262e1b7ffce8fc86efc1ae5777a4a96777

The response is

commit 8309c1262e1b7ffce8fc86efc1ae5777a4a96777
Author: <redacted>
Date:   Thu Aug 4 12:15:23 2016 -0400

    Fixed typo in commit

diff --git a/Product/Production/Common/CONNECTCoreLib/src/main/java/gov/hhs/fha/nhinc/messaging/server/BaseService.java b/Product/Production/Common/CONNECTCoreLib/src/main/java/gov/hhs/fha/nhinc/messaging/server/BaseService.java
index fa55e7e..4f3c155 100644
--- a/Product/Production/Common/CONNECTCoreLib/src/main/java/gov/hhs/fha/nhinc/messaging/server/BaseService.java
+++ b/Product/Production/Common/CONNECTCoreLib/src/main/java/gov/hhs/fha/nhinc/messaging/server/BaseService.java
@@ -56,6 +57,7 @@ public abstract class BaseService {

     protected AssertionType getAssertion(WebServiceContext context, AssertionType assertionIn) {
         AssertionType assertion;
-        WSAHeaderHelper wsaHlpr = new WSAHeaderHelper();
+        WSAHeaderHelper wsaHelper = new WSAHeaderHelper();
         if (assertionIn == null) {
             assertion = SAML2AssertionExtractor.getInstance().extractSamlAssertion(context);

I want to have something like the following. Change is a made up class:

//Load repo
FileRepositoryBuilder builder = new FileRepositoryBuilder();
Repository repo = builder.setGitDir(new File("/path/to/repo/.git")).setMustExist(true).build();
Git git = new Git(repo);
//get commits
Iterable<RevCommit> log = git.log().call();
for (RevCommit commit : log) {
    //Shows the hashid
    System.out.println("LogCommit: " + commit);
    //Shows only commit message
    String logMessage = commit.getFullMessage();
    System.out.println("LogMessage: " + logMessage);
    List<Change> changes = commit.getChanges();
    for(Change change: changes):
        System.out.println("File: " + change.getFile());
        System.out.println("Change: " + change.getChange());
        System.out.println("ChangeType: " + change.getChangeType());
}
git.close();

The output would look something like:

LogCommit: 8309c1262e1b7ffce8fc86efc1ae5777a4a96777
LogMessage: Fixed typo in commit
File: Product/Production/Common/CONNECTCoreLib/src/main/java/gov/hhs/fha/nhinc/messaging/server/BaseService.java
Change: WSAHeaderHelper wsaHlpr = new WSAHeaderHelper();
ChangeType: D
File: Product/Production/Common/CONNECTCoreLib/src/main/java/gov/hhs/fha/nhinc/messaging/server/BaseService.java
Change: WSAHeaderHelper wsaHelper = new WSAHeaderHelper();
ChangeType: A
Whitecat
  • 3,882
  • 7
  • 48
  • 78

2 Answers2

11

JGit has a very simple diff command that writes a textual diff of the changes between two commits to an output stream.

For example:

OutputStream outputStream = ...
List<DiffEntry> diffEntries = git.diff().setOutputStream(outputStream).call();

Probably more interesting is the list of DiffEntry returned after calling the command. Each DiffEntry represents a changed file and tells its path name, whether it was added, changed, or deleted, pointers (blob-ID's) to the old and new content and more.

And from each DiffEntry, you can obtain an EditList which holds information about which lines were changed.

For Example:

try (DiffFormatter diffFormatter = new DiffFormatter(DisabledOutputStream.INSTANCE)) {
  diffFormatter.setRepository(git.getRepository());
  List<DiffEntry> diffEntries = diffFormatter.scan(oldTreeIterator, newTreeIterator);
  FileHeader fileHeader = diffFormatter.toFileHeader(diffEntries.get(0));
  return fileHeader.toEditList();
}

This code also shows how to obtain diff entries with more detailed control without using the DiffCommand.

Just recently I wrote an entire blog post about JGit's diff APIs. For more details please see here: http://www.codeaffine.com/2016/06/16/jgit-diff/

Rüdiger Herrmann
  • 20,512
  • 11
  • 62
  • 79
  • Where would the print line be to get the string of the filename and changes in each file? – Whitecat Oct 12 '16 at 18:26
  • The blog post does not show how to actually see the changes, i.e. "the text `WSAHeaderHelper wsaHelper = new WSAHeaderHelper();`. It just says where changes occur I believe by using methods such as getBeginA(), which indicate line number of changes. – Whitecat Oct 12 '16 at 18:59
  • The blog post shows a snippet that "uses format() to write a patch to the output". Isn't that what you were looking for? Further down, the blog post explains how to obtain change information from an EditList, allowing you to assemble your own description format. If that's not what you are looking for, please clarify your question. – Rüdiger Herrmann Oct 13 '16 at 08:35
9

Thanks to Rüdiger Herrmann for the feedback and part of the code found on his gist.

I created a method diffCommit(String hashID), with 3 helper functions that will work exactly like git log --full-history -p -1 <hash-id>.

private Git git;
private Repository repo;

private void diffCommit(String hashID) throws IOException {
    //Initialize repositories.
    FileRepositoryBuilder builder = new FileRepositoryBuilder();
    repo = builder.setGitDir(new File("/path/to/repo" + "/.git")).setMustExist(true)
            .build();
    git = new Git(repo);

    //Get the commit you are looking for.
    RevCommit newCommit;
    try (RevWalk walk = new RevWalk(repo)) {
        newCommit = walk.parseCommit(repo.resolve(hashID));
    }

    System.out.println("LogCommit: " + newCommit);
    String logMessage = newCommit.getFullMessage();
    System.out.println("LogMessage: " + logMessage);
    //Print diff of the commit with the previous one.
    System.out.println(getDiffOfCommit(newCommit));

}
//Helper gets the diff as a string.
private String getDiffOfCommit(RevCommit newCommit) throws IOException {

    //Get commit that is previous to the current one.
    RevCommit oldCommit = getPrevHash(newCommit);
    if(oldCommit == null){
        return "Start of repo";
    }
    //Use treeIterator to diff.
    AbstractTreeIterator oldTreeIterator = getCanonicalTreeParser(oldCommit);
    AbstractTreeIterator newTreeIterator = getCanonicalTreeParser(newCommit);
    OutputStream outputStream = new ByteArrayOutputStream();
    try (DiffFormatter formatter = new DiffFormatter(outputStream)) {
        formatter.setRepository(git.getRepository());
        formatter.format(oldTreeIterator, newTreeIterator);
    }
    String diff = outputStream.toString();
    return diff;
}
//Helper function to get the previous commit.
public RevCommit getPrevHash(RevCommit commit)  throws  IOException {

    try (RevWalk walk = new RevWalk(repo)) {
        // Starting point
        walk.markStart(commit);
        int count = 0;
        for (RevCommit rev : walk) {
            // got the previous commit.
            if (count == 1) {
                return rev;
            }
            count++;
        }
        walk.dispose();
    }
    //Reached end and no previous commits.
    return null;
}
//Helper function to get the tree of the changes in a commit. Written by Rüdiger Herrmann
private AbstractTreeIterator getCanonicalTreeParser(ObjectId commitId) throws IOException {
    try (RevWalk walk = new RevWalk(git.getRepository())) {
        RevCommit commit = walk.parseCommit(commitId);
        ObjectId treeId = commit.getTree().getId();
        try (ObjectReader reader = git.getRepository().newObjectReader()) {
            return new CanonicalTreeParser(null, reader, treeId);
        }
    }
}

Here is additional code that will produce output similiar to git log --full-history

public void commit_logs() throws IOException, NoHeadException, GitAPIException {
    List<String> logMessages = new ArrayList<String>();
    FileRepositoryBuilder builder = new FileRepositoryBuilder();
    Repository repo = builder.setGitDir(new File("/path/to/repo" + "/.git"))
            .setMustExist(true).build();
    git = new Git(repo);
    Iterable<RevCommit> log = git.log().call();
    RevCommit previousCommit = null;
    for (RevCommit commit : log) {
        if (previousCommit != null) {
            AbstractTreeIterator oldTreeIterator = getCanonicalTreeParser( previousCommit );
            AbstractTreeIterator newTreeIterator = getCanonicalTreeParser( commit );
            OutputStream outputStream = new ByteArrayOutputStream();
            try( DiffFormatter formatter = new DiffFormatter( outputStream ) ) {
              formatter.setRepository( git.getRepository() );
              formatter.format( oldTreeIterator, newTreeIterator );
            }
            String diff = outputStream.toString();
            System.out.println(diff);
        }
        System.out.println("LogCommit: " + commit);
        String logMessage = commit.getFullMessage();
        System.out.println("LogMessage: " + logMessage);
        logMessages.add(logMessage.trim());
        previousCommit = commit;
    }
    git.close();
}


  private AbstractTreeIterator getCanonicalTreeParser( ObjectId commitId ) throws IOException {
    try( RevWalk walk = new RevWalk( git.getRepository() ) ) {
      RevCommit commit = walk.parseCommit( commitId );
      ObjectId treeId = commit.getTree().getId();
      try( ObjectReader reader = git.getRepository().newObjectReader() ) {
        return new CanonicalTreeParser( null, reader, treeId );
      }
    }
  }
Community
  • 1
  • 1
Whitecat
  • 3,882
  • 7
  • 48
  • 78