package com.door43.translationstudio.core;
import com.door43.translationstudio.git.Repo;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.LogCommand;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.PathFilter;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
/**
* Represents the commit history of a single file within a git repository
*/
public class FileHistory {
private final Repo repo;
private final File file;
private final Git git;
private RevCommit[] history = new RevCommit[0];
private int index = 0;
/**
*
* @param gitRepo
* @param relativeFile relative path to file in repo
*/
public FileHistory(Repo gitRepo, File relativeFile) throws IOException, GitAPIException {
this.repo = gitRepo;
this.git = gitRepo.getGit();
// sanitize file to be relative to repo
if(relativeFile != null) {
int pos = relativeFile.toString().indexOf(gitRepo.getLocalPath());
if (pos >= 0) {
this.file = new File(relativeFile.toString().substring(pos + gitRepo.getLocalPath().length() + 1));
} else {
this.file = null;
}
} else {
this.file = null;
}
}
/**
* Reloads the commit history
*/
public void loadCommits() throws IOException, GitAPIException {
if(this.file != null) {
// preserve current position if not at HEAD
RevCommit currentCommit = null;
if(this.index > 0) {
currentCommit = current();
}
// load history
Repository repository = this.git.getRepository();
ObjectId head = repository.resolve("HEAD");
if(head != null) {
LogCommand log = this.git.log();
log.add(head);
log.addPath(this.file.toString());
Iterable<RevCommit> commits = log.call();
ArrayList<RevCommit> historyList = new ArrayList<>();
for (RevCommit commit : commits) {
historyList.add(commit);
// restore current position
if (currentCommit != null) {
String hash = commit.toString().split(" ")[1];
String currentHash = currentCommit.toString().split(" ")[1];
if (hash.equals(currentHash)) {
index = historyList.size() - 1;
currentCommit = null;
}
}
}
this.history = historyList.toArray(new RevCommit[historyList.size()]);
} else {
this.history = new RevCommit[0];
}
} else {
this.history = new RevCommit[0];
}
}
/**
* Checks if there is a previous commit
* @return
*/
public boolean hasPrevious() {
return this.index + 1 < this.history.length;
}
/**
* Returns the previous commit in the file history
* The position in the history will not be changed if the previous index would be out of bounds
* @return null if we have reached the bottom of the commit history
*/
public RevCommit previous() {
int prevIndex = this.index + 1;
RevCommit commit = getCommit(prevIndex);
if(commit != null) {
this.index = prevIndex;
}
return commit;
}
/**
* Returns the currently viewed commit of the file history
* @return null if the history was not loaded
*/
public RevCommit current() {
return getCommit(this.index);
}
/**
* Checks if there is a next commit
* @return
*/
public boolean hasNext() {
return this.index -1 >= 0 && this.history.length > 0;
}
/**
* Returns the next commit in the file history
* The position in the history will not be changed if the next index would be out of bounds
* @return null if we have reached the top of the commit history
*/
public RevCommit next() {
int nextIndex = this.index - 1;
RevCommit commit = getCommit(nextIndex);
if(commit != null) {
this.index = nextIndex;
}
return commit;
}
/**
* Returns the HEAD of the commit tree
* @return null if the history was not loaded
*/
public RevCommit head() {
return getCommit(0);
}
/**
* Resets the history back to HEAD
*/
public void reset() {
this.index = 0;
}
/**
* Returns the commit at the given position
* @param pos the position of the commit in history
* @return null if the position is out of bounds
*/
private RevCommit getCommit(int pos) {
if(pos >= 0 && pos < this.history.length) {
return this.history[pos];
} else {
return null;
}
}
/**
* Returns the file contents at the given commit
* @param commit
* @return
*/
public String read(RevCommit commit) throws IOException, IllegalStateException {
if(commit != null) {
TreeWalk walk = new TreeWalk(this.git.getRepository());
walk.addTree(commit.getTree());
walk.setRecursive(true);
walk.setFilter(PathFilter.create(this.file.toString()));
if (!walk.next()) {
throw new IllegalStateException("Did not find expected file '" + this.file.toString() + "'");
}
ObjectId objectId = walk.getObjectId(0);
ObjectLoader loader = this.git.getRepository().open(objectId);
return new String(loader.getBytes(), "UTF-8");
} else {
return null;
}
}
/**
* Checks if the history cursor is currently at the HEAD
* @return
*/
public boolean isAtHead() {
return this.index == 0;
}
}