package com.atlassian.jgitflow.core.command;
import java.util.List;
import com.atlassian.jgitflow.core.GitFlowConfiguration;
import com.atlassian.jgitflow.core.JGitFlowConstants;
import com.atlassian.jgitflow.core.ReleaseMergeResult;
import com.atlassian.jgitflow.core.exception.*;
import com.atlassian.jgitflow.core.extension.HotfixFinishExtension;
import com.atlassian.jgitflow.core.extension.impl.EmptyHotfixFinishExtension;
import com.atlassian.jgitflow.core.extension.impl.MergeProcessExtensionWrapper;
import com.atlassian.jgitflow.core.util.GitHelper;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.MergeResult;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.util.StringUtils;
import static com.atlassian.jgitflow.core.util.Preconditions.checkState;
/**
* Finish a hotfix.
* <p>
* This will merge the hotfix into both master and develop and create a tag for the hotfix
* </p>
* <p></p>
* Examples ({@code flow} is a {@link com.atlassian.jgitflow.core.JGitFlow} instance):
* <p></p>
* Finish a hotfix:
* <p></p>
* <pre>
* flow.hotfixFinish("1.0").call();
* </pre>
* <p></p>
* Don't delete the local hotfix branch
* <p></p>
* <pre>
* flow.hotfixFinish("1.0").setKeepBranch(true).call();
* </pre>
* <p></p>
* Squash all commits on the hotfix branch into one before merging
* <p></p>
* <pre>
* flow.hotfixFinish("1.0").setSquash(true).call();
* </pre>
* <p></p>
* Push changes to the remote origin
* <p></p>
* <pre>
* flow.hotfixFinish("1.0").setPush(true).call();
* </pre>
* <p></p>
* Don't create a tag for the hotfix
* <p></p>
* <pre>
* flow.hotfixFinish("1.0").setNoTag(true).call();
* </pre>
*/
public class HotfixFinishCommand extends AbstractBranchMergingCommand<HotfixFinishCommand, ReleaseMergeResult>
{
private static final String SHORT_NAME = "hotfix-finish";
private String message;
private boolean noTag;
private HotfixFinishExtension extension;
/**
* Create a new hotfix finish command instance.
* <p></p>
* An instance of this class is usually obtained by calling {@link com.atlassian.jgitflow.core.JGitFlow#hotfixFinish(String)}
*
* @param hotfixName The name/version of the hotfix
* @param git The git instance to use
* @param gfConfig The GitFlowConfiguration to use
*/
public HotfixFinishCommand(String hotfixName, Git git, GitFlowConfiguration gfConfig)
{
super(hotfixName, git, gfConfig);
checkState(!StringUtils.isEmptyOrNull(hotfixName));
this.message = "tagging hotfix " + hotfixName;
this.noTag = false;
this.extension = new EmptyHotfixFinishExtension();
}
/**
* @return nothing
* @throws com.atlassian.jgitflow.core.exception.JGitFlowGitAPIException
* @throws com.atlassian.jgitflow.core.exception.LocalBranchMissingException
* @throws com.atlassian.jgitflow.core.exception.DirtyWorkingTreeException
* @throws com.atlassian.jgitflow.core.exception.JGitFlowIOException
* @throws com.atlassian.jgitflow.core.exception.BranchOutOfDateException
*/
@Override
public ReleaseMergeResult call() throws JGitFlowGitAPIException, LocalBranchMissingException, DirtyWorkingTreeException, JGitFlowIOException, BranchOutOfDateException, JGitFlowExtensionException, NotInitializedException
{
String prefixedBranchName = runBeforeAndGetPrefixedBranchName(extension.before(), JGitFlowConstants.PREFIXES.HOTFIX);
enforcer().requireGitFlowInitialized();
enforcer().requireLocalBranchExists(prefixedBranchName);
enforcer().requireCleanWorkingTree(isAllowUntracked());
MergeResult developResult = createEmptyMergeResult();
MergeResult masterResult = createEmptyMergeResult();
MergeResult releaseResult = createEmptyMergeResult();
try
{
doFetchIfNeeded(extension);
ensureLocalBranchesNotBehindRemotes(prefixedBranchName, gfConfig.getMaster(), gfConfig.getDevelop());
//checkout the branch to merge just so we can run any extensions that need to be on this branch
checkoutTopicBranch(prefixedBranchName, extension);
//first merge master
MergeProcessExtensionWrapper masterExtension = new MergeProcessExtensionWrapper(extension.beforeMasterCheckout(), extension.afterMasterCheckout(), extension.beforeMasterMerge(), extension.afterMasterMerge());
masterResult = doMerge(prefixedBranchName, gfConfig.getMaster(), masterExtension);
//now, tag master
if (!noTag && masterResult.getMergeStatus().isSuccessful())
{
doTag(gfConfig.getMaster(), message, masterResult, extension);
}
//IMPORTANT: we need to back-merge master into develop so that git describe works properly
MergeProcessExtensionWrapper developExtension = new MergeProcessExtensionWrapper(extension.beforeDevelopCheckout(), extension.afterDevelopCheckout(), extension.beforeDevelopMerge(), extension.afterDevelopMerge());
developResult = doMerge(gfConfig.getMaster(), gfConfig.getDevelop(), developExtension);
boolean mergeSuccess = checkMergeResults(masterResult, developResult);
if (mergeSuccess)
{
doPushIfNeeded(extension, !noTag, gfConfig.getDevelop(), gfConfig.getMaster(), prefixedBranchName);
}
//Backmerge to release branch if needed
if (releaseBranchExists())
{
String releaseBranchName = getReleaseBranchName();
MergeProcessExtensionWrapper releaseExtension = new MergeProcessExtensionWrapper(extension.beforeReleaseCheckout(), extension.afterReleaseCheckout(), extension.beforeReleaseMerge(), extension.afterReleaseMerge());
releaseResult = doMerge(gfConfig.getMaster(), releaseBranchName, releaseExtension);
boolean releaseMergeSuccess = checkMergeResults(releaseResult);
if (releaseMergeSuccess)
{
doPushIfNeeded(extension, !noTag, releaseBranchName);
}
}
if (mergeSuccess)
{
cleanupBranchesIfNeeded(gfConfig.getDevelop(), prefixedBranchName);
}
reporter.infoText(getCommandName(), "checking out '" + gfConfig.getDevelop() + "'");
git.checkout().setName(gfConfig.getDevelop()).call();
runExtensionCommands(extension.after());
return new ReleaseMergeResult(masterResult, developResult);
}
catch (GitAPIException e)
{
throw new JGitFlowGitAPIException(e);
}
finally
{
reporter.endCommand();
reporter.flush();
}
}
private boolean releaseBranchExists() throws JGitFlowGitAPIException
{
boolean exists = false;
List<Ref> branches = GitHelper.listBranchesWithPrefix(git, gfConfig.getPrefixValue(JGitFlowConstants.PREFIXES.RELEASE.configKey()));
if (!branches.isEmpty())
{
exists = true;
}
return exists;
}
private String getReleaseBranchName() throws JGitFlowGitAPIException
{
String branchName = "";
List<Ref> branches = GitHelper.listBranchesWithPrefix(git, gfConfig.getPrefixValue(JGitFlowConstants.PREFIXES.RELEASE.configKey()));
if (!branches.isEmpty())
{
branchName = branches.get(0).getName();
}
return branchName;
}
/**
* Set the commit message for the tag creation
*
* @param message
* @return {@code this}
*/
public HotfixFinishCommand setMessage(String message)
{
this.message = message;
return this;
}
/**
* Set whether to turn off tagging
*
* @param noTag {@code true} to turn off tagging, {@code false}(default) otherwise
* @return {@code this}
*/
public HotfixFinishCommand setNoTag(boolean noTag)
{
this.noTag = noTag;
return this;
}
public HotfixFinishCommand setExtension(HotfixFinishExtension extension)
{
this.extension = extension;
return this;
}
@Override
protected String getCommandName()
{
return SHORT_NAME;
}
}