org.openlca.git.repo.Diffs Maven / Gradle / Ivy
The newest version!
package org.openlca.git.repo;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.EmptyTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.openlca.git.iterator.DatabaseIterator;
import org.openlca.git.model.Commit;
import org.openlca.git.model.Diff;
import org.openlca.git.model.DiffType;
import org.openlca.git.model.Reference;
import org.openlca.git.util.GitUtil;
import org.openlca.git.util.ModelRefMap;
import org.openlca.util.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Diffs {
private static Logger log = LoggerFactory.getLogger(Diffs.class);
private final OlcaRepository repo;
private Diffs(OlcaRepository repo) {
this.repo = repo;
}
static Diffs of(OlcaRepository repo) {
return new Diffs(repo);
}
public Find find() {
return new Find();
}
public class Find {
private Commit commit;
private RevCommit leftCommit;
private RevCommit rightCommit;
private String path;
private boolean onlyCategories;
private boolean excludeCategories;
private boolean unsorted;
public Find commit(Commit commit) {
this.commit = commit;
return this;
}
public Find filter(String path) {
this.path = path;
return this;
}
public Find excludeCategories() {
this.excludeCategories = true;
return this;
}
public Find onlyCategories() {
this.onlyCategories = true;
return this;
}
public Find unsorted() {
this.unsorted = true;
return this;
}
public List withDatabase() {
if (!(repo instanceof ClientRepository))
throw new UnsupportedOperationException("Can only execute diff with database on ClientRepository");
this.leftCommit = getRevCommit(commit, true);
return sort(diffOfDatabase(path));
}
private List diffOfDatabase(String prefix) {
try (var walk = new TreeWalk(repo)) {
var repoIterator = createIterator(leftCommit, prefix);
var dbIterator = createDatabaseIterator(prefix);
walk.addTree(repoIterator);
walk.addTree(dbIterator);
walk.setFilter(KnownFilesFilter.createForPath(prefix));
walk.setRecursive(false);
return scan(walk, prefix, path -> diffOfDatabase(path));
} catch (IOException e) {
log.error("Error getting diffs for path " + prefix, e);
return new ArrayList<>();
}
}
public List withPreviousCommit() {
var leftCommit = repo.commits.find().before(commit.id).latest();
this.leftCommit = getRevCommit(leftCommit, false);
this.rightCommit = getRevCommit(commit, false);
return sort(diffOfCommits(path));
}
public List with(Commit other) {
this.leftCommit = getRevCommit(commit, false);
this.rightCommit = getRevCommit(other, false);
return sort(diffOfCommits(path));
}
private List diffOfCommits(String prefix) {
try (var walk = new TreeWalk(repo)) {
walk.addTree(createIterator(leftCommit, prefix));
walk.addTree(createIterator(rightCommit, prefix));
walk.setFilter(KnownFilesFilter.createForPath(prefix));
walk.setRecursive(false);
return scan(walk, prefix, path -> diffOfCommits(path));
} catch (IOException e) {
log.error("Error adding tree", e);
return new ArrayList<>();
}
}
private AbstractTreeIterator createIterator(RevCommit commit, String path)
throws IOException {
if (commit == null)
return new EmptyTreeIterator();
var treeId = Strings.nullOrEmpty(path)
? commit.getTree().getId()
: repo.getSubTreeId(commit.getTree().getId(), path);
if (ObjectId.zeroId().equals(treeId))
return new EmptyTreeIterator();
var it = new CanonicalTreeParser();
it.reset(repo.newObjectReader(), treeId);
return it;
}
private AbstractTreeIterator createDatabaseIterator(String path) {
if (repo instanceof ClientRepository r && r.database != null)
return new DatabaseIterator(r, path);
return new EmptyTreeIterator();
}
private RevCommit getRevCommit(Commit commit, boolean useHeadAsDefault) {
try {
var commitId = commit != null
? ObjectId.fromString(commit.id)
: null;
if (commitId != null)
return repo.parseCommit(commitId);
if (useHeadAsDefault)
return repo.getHeadCommit();
return null;
} catch (IOException e) {
log.error("Error loading commit", e);
return null;
}
}
private List scan(TreeWalk walk, String prefix, Function> scan) throws IOException {
var diffs = new ModelRefMap();
while (walk.next()) {
var path = !Strings.nullOrEmpty(prefix)
? prefix + "/" + walk.getPathString()
: walk.getPathString();
var oldMode = walk.getFileMode(0);
var newMode = walk.getFileMode(1);
var oldId = walk.getObjectId(0);
var newId = walk.getObjectId(1);
if (oldMode == FileMode.MISSING) {
addDiff(diffs, DiffType.ADDED, path, null, newId);
} else if (newMode == FileMode.MISSING) {
addDiff(diffs, DiffType.DELETED, path, oldId, null);
} else if (oldMode == FileMode.REGULAR_FILE && newMode == FileMode.REGULAR_FILE
&& !oldId.equals(newId)) {
addDiff(diffs, DiffType.MODIFIED, path, oldId, newId);
}
if (oldMode == FileMode.TREE || newMode == FileMode.TREE) {
scan.apply(path).forEach(diff -> merge(diffs, diff));
}
}
return diffs.values();
}
private void addDiff(ModelRefMap diffs, DiffType type, String path, ObjectId oldId, ObjectId newId)
throws IOException {
path = GitUtil.decode(path);
if (!path.contains("/"))
return;
var oldPath = isEmptyCategory(leftCommit, path)
? GitUtil.toEmptyCategoryPath(path)
: path;
var oldCommitId = leftCommit != null
? leftCommit.getId().getName()
: null;
var oldRef = oldId != null
? new Reference(oldPath, oldCommitId, oldId)
: null;
var isEmptyCategory = isEmptyCategory(rightCommit, path);
var newPath = isEmptyCategory
? GitUtil.toEmptyCategoryPath(path)
: path;
var newCommitId = rightCommit != null
? rightCommit.getName()
: null;
var newRef = new Reference(newPath, newCommitId, newId);
var diff = new Diff(type, oldRef, newRef);
if (diff.isCategory && (type == DiffType.MODIFIED || excludeCategories))
return;
if (!diff.isCategory && onlyCategories)
return;
merge(diffs, diff);
}
private void merge(ModelRefMap diffs, Diff diff) {
var other = diffs.get(diff);
if (other == null) {
diffs.put(diff, diff);
return;
}
if (diff.diffType == DiffType.DELETED && other.diffType == DiffType.ADDED) {
if (diff.path.equals(other.path)) {
diffs.remove(diff);
} else {
diffs.put(diff, new Diff(DiffType.MOVED, diff.oldRef, other.newRef));
}
} else if (diff.diffType == DiffType.ADDED && other.diffType == DiffType.DELETED) {
if (diff.path.equals(other.path)) {
diffs.remove(diff);
} else {
diffs.put(diff, new Diff(DiffType.MOVED, other.oldRef, diff.newRef));
}
}
}
private boolean isEmptyCategory(RevCommit commit, String path) throws IOException {
if (GitUtil.isDatasetPath(path))
return false;
var iterator = commit != null
? createIterator(commit, path)
: createDatabaseIterator(path);
if (iterator.eof())
return false;
var subPath = GitUtil.decode(iterator.getEntryPathString());
return GitUtil.isEmptyCategoryFile(subPath);
}
private List sort(List diffs) {
if (unsorted)
return diffs;
return diffs.stream()
.sorted()
.collect(Collectors.toList());
}
}
}