Skip to content

Instantly share code, notes, and snippets.

@robisonsantos
Created May 18, 2018 20:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robisonsantos/7a35b79c86eedb58a31402d3ffca63f6 to your computer and use it in GitHub Desktop.
Save robisonsantos/7a35b79c86eedb58a31402d3ffca63f6 to your computer and use it in GitHub Desktop.
Simple jGit git diff --raw implementation
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import static java.util.stream.Collectors.joining;
public class RawDiff {
public static void main(String[] args) throws IOException, GitAPIException {
Repository repo = createSampleRepo().orElseThrow(() -> new RuntimeException("Fail to create sample repo"));
System.out.println(String.format("Repository created at: %s", repo.getDirectory().getAbsolutePath()));
for (int i = 1; i < 6; i++) {
printRawDiff(repo, i);
}
FileUtils.deleteDirectory(repo.getDirectory().getParentFile());
}
private static void printRawDiff(Repository repo, int commitsFromHead) throws IOException, GitAPIException {
String commits = new String(new char[commitsFromHead]).replace("\0", "~");
List<DiffEntry> diffs = getDiff(repo, "HEAD", "HEAD" + commits);
String rawDiff = diffs.stream().map(diff -> {
List<String> entry = new ArrayList<>();
entry.add(StringUtils.leftPad(diff.getOldMode().toString(), 6, "0"));
entry.add(StringUtils.leftPad(diff.getNewMode().toString(), 6, "0"));
entry.add(diff.getOldId().name());
entry.add(diff.getNewId().name());
switch (diff.getChangeType()) {
case ADD:
case MODIFY:
case DELETE:
entry.add(diff.getChangeType().name().substring(0,1)); // A, M or D
break;
case COPY:
case RENAME:
entry.add(String.format("%s%d", diff.getChangeType().name().charAt(0), diff.getScore())); // C21, R43
}
if (!diff.getOldPath().equals("/dev/null")) {
entry.add(diff.getOldPath());
}
if (!diff.getNewPath().equals("/dev/null")) {
entry.add(diff.getNewPath());
}
return entry.stream().collect(joining(" "));
}).collect(joining("\n"));
System.out.println(String.format("---- git diff --raw HEAD%s HEAD ----", commits));
System.out.println(rawDiff);
System.out.println();
}
private static List<DiffEntry> getDiff(Repository repository, String after, String before) throws IOException, GitAPIException {
Git git = new Git(repository);
return git.diff().setOldTree(prepareTreeParser(repository, before))
.setNewTree(prepareTreeParser(repository, after))
.call();
}
private static AbstractTreeIterator prepareTreeParser(Repository repository, String objectId) throws IOException {
// from the commit we can build the tree which allows us to construct the TreeParser
//noinspection Duplicates
try (RevWalk walk = new RevWalk(repository)) {
RevCommit commit = walk.parseCommit(repository.resolve(objectId));
RevTree tree = walk.parseTree(commit.getTree().getId());
CanonicalTreeParser treeParser = new CanonicalTreeParser();
try (ObjectReader reader = repository.newObjectReader()) {
treeParser.reset(reader, tree.getId());
}
walk.dispose();
return treeParser;
}
}
private static Optional<Repository> createSampleRepo() {
Optional<Repository> repository = RepoUtils.createRepository();
return repository.map(repo -> {
File file = createFile(repo.getDirectory().getParent(), "testFile");
writeToFile(file, "This is a test");
addAndCommit(repo, "firstCommit");
writeToFile(file, "This is another test");
addAndCommit(repo, "secondCommit");
File file2 = createFile(repo.getDirectory().getParent(), "testFile2");
writeToFile(file2, "This is a test");
addAndCommit(repo, "thirdCommit");
File file3 = renameFile(file2, "newFile");
addAndCommit(repo, "Renamed file");
File file4 = copyTo("copiedFile", file3);
addAndCommit(repo,"Copied file");
deleteFile(file4);
addAndCommit(repo, "File deleted");
return repo;
});
}
private static void deleteFile(File file) {
try {
Files.deleteIfExists(file.toPath());
} catch (IOException e) {
throw new RuntimeException("Could not delete file", e);
}
}
private static File copyTo(String newName, File file) {
File newFile = new File(file.getParent(), newName);
try {
return Files.copy(file.toPath(), newFile.toPath(), StandardCopyOption.REPLACE_EXISTING).toFile();
} catch (IOException e) {
throw new RuntimeException("Could not copy file", e);
}
}
private static File renameFile(File file, String newName) {
File newFile = new File(file.getParent(), newName);
if (!file.renameTo(newFile)) {
throw new RuntimeException("Could not rename file");
}
return newFile;
}
private static void addAndCommit(Repository repo, String commitMessage) {
try (Git git = new Git(repo)) {
// Add everything
git.add().setUpdate(true).addFilepattern(".").call();
git.add().addFilepattern(".").call();
git.commit().setMessage(commitMessage).call();
} catch (GitAPIException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
private static void writeToFile(File file, String content) {
try (BufferedWriter writer = Files.newBufferedWriter(file.toPath(), StandardOpenOption.APPEND)) {
writer.write(content);
writer.newLine();
} catch (IOException ioe) {
throw new RuntimeException(ioe.getMessage(), ioe);
}
}
private static File createFile(String dirLocation, String fileName) {
File file = new File(dirLocation, fileName);
try {
if (!file.createNewFile()) {
throw new RuntimeException(String.format("Not able to create file %s", file));
}
} catch (IOException ioe) {
throw new RuntimeException(ioe.getMessage(), ioe);
}
return file;
}
}
// After running this:
// ---- git diff --raw HEAD~ HEAD ----
//100644 000000 0527e6bd2d76b45e2933183f1b506c7ac49f5872 0000000000000000000000000000000000000000 D copiedFile
//
//---- git diff --raw HEAD~~ HEAD ----
//
//
//---- git diff --raw HEAD~~~ HEAD ----
//000000 100644 0000000000000000000000000000000000000000 0527e6bd2d76b45e2933183f1b506c7ac49f5872 A newFile
//100644 000000 0527e6bd2d76b45e2933183f1b506c7ac49f5872 0000000000000000000000000000000000000000 D testFile2
//
//---- git diff --raw HEAD~~~~ HEAD ----
//000000 100644 0000000000000000000000000000000000000000 0527e6bd2d76b45e2933183f1b506c7ac49f5872 A newFile
//
//---- git diff --raw HEAD~~~~~ HEAD ----
//000000 100644 0000000000000000000000000000000000000000 0527e6bd2d76b45e2933183f1b506c7ac49f5872 A newFile
//100644 100644 0527e6bd2d76b45e2933183f1b506c7ac49f5872 9b95249b27451b62b60a19cd1badf0b79a493538 M testFile testFile
// Git execution:
//|master ⇒ git diff --raw HEAD~ HEAD
//:100644 000000 0527e6b... 0000000... D copiedFile
//
//|master ⇒ git diff --raw HEAD~~ HEAD
//
//|master ⇒ git diff --raw HEAD~~~ HEAD
//:000000 100644 0000000... 0527e6b... A newFile
//:100644 000000 0527e6b... 0000000... D testFile2
//
//|master ⇒ git diff --raw HEAD~~~~ HEAD
//:000000 100644 0000000... 0527e6b... A newFile
//
//|master ⇒ git diff --raw HEAD~~~~~ HEAD
//:000000 100644 0000000... 0527e6b... A newFile
//:100644 100644 0527e6b... 9b95249... M testFile
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment