/**
* This code is free software; you can redistribute it and/or modify it under
* the terms of the new BSD License.
*
* Copyright (c) 2012-2013, Sebastian Staudt
*/
package com.github.koraktor.mavanagaiata.git.jgit;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import org.eclipse.jgit.errors.AmbiguousObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.IndexDiff;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevFlag;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevTag;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.treewalk.FileTreeIterator;
import com.github.koraktor.mavanagaiata.git.AbstractGitRepository;
import com.github.koraktor.mavanagaiata.git.CommitWalkAction;
import com.github.koraktor.mavanagaiata.git.GitCommit;
import com.github.koraktor.mavanagaiata.git.GitRepositoryException;
import com.github.koraktor.mavanagaiata.git.GitTag;
import com.github.koraktor.mavanagaiata.git.GitTagDescription;
/**
* Wrapper around JGit's {@link Repository} object to represent a Git
* repository
*
* @author Sebastian Staudt
*/
public class JGitRepository extends AbstractGitRepository {
protected Map<ObjectId, RevCommit> commitCache;
protected Repository repository;
protected ObjectId headObject;
protected RevWalk revWalk;
/**
* Creates a new instance from a JGit repository object
*
* @param workTree The worktree of the repository or {@code null}
* @param gitDir The GIT_DIR of the repository or {@code null}
*/
public JGitRepository(File workTree, File gitDir)
throws GitRepositoryException {
FileRepositoryBuilder repositoryBuilder = new FileRepositoryBuilder();
repositoryBuilder.readEnvironment();
if (gitDir == null && workTree == null) {
throw new GitRepositoryException("Neither worktree nor GIT_DIR is set.");
} else {
if (workTree != null && !workTree.exists()) {
throw new GitRepositoryException("The worktree " + workTree + " does not exist");
}
if (gitDir != null && !gitDir.exists()) {
throw new GitRepositoryException("The GIT_DIR " + gitDir + " does not exist");
}
}
repositoryBuilder.setWorkTree(workTree);
if (gitDir != null) {
repositoryBuilder.setGitDir(gitDir);
} else {
repositoryBuilder.findGitDir(workTree);
if (repositoryBuilder.getGitDir() == null) {
throw new GitRepositoryException(workTree + " is not inside a Git repository. Please specify the GIT_DIR separately.");
}
}
try {
this.repository = repositoryBuilder.build();
} catch (IOException e) {
throw new GitRepositoryException("Could not initialize repository", e);
}
this.commitCache = new HashMap<ObjectId, RevCommit>();
}
public void check() throws GitRepositoryException {
if (!this.repository.getObjectDatabase().exists()) {
File path = (this.repository.isBare()) ?
this.repository.getDirectory() : this.repository.getWorkTree();
throw new GitRepositoryException(path.getAbsolutePath() + " is not a Git repository.");
}
}
/**
* {@inheritDoc}
* <p>
* Closes JGit's repository instance.
*
* @see Repository#close
*/
public void close() {
if (this.repository != null) {
this.repository.close();
this.repository = null;
}
}
public GitTagDescription describe() throws GitRepositoryException {
HashMap<RevCommit, String> tagCommits = new HashMap<RevCommit, String>();
for (Map.Entry<String, RevTag> tag : this.getRawTags().entrySet()) {
tagCommits.put((RevCommit) tag.getValue().getObject(), tag.getValue().getName());
}
RevCommit start = this.getCommit(this.getHeadObject());
RevWalk revWalk = this.getRevWalk();
RevFlag seenFlag = revWalk.newFlag("SEEN");
int distance = -1;
GitTag nextTag = null;
HashSet<RevCommit> commits = new HashSet<RevCommit>();
commits.add(start);
while (!commits.isEmpty()) {
distance ++;
HashSet<RevCommit> nextCommits = new HashSet<RevCommit>();
for (RevCommit currentCommit : commits) {
try {
revWalk.parseHeaders(currentCommit);
} catch (IOException e) {
throw new GitRepositoryException("Unable to parse headers of commit " + currentCommit.getName(), e);
}
if (currentCommit.has(seenFlag)) {
continue;
}
currentCommit.add(seenFlag);
if (tagCommits.containsKey(currentCommit)) {
nextTag = this.getTags().get(currentCommit.getId().getName());
break;
}
if (currentCommit.getParents() != null) {
nextCommits.addAll(Arrays.asList(currentCommit.getParents()));
}
}
commits.clear();
commits.addAll(nextCommits);
}
return new GitTagDescription(this, this.getHeadCommit(), nextTag, distance);
}
public String getAbbreviatedCommitId(GitCommit commit) throws GitRepositoryException {
try {
RevCommit rawCommit = ((JGitCommit) commit).commit;
return this.repository.getObjectDatabase().newReader()
.abbreviate(rawCommit).name();
} catch (IOException e) {
throw new GitRepositoryException(
String.format("Commit \"%s\" could not be abbreviated.", this.getHeadObject().getName()),
e);
}
}
public String getBranch() throws GitRepositoryException {
try {
return this.repository.getBranch();
} catch (IOException e) {
throw new GitRepositoryException("Current branch could not be read.", e);
}
}
public JGitCommit getHeadCommit() throws GitRepositoryException {
return new JGitCommit(this.getCommit(this.getHeadObject()));
}
public Map<String, GitTag> getTags()
throws GitRepositoryException {
Map<String, GitTag> tags = new HashMap<String, GitTag>();
for (Map.Entry<String, RevTag> tag : this.getRawTags().entrySet()) {
tags.put(tag.getKey(), new JGitTag(tag.getValue()));
}
return tags;
}
public File getWorkTree() {
return this.repository.getWorkTree();
}
public boolean isDirty(boolean ignoreUntracked) throws GitRepositoryException {
try {
FileTreeIterator workTreeIterator = new FileTreeIterator(this.repository);
IndexDiff indexDiff = new IndexDiff(this.repository, this.getHeadObject(), workTreeIterator);
indexDiff.diff();
return !ignoreUntracked && !indexDiff.getUntracked().isEmpty() ||
!(indexDiff.getAdded().isEmpty() && indexDiff.getChanged().isEmpty() &&
indexDiff.getRemoved().isEmpty() &&
indexDiff.getMissing().isEmpty() &&
indexDiff.getModified().isEmpty() &&
indexDiff.getConflicting().isEmpty());
} catch (IOException e) {
throw new GitRepositoryException("Could not create repository diff.", e);
}
}
public void walkCommits(CommitWalkAction action)
throws GitRepositoryException {
try {
RevWalk revWalk = this.getRevWalk();
revWalk.markStart(this.getCommit(this.getHeadObject()));
RevCommit commit;
while((commit = revWalk.next()) != null) {
action.execute(new JGitCommit(commit));
}
} catch (IOException e) {
throw new GitRepositoryException("", e);
}
}
/**
* Returns a commit object for the given object ID
*
* @return The commit object for the given object ID
* @see RevCommit
* @throws GitRepositoryException if the commit object cannot be retrieved
*/
protected RevCommit getCommit(ObjectId id) throws GitRepositoryException {
if (this.commitCache.containsKey(id)) {
return this.commitCache.get(id);
}
try {
RevWalk revWalk = this.getRevWalk();
RevCommit commit = revWalk.parseCommit(id);
this.commitCache.put(id, commit);
return commit;
} catch (IncorrectObjectTypeException e) {
throw new GitRepositoryException(
String.format("Object \"%s\" is not a commit.", id.getName()),
e);
} catch (MissingObjectException e) {
throw new GitRepositoryException(
String.format("Commit \"%s\" is missing.", id.getName()),
e);
} catch (IOException e) {
throw new GitRepositoryException(
String.format("Commit \"%s\" could not be loaded.", id.getName()),
e);
}
}
/**
* Returns the object for the Git ref currently set as {@code HEAD}
*
* @return The currently selected {@code HEAD} object
* @throws GitRepositoryException if the ref cannot be resolved
*/
protected ObjectId getHeadObject() throws GitRepositoryException {
if (this.headObject == null) {
try {
this.headObject = this.repository.resolve(this.headRef);
} catch (AmbiguousObjectException e) {
throw new GitRepositoryException(
String.format("Ref \"%s\" is ambiguous.", this.headRef),
e);
} catch (IOException e) {
throw new GitRepositoryException(
String.format("Ref \"%s\" could not be resolved.", this.headRef),
e);
}
}
if (this.headObject == null) {
if (this.headRef.equals("HEAD")) {
throw new GitRepositoryException(
"HEAD could not be resolved. You're probably on an unborn branch.");
}
throw new GitRepositoryException(
String.format("Ref \"%s\" is invalid.", this.headRef));
}
return this.headObject;
}
/**
* Returns a map of raw JGit tags available in this repository
* <p>
* The keys of the map are the SHA IDs of the objects referenced by the
* tags. The map's values are the raw tags themselves.
* <p>
* <em>Note</em>: Only annotated tags referencing commit objects will be
* returned.
*
* @return A map of raw JGit tags in this repository
* @throws GitRepositoryException if an error occurs while determining the
* tags in this repository
*/
protected Map<String, RevTag> getRawTags()
throws GitRepositoryException {
RevWalk revWalk = this.getRevWalk();
Map<String, Ref> tagRefs = this.repository.getTags();
Map<String, RevTag> tags = new HashMap<String, RevTag>();
try {
for (Map.Entry<String, Ref> tag : tagRefs.entrySet()) {
try {
RevTag revTag = revWalk.parseTag(tag.getValue().getObjectId());
RevObject object = revWalk.peel(revTag);
if (!(object instanceof RevCommit)) {
continue;
}
tags.put(object.getName(), revTag);
} catch(IncorrectObjectTypeException e) {
continue;
}
}
} catch (MissingObjectException e) {
throw new GitRepositoryException("The tags could not be resolved.", e);
} catch (IOException e) {
throw new GitRepositoryException("The tags could not be resolved.", e);
}
return tags;
}
/**
* Gets a JGit {@code RevWalk} instance for this repository
* <p>
* Creates a new instance or resets an existing one.
*
* @return A {@code RevWalk} instance for this repository
*/
protected RevWalk getRevWalk() {
if (this.revWalk == null) {
this.revWalk = new RevWalk(this.repository);
}
return this.revWalk;
}
}