org.apereo.cas.mgmt.GitUtil Maven / Gradle / Ivy
The newest version!
package org.apereo.cas.mgmt;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apereo.cas.mgmt.authentication.CasUserProfile;
import org.apereo.cas.mgmt.services.web.beans.Commit;
import org.apereo.cas.mgmt.services.web.beans.History;
import org.eclipse.jgit.api.CreateBranchCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.PullResult;
import org.eclipse.jgit.api.ResetCommand;
import org.eclipse.jgit.api.Status;
import org.eclipse.jgit.diff.DiffAlgorithm;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.diff.EditList;
import org.eclipse.jgit.diff.RawText;
import org.eclipse.jgit.diff.RawTextComparator;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.merge.MergeStrategy;
import org.eclipse.jgit.notes.Note;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.transport.FetchResult;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
* Utility class used to help with manipulating git repositories.
*
* @author Travis Schmidt
* @since 5.2
*/
@Slf4j
@RequiredArgsConstructor
@NoArgsConstructor(force = true)
public class GitUtil implements AutoCloseable {
/**
* Constant representing length of Object ID to return.
*/
public static final int NAME_LENGTH = 40;
private final Git git;
public GitUtil(final String path) {
boolean repositoryMustExist = true;
final File gitDir = new File(path);
if (!gitDir.exists()) {
LOGGER.debug("Creating git repository directory at [{}]", gitDir);
final boolean result = gitDir.mkdirs();
if (!result) {
LOGGER.warn("Failed to create git repository directory at [{}]", gitDir);
repositoryMustExist = false;
}
}
this.git = initializeGitRepository(gitDir, repositoryMustExist);
}
/**
* Returns Commit objects for the last n commits.
*
* @param n - number of commits to return
* @return - List of Commit objects
* @throws Exception - failed.
*/
public Stream getLastNCommits(final int n) throws Exception {
return StreamSupport.stream(git.log().setMaxCount(n).call().spliterator(), false);
}
/**
* Gets repository.
*
* @return the repository
*/
public Repository getRepository() {
return git.getRepository();
}
/**
* Returns a list of Commits representing all changes since the last time the repo was
* published.
*
* @return - List of Commit objects
* @throws Exception - failed
*/
public List getUnpublishedCommits() throws Exception {
final List commits = StreamSupport
.stream(git.log()
.addRange(getPublished().getPeeledObjectId(), git.getRepository().resolve("HEAD"))
.call().spliterator(), false)
.map(c -> new Commit(c.abbreviate(NAME_LENGTH).name(), c.getFullMessage(), null))
.collect(Collectors.toList());
Collections.reverse(commits);
return commits;
}
/**
* Creates a branch with the passed name and commit id from wich to start the branch.
*
* @param branchName - The name of the new branch to create.
* @param startPoint - The commit from which to start the branch.
* @throws Exception - failed.
*/
public void createBranch(final String branchName, final String startPoint) throws Exception {
git.checkout()
.setCreateBranch(true)
.setName(branchName)
.setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.TRACK)
.setStartPoint(startPoint)
.call();
}
/**
* Cherry picks a single commit to be merge in the current branch.
*
* @param commit - RevCommit to be included.
* @throws Exception - failed.
*/
public void cherryPickCommit(final RevCommit commit) throws Exception {
git.cherryPick().include(commit).setNoCommit(true).call();
}
/**
* Creates a branch in the remote repository from which the the current git repository was cloned.
* The branch is created from the commit passed and given the name that is passed in.
*
* @param commit - RevCommit that is to be pushed.
* @param submitName - The name of the remote branch to be created.
* @throws Exception - failed.
*/
public void createPullRequest(final RevCommit commit, final String submitName) throws Exception {
markAsSubmitted(commit);
git.push()
.setRemote("origin")
.setPushAll()
.setForce(true)
.setRefSpecs(new RefSpec("HEAD:refs/heads/" + submitName))
.call();
}
/**
* Commit.
*
* @param message the message
* @return the rev commit
* @throws Exception the exception
*/
public RevCommit commit(final String message) throws Exception {
if (!isUndefined()) {
return git.commit().setAll(true).setMessage(message).call();
}
return null;
}
/**
* Commits all working changes to the repository with the passed commit message.
*
* @param user - CasUserProfile of the logged in user.
* @param msg - Commit message.
* @return - RevCommit of the new commit.
* @throws Exception - failed.
*/
public RevCommit commit(final CasUserProfile user, final String msg) throws Exception {
return git.commit()
.setAll(true)
.setCommitter(getCommitterId(user))
.setMessage(msg)
.call();
}
/**
* Checks out the passed ref to be the current branch of the repository.
*
* @param ref - String representing a commit in the repository.
* @throws Exception - failed.
*/
public void checkout(final String ref) throws Exception {
git.checkout()
.setName(ref)
.call();
}
/**
* Checks out a single file from a commit in the repository and adds it to the working dir.
*
* @param path - Full path to the file.
* @param ref - String representing a commit in the repository.
* @throws Exception - failed.
*/
public void checkout(final String path, final String ref) throws Exception {
git.checkout()
.setStartPoint(ref)
.addPath(path)
.call();
}
/**
* Adds unversioned files to be tracked by the repository.
*
* @throws Exception - failed.
*/
public void addWorkingChanges() throws Exception {
final Status status = git.status().call();
status.getUntracked().forEach(this::addFile);
}
/**
* Status.
*
* @return the status
* @throws Exception the exception
*/
public Status status() throws Exception {
if (!isUndefined()) {
return git.status().call();
}
return null;
}
/**
* Scans the working dir for active changes and returns a list of differences.
*
* @return - List of DiffEntry.
*/
public List scanWorkingDiffs() {
if (isUndefined()) {
return new ArrayList<>();
}
final FileTreeIterator workTreeIterator = new FileTreeIterator(git.getRepository());
final CanonicalTreeParser oldTreeIter = new CanonicalTreeParser();
try (ObjectReader reader = git.getRepository().newObjectReader()) {
oldTreeIter.reset(reader, git.getRepository().resolve("HEAD^{tree}"));
final DiffFormatter formatter = new DiffFormatter(new ByteArrayOutputStream());
formatter.setRepository(git.getRepository());
return formatter.scan(oldTreeIter, workTreeIterator);
} catch (final Exception e) {
LOGGER.error(e.getMessage(), e);
}
return new ArrayList<>();
}
/**
* Returns the content of the file as a String that is represented by the passed object id.
*
* @param id - ObjectId of the file in the repository
* @return - Contents of the object as String.
* @throws Exception - failed.
*/
public String readFormWorkingTree(final ObjectId id) throws Exception {
final FileTreeIterator workTreeIterator = new FileTreeIterator(git.getRepository());
while (!workTreeIterator.eof() && !workTreeIterator.getEntryObjectId().equals(id)) {
workTreeIterator.next(1);
}
return IOUtils.toString(workTreeIterator.openEntryStream(), Charset.defaultCharset());
}
/**
* Returns a RawText representation of a file in the passed repository. Used in creating diffs.
*
* @param repo - The repository to pull the change.
* @param path - The path to the file.
* @return - RawText representation of the file.
* @throws Exception - failed.
*/
public RawText raw(final Repository repo, final String path) throws Exception {
final File file = new File(repo.getWorkTree().getAbsolutePath() + "/" + path);
return new RawText(FileUtils.readFileToByteArray(file));
}
/**
* Returns the file as a String form the repository indexed by the passed String
* representing its ObjectId.
*
* @param id - String id of a file in the repository.
* @return - File returned as String.
* @throws Exception - failed.
*/
public String readObject(final String id) throws Exception {
return readObject(ObjectId.fromString(id));
}
/**
* Returns the file as a String form the repository indexed by its ObjectId.
*
* @param id - ObjectID of the file.
* @return - File returned as String.
* @throws Exception - failed.
*/
@SuppressWarnings("DefaultCharset")
public String readObject(final ObjectId id) throws Exception {
final ObjectReader reader = git.getRepository().newObjectReader();
if (reader.has(id)) {
return new String(reader.open(id).getBytes());
} else {
return readFormWorkingTree(id);
}
}
/**
* Merges the branch represented by the passed branchId in the current branch.
*
* @param branchId - String representation of an ObjectId
* @throws Exception - failed.
*/
public void merge(final String branchId) throws Exception {
git.merge()
.setCommit(true)
.include(ObjectId.fromString(branchId))
.call();
}
/**
* Returns a RevCommit object looked up from the passed string id.
*
* @param id - String representing an ObjectID for a RevCommit.
* @return - RevCommit
* @throws Exception - failed.
*/
public RevCommit getCommit(final String id) throws Exception {
return new RevWalk(git.getRepository())
.parseCommit(ObjectId.fromString(id));
}
/**
* Appends a note to a commit that already has notes.
*
* @param com - RevObject representing the commit to add the note to.
* @param msg - The note to append.
* @throws Exception - failed.
*/
public void appendNote(final RevObject com, final String msg) throws Exception {
final Note note = note(com);
final StringBuilder buffer = new StringBuilder(msg.length());
if (note != null) {
try (OutputStream bytes = new ByteArrayOutputStream()) {
git.getRepository().open(note.getData()).copyTo(bytes);
buffer.append(bytes.toString().concat("\n\n"));
}
}
buffer.append(msg);
addNote(com, buffer.toString());
git.close();
}
/**
* Creates a note to a commit.
*
* @param com - the RevObject fo the commit
* @param note - the note text.
* @throws Exception - failed.
*/
public void addNote(final RevObject com, final String note) throws Exception {
git.notesAdd()
.setObjectId(com)
.setMessage(note)
.call();
}
/**
* Returns the note attached to the commit with the passed id.
*
* @param id - String representing a commit id.
* @return - Note attached to commit.
* @throws Exception - failed.
*/
public Note note(final String id) throws Exception {
return note(getCommit(id));
}
/**
* Returns the Note attached to the passed commit.
*
* @param com - RevObject of the commit.
* @return - Returns Note from the commit.
* @throws Exception - failed.
*/
public Note note(final RevObject com) throws Exception {
return git.notesShow()
.setObjectId(com)
.call();
}
/**
* Gets the note attached to a commit.
*
* @param com - the commit
* @return - the note
* @throws Exception - failed
*/
public String noteText(final RevCommit com) throws Exception {
return noteText(note(com));
}
/**
* Returns a the Note of a commit as a String.
*
* @param com - The RevObkect of the commit to pull the note from.
* @return - Returns the note text as a String.
* @throws Exception -failed.
*/
public String noteText(final RevObject com) throws Exception {
if (!isUndefined()) {
final Note note = note(com);
if (note != null) {
final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
git.getRepository().open(note.getData()).copyTo(bytes);
return bytes.toString();
}
}
return StringUtils.EMPTY;
}
/**
* Extracts the text from a Note.
*
* @param note - the note
* @return - the text
* @throws Exception - failed
*/
public String noteText(final Note note) throws Exception {
final StringBuilder buffer = new StringBuilder();
try (OutputStream bytes = new ByteArrayOutputStream()) {
git.getRepository().open(note.getData()).copyTo(bytes);
buffer.append(bytes.toString().concat("\n\n"));
}
return buffer.toString();
}
/**
* Returns the history of a file in the repository.
*
* @param path - the file path
* @return - List of History objects
* @throws Exception - failed.
*/
public List history(final String path) throws Exception {
return logs(path)
.map(r -> createHistory(r, path))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
/**
* Returns the logs for a file in the repository.
*
* @param path - The file path.
* @return - Stream of RevCommits the file is in.
* @throws Exception - failed.
*/
public Stream logs(final String path) throws Exception {
return StreamSupport.stream(git.log().addPath(path).call().spliterator(), false);
}
/**
* Checks out a file into the working directory.
*
* @param path - The file path.
* @throws Exception - failed.
*/
public void checkoutFile(final String path) throws Exception {
git.checkout()
.addPath(path)
.call();
}
/**
* Returns the logs for a specified commit.
*
* @param com - The commit to retrieive logs for.
* @return - Stream of RevCommits contained in the passed commit.
* @throws Exception - failed.
*/
public Stream commitLogs(final RevCommit com) throws Exception {
return StreamSupport.stream(git.log().add(com).call().spliterator(), false);
}
/**
* Peforms a hard reset on the repository to the passed commit.
*
* @param reset - the RevCommit to reset the repository to.
* @throws Exception - failed.
*/
public void reset(final RevCommit reset) throws Exception {
if (!isUndefined()) {
git.reset()
.setRef(reset.abbreviate(NAME_LENGTH).name())
.setMode(ResetCommand.ResetType.HARD)
.call();
}
}
/**
* Reset.
*
* @param path the path
* @throws Exception the exception
*/
public void reset(final String path) throws Exception {
if (!isUndefined()) {
git.reset().addPath(path).call();
}
}
/**
* Creates a History object for the passed file in the passed commit.
*
* @param r - The commit to pull the History from.
* @param path - The file path.
* @return - History of the path for the passed commit.
*/
public History createHistory(final RevCommit r, final String path) {
try {
final TreeWalk treeWalk = historyWalk(r, path);
if (treeWalk.next()) {
final History history = new History();
history.setId(treeWalk.getObjectId(0).abbreviate(NAME_LENGTH).name());
history.setCommit(r.abbreviate(NAME_LENGTH).name());
history.setPath(treeWalk.getPathString());
history.setMessage(r.getFullMessage());
history.setTime(formatCommitTime(r.getCommitTime()));
history.setCommitter(r.getCommitterIdent().getName());
return history;
}
} catch (final Exception e) {
LOGGER.error(e.getMessage(), e);
}
return null;
}
private static String formatCommitTime(final int ctime) {
return LocalDateTime.ofInstant(new Date(ctime * 1000L).toInstant(),
ZoneId.systemDefault())
.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}
/**
* Returns a TreeWalk object for the passed commit and file path.
*
* @param r - The commit to start the walk from.
* @param path - The file path.
* @return - TreeWalk
* @throws Exception - failed.
*/
public TreeWalk historyWalk(final RevCommit r, final String path) throws Exception {
final TreeWalk treeWalk = new TreeWalk(git.getRepository());
treeWalk.addTree(r.getTree());
treeWalk.setFilter(new HistoryTreeFilter(path));
return treeWalk;
}
/**
* Method adds an untracked file to the git index.
*
* @param file - the file.
*/
@SneakyThrows
public void addFile(final String file) {
if (!isUndefined()) {
git.add().addFilepattern(file).call();
}
}
/**
* Marks a commit as being submitted for a pull request.
*
* @param c - The RevObject of the commit to mark as submitted.
* @throws Exception -failed.
*/
public void markAsSubmitted(final RevObject c) throws Exception {
appendNote(c, "SUBMITTED on " + new Date().toString() + "\n ");
}
/**
* Creates a Person Identity to add to the commit.
*
* @param user - CasUserProfile of the logged in user.
* @return - PersonIden object to be added to a commit.
*/
public static PersonIdent getCommitterId(final CasUserProfile user) {
final String email = user.getEmail() != null ? user.getEmail() : StringUtils.EMPTY;
return new PersonIdent(user.getId(), email);
}
/**
* Method will tag the current HEAD commit has being the latest published.
*/
@SneakyThrows
public void setPublished() {
if (!isUndefined()) {
git.tagDelete().setTags("published").call();
git.tag().setName("published").call();
}
}
/**
* Method returns the Ref to the commit witht the published tag.
*
* @return - Ref of the published commit
*/
public Ref getPublished() {
try {
final Ref ref = git.tagList().call().get(0);
return git.getRepository().peel(ref);
} catch (final Exception e) {
LOGGER.trace(e.getMessage(), e);
return null;
}
}
/**
* Rm.
*
* @param newPath the new path
* @throws Exception the exception
*/
public void rm(final String newPath) throws Exception {
if (!isUndefined()) {
git.rm().addFilepattern(newPath).call();
}
}
/**
* Class used to define a TreeFilter to only pull history for a single path.
*/
@RequiredArgsConstructor
private static class HistoryTreeFilter extends TreeFilter {
private final String path;
@Override
public boolean include(final TreeWalk treeWalk) {
final Splitter splitter = Splitter.on("-");
final List pathSplit = splitter.splitToList(path);
final List treePathSplit = splitter.splitToList(treeWalk.getPathString());
return pathSplit.get(pathSplit.size() - 1).equals(treePathSplit.get(treePathSplit.size() - 1));
}
@Override
public boolean shouldBeRecursive() {
return false;
}
@Override
public TreeFilter clone() {
return null;
}
}
/**
* Closes the git repository.
*/
@Override
public void close() {
if (!isUndefined()) {
git.close();
}
}
/**
* Method to determine if there is not wrapped repository.
*
* @return - true if no git repository is present.
*/
public boolean isUndefined() {
return git == null;
}
/**
* Returns a new ObjectReader for the repository.
*
* @return - ObjectReader.
*/
public ObjectReader objectReader() {
return git.getRepository().newObjectReader();
}
/**
* Returns the root path of the repository.
*
* @return -String representing the root directory.
*/
public String repoPath() {
if (!isUndefined()) {
return git.getRepository().getDirectory().getParent();
}
return StringUtils.EMPTY;
}
/**
* Returns a stream of Branches that are contained in the repository.
*
* @return - Stream of Branch Refs
* @throws Exception - failed.
*/
public Stream branches() throws Exception {
if (!isUndefined()) {
return git.branchList().call().stream();
}
return Stream.empty();
}
/**
* Returns the repository wrapped by this utility.
*
* @return - Git repository.
*/
private Git getGit() {
return git;
}
/**
* Pulls the text form a Note object and writes it to the passes Outputstream.
*
* @param note - The Note contained in the repository to read.
* @param output - The stream to ouput the note text.
* @throws Exception - failed.
*/
public void writeNote(final Note note, final OutputStream output) throws Exception {
if (!isUndefined()) {
git.getRepository().open(note.getData()).copyTo(output);
}
}
/**
* Pulls changes form the default remote repository into the wrapped repository.
*
* @throws Exception - failed.
*/
public void pull() throws Exception {
if (!isUndefined()) {
git.pull().call();
}
}
/**
* Returns a BranchMap for the commit passed as a Ref.
*
* @param r - Ref commit to generate the BranchMap for.
* @return - BranchMap.
*/
public BranchMap mapBranches(final Ref r) {
try {
final RevWalk revWalk = new RevWalk(git.getRepository());
return new BranchMap(this, r, revWalk.parseCommit(git.getRepository().resolve(r.getName())));
} catch (final Exception e) {
LOGGER.error(e.getMessage(), e);
}
return null;
}
/**
* Returns a list fo differences between the last two commits in a branch.
*
* @param branch - The branch to check for differences against.
* @return - List of DiffEntry.
* @throws Exception - failed.
*/
public List getDiffsMinus1(final String branch) throws Exception {
return getDiffs(branch + "^{tree}", branch + "~1^{tree}");
}
/**
* Returns the the diffs of two branches.
*
* @param first - the first branch
* @param second - the second branch
* @return - List of DiffEntry
* @throws Exception - failed
*/
public List getDiffs(final String first, final String second) throws Exception {
final CanonicalTreeParser oldTreeIter = new CanonicalTreeParser();
final ObjectReader reader = git.getRepository().newObjectReader();
oldTreeIter.reset(reader, git.getRepository().resolve(first));
final CanonicalTreeParser newTreeIter = new CanonicalTreeParser();
newTreeIter.reset(reader, git.getRepository().resolve(second));
return git.diff().setOldTree(oldTreeIter).setNewTree(newTreeIter).call();
}
/**
* Returns the Diff of a file on a path against HEAD.
*
* @param path - path to file
* @return - DiffEntry
* @throws Exception -failed
*/
public DiffEntry getChange(final String path) throws Exception {
return getChange("HEAD", path);
}
/**
* Returns the diff of a file in a commit and against the the previous version.
*
* @param commit - the commit
* @param path - the file path
* @return - DiffEntry
* @throws Exception - No difference
*/
public DiffEntry getChange(final String commit, final String path) throws Exception {
return getDiffsMinus1(commit).stream()
.filter(d -> d.getNewPath().contains(path))
.findFirst().orElseThrow(() -> new Exception("No Difference"));
}
/**
* Compares the same file between two commits.
*
* @param first - the first commit
* @param second - the second commit
* @param path - the file
* @return - DiffEntry
* @throws Exception - No Difference
*/
public DiffEntry getChange(final String first, final String second, final String path) throws Exception {
return getDiffs(first + "^{tree}", second + "^{tree}").stream()
.filter(d -> d.getNewPath().contains(path))
.findFirst().orElseThrow(() -> new Exception("No Difference"));
}
/**
* Returns a list fo differences between the last two commits in a branch.
*
* @param branch - The branch to check for differences against.
* @return - List of DiffEntry.
* @throws Exception - failed.
*/
public List getPublishDiffs(final String branch) throws Exception {
return getDiffs(branch + "~1^{tree}", branch + "^{tree}");
}
/**
* Returns a list fo differences between the last two commits in a branch.
*
* @param branch - The branch to check for differences against.
* @return - List of DiffEntry.
* @throws Exception - failed.
*/
public List getDiffsToRevert(final String branch) throws Exception {
final CanonicalTreeParser oldTreeIter = new CanonicalTreeParser();
final ObjectReader reader = git.getRepository().newObjectReader();
oldTreeIter.reset(reader, git.getRepository().resolve(branch + "^{tree}"));
final CanonicalTreeParser newTreeIter = new CanonicalTreeParser();
newTreeIter.reset(reader, git.getRepository().resolve(branch + "~1^{tree}"));
return git.diff().setOldTree(oldTreeIter).setNewTree(newTreeIter).call();
}
/**
* Overloaded method to return a formatted diff by using two ObjectIds.
*
* @param oldId - ObjectId.
* @param newId - ObectId.
* @return - Formatted diff in a byte[].
* @throws Exception -failed.
*/
public byte[] getFormatter(final ObjectId oldId, final ObjectId newId) throws Exception {
return getFormatter(rawText(oldId), rawText(newId));
}
/**
* Overloaded method to return a formatted diff by using a RawText and an ObjectId.
*
* @param oldText - RawText.
* @param newId - ObjectId.
* @return - Formatted diff in a byte[].
* @throws Exception -failed.
*/
public byte[] getFormatter(final RawText oldText, final ObjectId newId) throws Exception {
return getFormatter(oldText, rawText(newId));
}
/**
* Overloaded method to return a formatted diff by using a RawText and an ObjectId.
*
* @param oldId - ObjectId.
* @param newText - RawText.
* @return - Formatted diff in a byte[].
* @throws Exception - failed.
*/
public byte[] getFormatter(final ObjectId oldId, final RawText newText) throws Exception {
return getFormatter(rawText(oldId), newText);
}
/**
* Compares the RawText of two files and creates a formateted diff to return.
*
* @param oldText - RawText.
* @param newText - RawText.
* @return - Formatted diff in a byte[].
* @throws Exception -failed.
*/
public byte[] getFormatter(final RawText oldText, final RawText newText) throws Exception {
if (!isUndefined()) {
final DiffAlgorithm diffAlgorithm = DiffAlgorithm.getAlgorithm(
git.getRepository().getConfig().getEnum("diff",
null, "algorithm", DiffAlgorithm.SupportedAlgorithm.HISTOGRAM));
final EditList editList = diffAlgorithm.diff(RawTextComparator.DEFAULT, oldText, newText);
final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
final DiffFormatter df = new DiffFormatter(bytes);
df.setRepository(git.getRepository());
df.format(editList, oldText, newText);
df.flush();
return bytes.toByteArray();
}
return StringUtils.EMPTY.getBytes(StandardCharsets.UTF_8);
}
/**
* Returns the RawText of file specified by ObjectId.
*
* @param id - ObjectId of a file.
* @return - RawText.
* @throws Exception - failed.
*/
@SuppressWarnings("DefaultCharset")
public RawText rawText(final ObjectId id) throws Exception {
final ObjectReader objectReader = objectReader();
if (objectReader.has(id)) {
return new RawText(objectReader.open(id).getBytes());
} else {
return new RawText(readFormWorkingTree(id).getBytes());
}
}
/**
* Returns the RawText of a file specified by its path.
*
* @param path - File path.
* @return - RawText.
* @throws Exception - failed.
*/
public RawText rawText(final String path) throws Exception {
final File file = new File(git.getRepository().getWorkTree().getAbsolutePath() + "/" + path);
return new RawText(FileUtils.readFileToByteArray(file));
}
/**
* Returns the last commit before the commit that was submitted as a pull request.
*
* @param branchName - Name given to the branch when submitted.
* @return - RevCommit of the previous commit.
* @throws Exception - failed.
*/
public RevCommit findCommitBeforeSubmit(final String branchName) throws Exception {
final RevCommit com = findSubmitCommit(branchName);
final RevCommit before = commitLogs(com).skip(1).findFirst().get();
return before;
}
/**
* Returns the commit used to submit the pull request.
*
* @param branchName - Name given to the branch when submitted.
* @return - RevCommit used to submit the pull request.
* @throws Exception - failed.
*/
public RevCommit findSubmitCommit(final String branchName) throws Exception {
return git.branchList()
.call()
.stream()
.map(this::mapBranches)
.filter(r -> r.getRef().getName().contains(Iterables.get(Splitter.on('_').split(branchName), 1)))
.findFirst().get().revCommit;
}
/**
* Marks a pull request as being reverted by the person who submitted it.
*
* @param branch - Ref of the branch to revert.
* @param user - CasUserProfile of the logged in user.
* @throws Exception - failed.
*/
public void markAsReverted(final String branch, final CasUserProfile user) throws Exception {
final RevWalk revWalk = new RevWalk(git.getRepository());
final RevCommit com = revWalk.parseCommit(git.getRepository().resolve(branch));
final String msg = "REVERTED by " + user.getId() + " on " + new Date().toString() + "\n ";
appendNote(com, msg);
}
/**
* Returns the name of the current branch in the repository.
*
* @return - the name of the branch.
* @throws Exception - failed
*/
public String currentBranchName() throws Exception {
return git.getRepository().getBranch();
}
/**
* Rebases the wrapped repository to the remote it was created form.
*
* @return self
*/
public GitUtil rebase() {
try {
if (checkMaster()) {
attemptRebase().stream().forEach(this::resolveConflict);
}
} catch (final Exception e) {
LOGGER.error("Error Rebasing git ", e);
} finally {
git.close();
}
return this;
}
private boolean checkMaster() throws Exception {
final FetchResult fr = git.fetch().setDryRun(true).call();
git.close();
return !fr.getTrackingRefUpdates().isEmpty();
}
private Collection attemptRebase() throws Exception {
final Collection conflicts = new HashSet<>();
createStashIfNeeded();
final PullResult pr = git.pull().setStrategy(MergeStrategy.RESOLVE).setRebase(true).call();
if (pr.getRebaseResult().getConflicts() != null) {
conflicts.addAll(pr.getRebaseResult().getConflicts());
}
conflicts.addAll(applyStashIfNeeded());
return conflicts;
}
private void resolveConflict(final String path) {
try {
git.reset().addPath(path).call();
git.checkout().addPath(path).call();
} catch (final Exception e) {
LOGGER.error(e.getMessage(), e);
}
}
private void createStashIfNeeded() throws Exception {
if (!git.status().call().isClean()) {
git.stashCreate().call();
}
}
private Collection applyStashIfNeeded() throws Exception {
if (!git.stashList().call().isEmpty()) {
try {
git.stashApply().call();
} catch (final Exception e) {
final Set conflicts = git.status().call().getConflicting();
git.close();
return conflicts;
}
}
return new HashSet<>();
}
/**
* Method determines if a branch has been rejected by an admin.
*
* @param com - RevObject of the commit.
* @return - trues if commit is marked as rejected.
*/
public boolean isRejected(final RevObject com) {
try {
return noteText(com).contains("REJECTED");
} catch (final Exception e) {
LOGGER.error(e.getMessage(), e);
}
return false;
}
/**
* Method determines if a branch has been rejected by an admin.
*
* @param com - RevObject of the commit.
* @return - true if the commit is marked as reverted.
*/
public boolean isReverted(final RevObject com) {
try {
return noteText(com).contains("REVERTED");
} catch (final Exception e) {
LOGGER.error(e.getMessage(), e);
}
return false;
}
/**
* Method determines if a branch has been accepted by an admin.
*
* @param com - RevObject of the commit.
* @return - true if the commit is marked as accpeted.
*/
public boolean isAccepted(final RevObject com) {
try {
return noteText(com).contains("ACCEPTED");
} catch (final Exception e) {
LOGGER.error(e.getMessage(), e);
}
return false;
}
/**
* This methods moves the file when it has been renamed.
*
* @param oldName - the old name
* @param newName - the new mame
* @throws Exception - failed.
*/
public void move(final String oldName, final String newName) throws Exception {
final String repoPath = repoPath();
LOGGER.debug("Attempting to move [{}] to [{}]", oldName, newName);
final Path oldPath = Paths.get(repoPath + '/' + oldName);
final Path target = Paths.get(repoPath + '/' + newName);
LOGGER.debug("Moving [{}] to [{}]", oldPath, target);
Files.move(oldPath, target);
git.add().addFilepattern(newName).call();
git.rm().addFilepattern(oldName).call();
}
/**
* Object used to represent the history of a branch.
*/
@RequiredArgsConstructor
@Getter
@Setter
public static class BranchMap {
private Ref ref;
private RevCommit revCommit;
private final GitUtil git;
public BranchMap(final GitUtil git, final Ref ref, final RevCommit revCommit) {
this(git);
this.ref = ref;
this.revCommit = revCommit;
}
public String getName() {
return ref.getName();
}
public String getFullMessage() {
return revCommit.getFullMessage();
}
public String getCommitter() {
return revCommit.getCommitterIdent().getName();
}
public int getCommitTime() {
return revCommit.getCommitTime();
}
public String getId() {
return revCommit.abbreviate(NAME_LENGTH).name();
}
public boolean isAccepted() {
return git.isAccepted(revCommit);
}
public boolean isRejected() {
return git.isRejected(revCommit);
}
public boolean isReverted() {
return git.isReverted(revCommit);
}
}
@SneakyThrows
private static Git initializeGitRepository(final File path, final boolean mustExist) {
LOGGER.debug("Initializing git repository directory at [{}] with strict path checking [{}]", path, BooleanUtils.toStringOnOff(mustExist));
final FileRepositoryBuilder builder = new FileRepositoryBuilder()
.setGitDir(path)
.setMustExist(mustExist)
.findGitDir()
.readEnvironment();
try {
return new Git(builder.build());
} catch (final RepositoryNotFoundException e) {
LOGGER.error("Git repository not found/initialized at [{}]", path.getCanonicalPath());
throw new RuntimeException(e.getMessage(), e);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy