Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use JGit to get list of changes in files?

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
like image 848
Whitecat Avatar asked Oct 08 '16 17:10

Whitecat


2 Answers

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/

like image 149
Rüdiger Herrmann Avatar answered Sep 28 '22 11:09

Rüdiger Herrmann


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 );
      }
    }
  }
like image 45
Whitecat Avatar answered Sep 28 '22 12:09

Whitecat