package com.atlassian.jgitflow.core;
import java.io.File;
import java.nio.charset.Charset;
import java.util.List;
import com.atlassian.jgitflow.core.exception.JGitFlowException;
import com.atlassian.jgitflow.core.exception.MergeConflictsNotResolvedException;
import com.atlassian.jgitflow.core.util.GitHelper;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.io.Files;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.MergeCommand;
import org.eclipse.jgit.api.MergeResult;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.RefSpec;
import static com.google.common.base.Preconditions.checkState;
/**
* @since version
*/
public class FeatureFinishCommand extends AbstractGitFlowCommand<Void>
{
private final String branchName;
private boolean fetchDevelop;
private boolean rebase;
private boolean keepBranch;
private boolean forceDeleteBranch;
private boolean squash;
public FeatureFinishCommand(String name, Git git, GitFlowConfiguration gfConfig)
{
super(git,gfConfig);
checkState(!Strings.isNullOrEmpty(name));
this.branchName = name;
this.fetchDevelop = false;
this.rebase = false;
this.keepBranch = false;
this.forceDeleteBranch = false;
this.squash = false;
}
@Override
public Void call() throws Exception
{
String prefixedBranchName = gfConfig.getPrefixValue(JGitFlowConstants.PREFIXES.FEATURE.configKey()) + branchName;
requireGitFlowInitialized();
requireLocalBranchExists(prefixedBranchName);
//check to see if we're restoring from a merge conflict
File flowDir = new File(git.getRepository().getDirectory(), JGitFlowConstants.GITFLOW_DIR);
File mergeBase = new File(flowDir, JGitFlowConstants.MERGE_BASE);
if(mergeBase.exists())
{
if(GitHelper.workingTreeIsClean(git))
{
//check to see if the merge was done
String finishBase = Files.readFirstLine(mergeBase, Charset.forName("UTF-8"));
if(GitHelper.isMergedInto(git, prefixedBranchName, finishBase))
{
mergeBase.delete();
cleanupBranch(prefixedBranchName);
return null;
}
else
{
mergeBase.delete();
}
}
else
{
throw new MergeConflictsNotResolvedException("Merge conflicts are not resolved");
}
}
//not restoring a merge, continue
requireCleanWorkingTree();
boolean remoteFeatureExists = GitHelper.remoteBranchExists(git,prefixedBranchName);
//update from remote if needed
if(remoteFeatureExists && fetchDevelop)
{
RefSpec branchSpec = new RefSpec("+" + Constants.R_HEADS + prefixedBranchName + ":" + Constants.R_REMOTES + "origin/" + prefixedBranchName);
RefSpec developSpec = new RefSpec("+" + Constants.R_HEADS + gfConfig.getDevelop() + ":" + Constants.R_REMOTES + "origin/" + gfConfig.getDevelop());
git.fetch().setRefSpecs(branchSpec).call();
git.fetch().setRefSpecs(developSpec).call();
}
//make sure nothing is behind
if(remoteFeatureExists)
{
requireLocalBranchNotBehindRemote(prefixedBranchName);
}
if(GitHelper.remoteBranchExists(git,gfConfig.getDevelop()))
{
requireLocalBranchNotBehindRemote(gfConfig.getDevelop());
}
if(rebase)
{
FeatureRebaseCommand rebaseCommand = new FeatureRebaseCommand(branchName,git,gfConfig);
rebaseCommand.call();
}
//merge into base
git.checkout().setName(gfConfig.getDevelop()).call();
Ref featureBranch = GitHelper.getLocalBranch(git, prefixedBranchName);
RevCommit developCommit = GitHelper.getLatestCommit(git,gfConfig.getDevelop());
RevCommit featureCommit = GitHelper.getLatestCommit(git, prefixedBranchName);
List<RevCommit> commitList = ImmutableList.copyOf(git.log().setMaxCount(2).addRange(developCommit,featureCommit).call());
MergeResult mergeResult = null;
if(commitList.size() < 2)
{
mergeResult = git.merge().setFastForward(MergeCommand.FastForwardMode.FF).include(featureBranch).call();
}
else
{
if(squash)
{
mergeResult = git.merge().setSquash(true).include(featureBranch).call();
git.commit().call();
this.forceDeleteBranch = true;
}
else
{
mergeResult = git.merge().setFastForward(MergeCommand.FastForwardMode.NO_FF).include(featureBranch).call();
}
}
if(null == mergeResult || mergeResult.getMergeStatus().equals(MergeResult.MergeStatus.FAILED) || mergeResult.getMergeStatus().equals(MergeResult.MergeStatus.CONFLICTING))
{
Files.createParentDirs(mergeBase);
Files.touch(mergeBase);
Files.write(gfConfig.getDevelop(),mergeBase,Charset.forName("UTF-8"));
throw new MergeConflictsNotResolvedException("merge conflicts exist, please resolve!");
}
cleanupBranch(prefixedBranchName);
return null;
}
private void cleanupBranch(String branch) throws JGitFlowException, GitAPIException
{
requireLocalBranchExists(branch);
requireCleanWorkingTree();
//make sure we're on the develop branch
git.checkout().setName(gfConfig.getDevelop()).call();
//delete the branch
if(fetchDevelop)
{
RefSpec spec = new RefSpec(":" + Constants.R_HEADS + branch);
git.push().setRemote("origin").setRefSpecs(spec).call();
}
if(!keepBranch)
{
if(forceDeleteBranch)
{
git.branchDelete().setForce(true).setBranchNames(branch).call();
}
else
{
git.branchDelete().setForce(false).setBranchNames(branch).call();
}
}
}
public FeatureFinishCommand setFetchDevelop(boolean fetch)
{
this.fetchDevelop = fetch;
return this;
}
public FeatureFinishCommand setRebase(boolean rebase)
{
this.rebase = rebase;
return this;
}
public FeatureFinishCommand setKeepBranch(boolean keep)
{
this.keepBranch = keep;
return this;
}
public FeatureFinishCommand setForceDeleteBranch(boolean force)
{
this.forceDeleteBranch = force;
return this;
}
public FeatureFinishCommand setSquash(boolean squash)
{
this.squash = squash;
return this;
}
}