How to Show Changes Between Commits with Jgit

How to show changes between commits with JGit

To obtain the tree of the head commit, call

git.getRepository().resolve( "HEAD^{tree}" )

and to obtain the tree of the parent of the HEAD commit, call

git.getRepository().resolve( "HEAD~1^{tree}" )

Search for 'Git caret and tilde' if you are interested in more details.

To summarize, here goes a snippet that computes the diff of two commits:

File file = new File( git.getRepository().getWorkTree(), "file.txt" );
writeFile( file, "first version" );
RevCommit newCommit = commitChanges();
writeFile( file, "second version" );
RevCommit oldCommit = commitChanges();

ObjectReader reader = git.getRepository().newObjectReader();
CanonicalTreeParser oldTreeIter = new CanonicalTreeParser();
ObjectId oldTree = git.getRepository().resolve( "HEAD^{tree}" ); // equals newCommit.getTree()
oldTreeIter.reset( reader, oldTree );
CanonicalTreeParser newTreeIter = new CanonicalTreeParser();
ObjectId newTree = git.getRepository().resolve( "HEAD~1^{tree}" ); // equals oldCommit.getTree()
newTreeIter.reset( reader, newTree );

DiffFormatter df = new DiffFormatter( new ByteArrayOutputStream() ); // use NullOutputStream.INSTANCE if you don't need the diff output
df.setRepository( git.getRepository() );
List<DiffEntry> entries = df.scan( oldTreeIter, newTreeIter );

for( DiffEntry entry : entries ) {
System.out.println( entry );
}

private RevCommit commitChanges() throws GitAPIException {
git.add().addFilepattern( "." ).call();
return git.commit().setMessage( "commit message" ).call();
}

private static void writeFile( File file, String content ) throws IOException {
FileOutputStream outputStream = new FileOutputStream( file );
outputStream.write( content.getBytes( "UTF-8" ) );
outputStream.close();
}

For further considerations about showing changes between commits, you may want to read this in-depth discussion of JGit's diff APIs that can be found here: http://www.codeaffine.com/2016/06/16/jgit-diff/

List of files changed between commits with JGit

You can use the DiffFormatter to get a list of DiffEntrys. Each entry has a changeType that specifies whether a file was added, removed or changed. An Entrys' getOldPath() and getNewPath() methods return the path name. The JavaDoc lists what each method retuns for a given change type.

ObjectReader reader = git.getRepository().newObjectReader();
CanonicalTreeParser oldTreeIter = new CanonicalTreeParser();
ObjectId oldTree = git.getRepository().resolve( "HEAD~1^{tree}" );
oldTreeIter.reset( reader, oldTree );
CanonicalTreeParser newTreeIter = new CanonicalTreeParser();
ObjectId newTree = git.getRepository().resolve( "HEAD^{tree}" );
newTreeIter.reset( reader, newTree );

DiffFormatter diffFormatter = new DiffFormatter( DisabledOutputStream.INSTANCE );
diffFormatter.setRepository( git.getRepository() );
List<DiffEntry> entries = diffFormatter.scan( oldTreeIter, newTreeIter );

for( DiffEntry entry : entries ) {
System.out.println( entry.getChangeType() );
}

The above example lists the changed files between HEAD and its predecessor, but can be changed to compare arbitrary commits like abc^{tree}.

How to get the changed files list for a commit with JGit

With two refs pointing at the two commits it should suffice to do the following to iterate all changes between the commits:

        ObjectId oldHead = repository.resolve("HEAD^^^^{tree}");
ObjectId head = repository.resolve("HEAD^{tree}");

// prepare the two iterators to compute the diff between
try (ObjectReader reader = repository.newObjectReader()) {
CanonicalTreeParser oldTreeIter = new CanonicalTreeParser();
oldTreeIter.reset(reader, oldHead);
CanonicalTreeParser newTreeIter = new CanonicalTreeParser();
newTreeIter.reset(reader, head);

// finally get the list of changed files
try (Git git = new Git(repository)) {
List<DiffEntry> diffs= git.diff()
.setNewTree(newTreeIter)
.setOldTree(oldTreeIter)
.call();
for (DiffEntry entry : diffs) {
System.out.println("Entry: " + entry);
}
}
}
}

There is a ready-to-run example snippet contained in the jgit-cookbook

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

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 );
}
}
}

How to log the commits between two release tags with JGit

The LogCommand allows to specify the range of commits that it will include. The range need to be given as ObjectIds. And if tags mark the start and end points, the commit IDs that they reference need to be extracted first.

The snippet below illustrates the necessary steps:

ObjectId from = repo.resolve("refs/tags/start-tag");
ObjectId to = repo.resolve("refs/tags/end-tag");
git.log().addRange(from, to).call();

If annotated tags are used, they may have to be unpeeled first as described here: what is the difference between getPeeledObjectId() and getObjectId() of Ref Object?

Equivalent of git diff in JGit

The JGit diff code is located in DiffFormatter and its associated classes. If you take a closer look, you'll see that the code isn't meant to diff arbitrary byte streams. It is coupled to a an existing repository with commits, trees, etc.

If you don't mind the wrong file names, you can use this workaround:

1) create a temporary repository

2) create a commit with a single file (named ab.txt) that holds the contents of a.txt

3) create another commit with a single file - named identical as the above file - that holds the contents of b.txt

4) now you can use JGit to diff the two commits

Example code:

File file = new File( git.getRepository().getWorkTree(), "ab.txt" );
writeFile( file, "line1\n" );
RevCommit oldCommit = commitChanges();
writeFile( file, "line1\nline2\n" );
RevCommit newCommit = commitChanges();

ObjectReader reader = git.getRepository().newObjectReader();
CanonicalTreeParser oldTreeIter = new CanonicalTreeParser();
oldTreeIter.reset( reader, oldCommit.getTree() );
CanonicalTreeParser newTreeIter = new CanonicalTreeParser();
newTreeIter.reset( reader, newCommit.getTree() );

DiffFormatter diffFormatter = new DiffFormatter( System.out );
diffFormatter.setRepository( git.getRepository() );
List<DiffEntry> entries = diffFormatter.scan( newTreeIter, oldTreeIter );
diffFormatter.format( entries );
diffFormatter.close();

private RevCommit commitChanges() throws GitAPIException {
git.add().addFilepattern( "." ).call();
return git.commit().setMessage( "commit message" ).call();
}

private static void writeFile( File file, String content ) throws IOException {
FileOutputStream outputStream = new FileOutputStream( file );
outputStream.write( content.getBytes( "UTF-8" ) );
outputStream.close();
}

How to archive diff files between two commit with jgit?

You cannot archive diffs, neither with Git nor with JGit. An archive is always created from a tree.



Related Topics



Leave a reply



Submit