/jgit-flow-core/src/main/java/com/atlassian/jgitflow/core/util/GitHelper.java
Java | 614 lines | 440 code | 75 blank | 99 comment | 55 complexity | f6e58e6940107246f88b75b5dc74aeb1 MD5 | raw file
Possible License(s): Apache-2.0
- package com.atlassian.jgitflow.core.util;
- import java.io.IOException;
- import java.util.*;
- import com.atlassian.jgitflow.core.JGitFlowConstants;
- import com.atlassian.jgitflow.core.JGitFlowReporter;
- import com.atlassian.jgitflow.core.exception.JGitFlowGitAPIException;
- import com.atlassian.jgitflow.core.exception.JGitFlowIOException;
- import com.atlassian.jgitflow.core.exception.LocalBranchMissingException;
- import org.eclipse.jgit.api.Git;
- import org.eclipse.jgit.api.ListBranchCommand;
- import org.eclipse.jgit.api.errors.GitAPIException;
- import org.eclipse.jgit.lib.*;
- import org.eclipse.jgit.revwalk.RevCommit;
- import org.eclipse.jgit.revwalk.RevWalk;
- import org.eclipse.jgit.revwalk.filter.RevFilter;
- import org.eclipse.jgit.treewalk.FileTreeIterator;
- import org.eclipse.jgit.util.StringUtils;
- import static com.atlassian.jgitflow.core.util.Preconditions.checkNotNull;
- /**
- * A helper class for common Git operations
- */
- public class GitHelper
- {
- /**
- * Checks to see if one branch is merged into another
- *
- * @param git The git instance to use
- * @param commitString The name of the commit we're testing
- * @param baseBranch The name of the base branch to look for the merge
- * @return if the contents of branchName has been merged into baseName
- * @throws com.atlassian.jgitflow.core.exception.LocalBranchMissingException
- * @throws com.atlassian.jgitflow.core.exception.JGitFlowIOException
- * @throws com.atlassian.jgitflow.core.exception.JGitFlowGitAPIException
- */
- public static boolean isMergedInto(Git git, String commitString, String baseBranch) throws LocalBranchMissingException, JGitFlowIOException, JGitFlowGitAPIException
- {
- RevCommit branchCommit = getCommitForString(git, commitString);
- return isMergedInto(git, branchCommit, baseBranch);
- }
- /**
- * Gets a commit for a given string with no body
- *
- * @param git The git instance to use
- * @param commitId The name of the commit to find
- * @return The commit
- * @throws com.atlassian.jgitflow.core.exception.JGitFlowIOException
- */
- public static RevCommit getCommitForString(Git git, String commitId) throws JGitFlowIOException, LocalBranchMissingException
- {
- RevWalk walk = null;
- try
- {
- ObjectId commit = git.getRepository().resolve(commitId);
- if (null == commit)
- {
- throw new LocalBranchMissingException("commit " + commitId + " does not exist");
- }
- walk = new RevWalk(git.getRepository());
- walk.setRetainBody(true);
- return walk.parseCommit(commit);
- }
- catch (IOException e)
- {
- throw new JGitFlowIOException(e);
- }
- finally
- {
- if (null != walk)
- {
- walk.release();
- }
- }
- }
- /**
- * Checks to see if a specific commit is merged into a branch
- *
- * @param git The git instance to use
- * @param commit The commit to test
- * @param branchName The name of the base branch to look for the merge
- * @return if the contents of commit has been merged into baseName
- * @throws com.atlassian.jgitflow.core.exception.JGitFlowGitAPIException
- * @throws com.atlassian.jgitflow.core.exception.JGitFlowIOException
- */
- public static boolean isMergedInto(Git git, RevCommit commit, String branchName) throws JGitFlowGitAPIException, JGitFlowIOException
- {
- Repository repo = git.getRepository();
- try
- {
- ObjectId base = repo.resolve(branchName);
- if (null == base)
- {
- return false;
- }
- Iterable<RevCommit> baseCommits = git.log().add(base).call();
- boolean merged = false;
- for (RevCommit entry : baseCommits)
- {
- if (entry.getId().equals(commit))
- {
- merged = true;
- break;
- }
- if (entry.getParentCount() > 1 && Arrays.asList(entry.getParents()).contains(commit))
- {
- merged = true;
- break;
- }
- }
- return merged;
- }
- catch (GitAPIException e)
- {
- throw new JGitFlowGitAPIException(e);
- }
- catch (IOException e)
- {
- throw new JGitFlowIOException(e);
- }
- }
- /**
- * Gets the latest commit for a branch
- *
- * @param git The git instance to use
- * @param branchName The name of the branch to find the commit on
- * @return The latest commit for the branch
- * @throws com.atlassian.jgitflow.core.exception.JGitFlowIOException
- */
- public static RevCommit getLatestCommit(Git git, String branchName) throws JGitFlowIOException
- {
- RevWalk walk = null;
- try
- {
- ObjectId branch = git.getRepository().resolve(branchName);
- walk = new RevWalk(git.getRepository());
- walk.setRetainBody(true);
- return walk.parseCommit(branch);
- }
- catch (IOException e)
- {
- throw new JGitFlowIOException(e);
- }
- finally
- {
- if (null != walk)
- {
- walk.release();
- }
- }
- }
- /**
- * Checks to see if a local branch with the given name exists
- *
- * @param git The git instance to use
- * @param branchName The name of the branch to look for
- * @return if the branch exists or not
- * @throws com.atlassian.jgitflow.core.exception.JGitFlowGitAPIException
- */
- public static boolean localBranchExists(Git git, String branchName) throws JGitFlowGitAPIException
- {
- boolean exists = false;
- if (StringUtils.isEmptyOrNull(branchName))
- {
- return exists;
- }
- try
- {
- List<Ref> refs = git.branchList().setListMode(null).call();
- for (Ref ref : refs)
- {
- String simpleName = ref.getName().substring(ref.getName().indexOf(Constants.R_HEADS) + Constants.R_HEADS.length());
- if (simpleName.equals(branchName))
- {
- exists = true;
- break;
- }
- }
- return exists;
- }
- catch (GitAPIException e)
- {
- throw new JGitFlowGitAPIException(e);
- }
- }
- /**
- * Checks to see if a remote branch with the given name exists
- *
- * @param git The git instance to use
- * @param branch The name of the branch to look for
- * @return if the branch exists or not
- * @throws com.atlassian.jgitflow.core.exception.JGitFlowGitAPIException
- */
- public static boolean remoteBranchExists(Git git, final String branch, JGitFlowReporter reporter) throws JGitFlowGitAPIException
- {
- reporter.debugMethod(getName(), "remoteBranchExists");
- reporter.debugText(getName(), "checking for branch: " + branch);
- boolean exists = false;
- if (StringUtils.isEmptyOrNull(branch))
- {
- return exists;
- }
- try
- {
- List<Ref> refs = git.branchList().setListMode(ListBranchCommand.ListMode.REMOTE).call();
- reporter.debugText(getName(), "got " + refs.size() + " remote refs");
- for (Ref ref : refs)
- {
- reporter.debugText(getName(), "ref name: " + ref.getName());
- //if we're not coming from origin, just ignore
- if (!ref.getName().contains(JGitFlowConstants.R_REMOTE_ORIGIN))
- {
- continue;
- }
- String simpleName = ref.getName().substring(ref.getName().indexOf(JGitFlowConstants.R_REMOTE_ORIGIN) + JGitFlowConstants.R_REMOTE_ORIGIN.length());
- reporter.debugText(getName(), "ref simple name: " + simpleName);
- reporter.debugText(getName(), "simple name equals branch? " + simpleName.equals(branch));
- if (simpleName.equals(branch))
- {
- exists = true;
- break;
- }
- }
- return exists;
- }
- catch (GitAPIException e)
- {
- throw new JGitFlowGitAPIException(e);
- }
- finally
- {
- reporter.endMethod();
- reporter.flush();
- }
- }
- public static boolean localBranchBehindRemote(Git git, final String branch, JGitFlowReporter reporter) throws JGitFlowIOException
- {
- final RevWalk walk = new RevWalk(git.getRepository());
- walk.setRetainBody(true);
- boolean behind = false;
- try
- {
- Ref remote = getRemoteBranch(git, branch);
- Ref local = getLocalBranch(git, branch);
- checkNotNull(remote);
- checkNotNull(local);
- ObjectId remoteId = git.getRepository().resolve(remote.getObjectId().getName());
- RevCommit remoteCommit = walk.parseCommit(remoteId);
- RevCommit localCommit = walk.parseCommit(local.getObjectId());
- if (!localCommit.equals(remoteCommit))
- {
- reporter.debugText(getName(), localCommit.getName() + " !equals " + remoteCommit.getName());
- behind = true;
- walk.setRevFilter(RevFilter.MERGE_BASE);
- walk.markStart(localCommit);
- walk.markStart(remoteCommit);
- RevCommit base = walk.next();
- reporter.debugText(getName(), "checking if remote is at our merge base");
- if (null != base)
- {
- walk.parseBody(base);
- //remote is behind
- if (remoteCommit.equals(base))
- {
- reporter.debugText(getName(), "remote equals merge base, branch is newer");
- behind = false;
- }
- }
- }
- }
- catch (IOException e)
- {
- reporter.errorText(getName(), e.getMessage());
- reporter.endMethod();
- reporter.flush();
- throw new JGitFlowIOException(e);
- }
- finally
- {
- walk.release();
- }
- return behind;
- }
- /**
- * Gets a reference to a remote branch with the given name
- *
- * @param git The git instance to use
- * @param branchName The name of the remote branch
- * @return A reference to the remote branch or null
- * @throws com.atlassian.jgitflow.core.exception.JGitFlowIOException
- */
- public static Ref getRemoteBranch(Git git, String branchName) throws JGitFlowIOException
- {
- try
- {
- final Map<String, Ref> refList = git.getRepository().getRefDatabase().getRefs(Constants.R_REMOTES);
- Ref remote = null;
- for (Map.Entry<String, Ref> entry : refList.entrySet())
- {
- int index = entry.getValue().getName().indexOf(JGitFlowConstants.R_REMOTE_ORIGIN);
- if (index < 0)
- {
- continue;
- }
- String simpleName = entry.getValue().getName().substring(index + JGitFlowConstants.R_REMOTE_ORIGIN.length());
- if (simpleName.equals(branchName))
- {
- remote = entry.getValue();
- break;
- }
- }
- return remote;
- }
- catch (IOException e)
- {
- throw new JGitFlowIOException(e);
- }
- }
- /**
- * Gets a reference to a local branch with the given name
- *
- * @param git The git instance to use
- * @param branchName The name of the remote branch
- * @return A reference to the local branch or null
- * @throws com.atlassian.jgitflow.core.exception.JGitFlowIOException
- */
- public static Ref getLocalBranch(Git git, String branchName) throws JGitFlowIOException
- {
- try
- {
- Ref ref2check = git.getRepository().getRef(branchName);
- Ref local = null;
- if (ref2check != null && ref2check.getName().startsWith(Constants.R_HEADS))
- {
- local = ref2check;
- }
- return local;
- }
- catch (IOException e)
- {
- throw new JGitFlowIOException(e);
- }
- }
- /**
- * Gets a list of branch references that begin with the given prefix
- *
- * @param git The git instance to use
- * @param prefix The prefix to test for
- * @return A list of branch references matching the given prefix
- * @throws com.atlassian.jgitflow.core.exception.JGitFlowGitAPIException
- */
- public static List<Ref> listBranchesWithPrefix(Git git, String prefix, JGitFlowReporter reporter) throws JGitFlowGitAPIException
- {
- List<Ref> branches = new ArrayList<Ref>();
- reporter.debugMethod(getName(), "listBranchesWithPrefix");
- try
- {
- List<Ref> refs = git.branchList().setListMode(ListBranchCommand.ListMode.ALL).call();
- for (Ref ref : refs)
- {
- String simpleName;
- String originPrefix = Constants.R_REMOTES + Constants.DEFAULT_REMOTE_NAME + "/";
- if (ref.getName().indexOf(Constants.R_HEADS) > -1)
- {
- simpleName = ref.getName().substring(ref.getName().indexOf(Constants.R_HEADS) + Constants.R_HEADS.length());
- }
- else if (ref.getName().indexOf(originPrefix) > -1)
- {
- simpleName = ref.getName().substring(ref.getName().indexOf(originPrefix) + originPrefix.length());
- }
- else
- {
- simpleName = "";
- }
- reporter.debugText(getName(), "simple name [" + simpleName + "] startsWith prefix [" + prefix + "] ? " + simpleName.startsWith(prefix));
- if (simpleName.startsWith(prefix))
- {
- branches.add(ref);
- }
- }
- return branches;
- }
- catch (GitAPIException e)
- {
- throw new JGitFlowGitAPIException(e);
- }
- finally
- {
- reporter.endMethod();
- }
- }
- /**
- * Tests to see if a working folder is clean. e.g. all changes have been committed.
- *
- * @param git The git instance to use
- * @param allowUntracked
- * @param reporter @return if the branch is clean or not
- * @throws com.atlassian.jgitflow.core.exception.JGitFlowIOException
- * @throws com.atlassian.jgitflow.core.exception.JGitFlowGitAPIException
- */
- public static CleanStatus workingTreeIsClean(Git git, boolean allowUntracked, JGitFlowReporter reporter) throws JGitFlowIOException, JGitFlowGitAPIException
- {
- reporter.debugMethod(getName(), "workingTreeIsClean");
- try
- {
- IndexDiff diffIndex = new IndexDiff(git.getRepository(), Constants.HEAD, new FileTreeIterator(git.getRepository()));
- if (diffIndex.diff())
- {
- int addedSize = diffIndex.getAdded().size();
- int assumedSize = diffIndex.getAssumeUnchanged().size();
- int changedSize = diffIndex.getChanged().size();
- int conflictSize = diffIndex.getConflicting().size();
- int ignoredSize = diffIndex.getIgnoredNotInIndex().size();
- int missingSize = diffIndex.getMissing().size();
- int modifiedSize = diffIndex.getModified().size();
- int removedSize = diffIndex.getRemoved().size();
- int untrackedSize = diffIndex.getUntracked().size();
- int untrackedFolderSize = diffIndex.getUntrackedFolders().size();
- boolean changed = false;
- boolean untracked = false;
- StringBuilder sb = new StringBuilder();
- reporter.debugText(getName(), "diffIndex.diff() returned diffs. working tree is dirty!");
- reporter.debugText(getName(), "added size: " + addedSize);
- reportDirtyDetails(getName(), "added", diffIndex.getAdded(), reporter);
- reporter.debugText(getName(), "assume unchanged size: " + assumedSize);
- reporter.debugText(getName(), "changed size: " + changedSize);
- reportDirtyDetails(getName(), "changed", diffIndex.getChanged(), reporter);
- reporter.debugText(getName(), "conflicting size: " + conflictSize);
- reportDirtyDetails(getName(), "conflicting", diffIndex.getConflicting(), reporter);
- reporter.debugText(getName(), "ignored not in index size: " + ignoredSize);
- reporter.debugText(getName(), "missing size: " + missingSize);
- reportDirtyDetails(getName(), "missing", diffIndex.getMissing(), reporter);
- reporter.debugText(getName(), "modified size: " + modifiedSize);
- reportDirtyDetails(getName(), "modified", diffIndex.getModified(), reporter);
- reporter.debugText(getName(), "removed size: " + removedSize);
- reportDirtyDetails(getName(), "removed", diffIndex.getRemoved(), reporter);
- reporter.debugText(getName(), "untracked size: " + untrackedSize);
- reportDirtyDetails(getName(), "untracked", diffIndex.getUntracked(), reporter);
- reporter.debugText(getName(), "untracked folders size: " + untrackedFolderSize);
- reportDirtyDetails(getName(), "untracked folders", diffIndex.getUntrackedFolders(), reporter);
- reporter.endMethod();
- if (addedSize > 0 || changedSize > 0 || conflictSize > 0 || missingSize > 0 || modifiedSize > 0 || removedSize > 0)
- {
- changed = true;
- sb.append("Working tree has uncommitted changes");
- }
- if (!allowUntracked && (untrackedSize > 0 || untrackedFolderSize > 0))
- {
- if (ignoredSize > 0)
- {
- Set<String> ignores = diffIndex.getIgnoredNotInIndex();
- if (untrackedSize > 0)
- {
- Set<String> utFiles = diffIndex.getUntracked();
- utFiles.removeAll(ignores);
- untrackedSize = utFiles.size();
- }
- if (untrackedFolderSize > 0)
- {
- Set<String> utFolders = diffIndex.getUntrackedFolders();
- utFolders.removeAll(ignores);
- untrackedFolderSize = utFolders.size();
- }
- }
- if (untrackedSize > 0 || untrackedFolderSize > 0)
- {
- untracked = true;
- }
- if (!changed)
- {
- sb.append("Working tree has untracked files");
- }
- else
- {
- sb.append(" and untracked files");
- }
- }
- return new CleanStatus(untracked, changed, sb.toString());
- }
- reporter.debugText(getName(), "working tree is clean");
- reporter.endMethod();
- return new CleanStatus(false, false, "Working tree is clean");
- }
- catch (IOException e)
- {
- reporter.errorText(getName(), e.getMessage());
- reporter.endMethod();
- reporter.flush();
- throw new JGitFlowIOException(e);
- }
- }
- private static void reportDirtyDetails(String cmdName, String reason, Set<String> files, JGitFlowReporter reporter)
- {
- if (files.size() > 0)
- {
- reporter.debugText(cmdName, reason + " details: ");
- for (String file : files)
- {
- reporter.debugText(cmdName, " -- " + reason + ": " + file);
- }
- }
- }
- /**
- * Tests to see if a tag exists with the given name
- *
- * @param git The git instance to use
- * @param tagName The name of the tag to test for
- * @return if the tag exists or not
- * @throws com.atlassian.jgitflow.core.exception.JGitFlowGitAPIException
- */
- public static boolean tagExists(Git git, final String tagName) throws JGitFlowGitAPIException
- {
- boolean exists = false;
- if (StringUtils.isEmptyOrNull(tagName))
- {
- return exists;
- }
- try
- {
- List<Ref> refs = git.tagList().call();
- for (Ref ref : refs)
- {
- String simpleName = ref.getName().substring(ref.getName().indexOf(Constants.R_TAGS) + Constants.R_TAGS.length());
- if (simpleName.equals(tagName))
- {
- exists = true;
- break;
- }
- }
- return exists;
- }
- catch (GitAPIException e)
- {
- throw new JGitFlowGitAPIException(e);
- }
- }
- private static String getName()
- {
- return GitHelper.class.getSimpleName();
- }
- }