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 DiffEntry
s. Each entry has a changeType that specifies whether a file was added, removed or changed. An Entry
s' 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 ObjectId
s. 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
In Java Lambda's Why Is Getclass() Called on a Captured Variable
How to Clone a Generic List in Java
Java: How to Use Urlconnection to Post Request with Authorization
Are Thread.Sleep(0) and Thread.Yield() Statements Equivalent
Why Should I Override Hashcode() When I Override Equals() Method
@Valid Annotation Is Not Validating the List of Child Objects
Java How Expensive Is a Method Call
How to Fix Invalid Byte 1 of 1-Byte Utf-8 Sequence
Closing a Joptionpane Programmatically
Auto Resizing the Jtable Column Widths
What Does Maven Update Project Do in Eclipse
What Do Curly Braces in Java Mean by Themselves
Why Catch Exceptions in Java, When You Can Catch Throwables
How to Exit a While Loop in Java