/jgit-flow-core/src/main/java/com/atlassian/jgitflow/core/ReleaseFinishCommand.java
Java | 329 lines | 173 code | 38 blank | 118 comment | 17 complexity | f9288692819937d126cc9f53c281691c MD5 | raw file
- package com.atlassian.jgitflow.core;
- import com.atlassian.jgitflow.core.exception.*;
- import com.atlassian.jgitflow.core.util.GitHelper;
- 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.ObjectId;
- import org.eclipse.jgit.lib.Ref;
- import org.eclipse.jgit.merge.MergeStrategy;
- import org.eclipse.jgit.revwalk.RevCommit;
- import org.eclipse.jgit.transport.RefSpec;
- import org.eclipse.jgit.util.StringUtils;
- import static com.atlassian.jgitflow.core.util.Preconditions.checkState;
- /**
- * Finish a release.
- * <p>
- * This will merge the release into both master and develop and create a tag for the release
- * </p>
- * <p>
- * Examples (<code>flow</code> is a {@link JGitFlow} instance):
- * <p>
- * Finish a release:
- *
- * <pre>
- * flow.releaseFinish("1.0").call();
- * </pre>
- * <p>
- * Don't delete the local release branch
- *
- * <pre>
- * flow.releaseFinish("1.0").setKeepBranch(true).call();
- * </pre>
- * <p>
- * Squash all commits on the release branch into one before merging
- *
- * <pre>
- * flow.releaseFinish("1.0").setSquash(true).call();
- * </pre>
- * <p>
- * Push changes to the remote origin
- *
- * <pre>
- * flow.releaseFinish("1.0").setPush(true).call();
- * </pre>
- * <p>
- * Don't create a tag for the release
- *
- * <pre>
- * flow.releaseFinish("1.0").setNoTag(true).call();
- * </pre>
- */
- public class ReleaseFinishCommand extends AbstractGitFlowCommand<ReleaseMergeResult>
- {
- private final String releaseName;
- private boolean fetch;
- private String message;
- private boolean push;
- private boolean keepBranch;
- private boolean noTag;
- private boolean squash;
- private ExternalAction preDevelopMergeAction;
- /**
- * Create a new release finish command instance.
- * <p></p>
- * An instance of this class is usually obtained by calling {@link JGitFlow#releaseFinish(String)}
- * @param releaseName The name/version of the release
- * @param git The git instance to use
- * @param gfConfig The GitFlowConfiguration to use
- */
- public ReleaseFinishCommand(String releaseName, Git git, GitFlowConfiguration gfConfig)
- {
- super(git, gfConfig);
- checkState(!StringUtils.isEmptyOrNull(releaseName));
- this.releaseName = releaseName;
- this.fetch = false;
- this.message = "tagging release " + releaseName;
- this.push = false;
- this.keepBranch = false;
- this.noTag = false;
- this.squash = false;
- this.preDevelopMergeAction = null;
- }
- /**
- *
- * @return nothing
- * @throws JGitFlowGitAPIException
- * @throws LocalBranchMissingException
- * @throws DirtyWorkingTreeException
- * @throws JGitFlowIOException
- * @throws BranchOutOfDateException
- */
- @Override
- public ReleaseMergeResult call() throws JGitFlowGitAPIException, LocalBranchMissingException, DirtyWorkingTreeException, JGitFlowIOException, BranchOutOfDateException, ExternalActionFailedException
- {
- String prefixedReleaseName = gfConfig.getPrefixValue(JGitFlowConstants.PREFIXES.RELEASE.configKey()) + releaseName;
- requireLocalBranchExists(prefixedReleaseName);
- requireCleanWorkingTree();
- MergeResult masterResult = new MergeResult(null,null,new ObjectId[] { null, null }, MergeResult.MergeStatus.ALREADY_UP_TO_DATE,MergeStrategy.RESOLVE,null);
- try
- {
- if (fetch)
- {
- RefSpec developSpec = new RefSpec("+" + Constants.R_HEADS + gfConfig.getDevelop() + ":" + Constants.R_REMOTES + "origin/" + gfConfig.getDevelop());
- RefSpec masterSpec = new RefSpec("+" + Constants.R_HEADS + gfConfig.getMaster() + ":" + Constants.R_REMOTES + "origin/" + gfConfig.getMaster());
- git.fetch().setRemote(Constants.DEFAULT_REMOTE_NAME).setRefSpecs(masterSpec).call();
- git.fetch().setRemote(Constants.DEFAULT_REMOTE_NAME).setRefSpecs(developSpec).call();
- git.fetch().setRemote(Constants.DEFAULT_REMOTE_NAME).call();
- }
- if (GitHelper.remoteBranchExists(git, gfConfig.getMaster()))
- {
- requireLocalBranchNotBehindRemote(gfConfig.getMaster());
- }
- if (GitHelper.remoteBranchExists(git, gfConfig.getDevelop()))
- {
- requireLocalBranchNotBehindRemote(gfConfig.getDevelop());
- }
- Ref releaseBranch = GitHelper.getLocalBranch(git, prefixedReleaseName);
- RevCommit releaseCommit = GitHelper.getLatestCommit(git, prefixedReleaseName);
-
- /*
- try to merge into master
- in case a previous attempt to finish this release branch has failed,
- but the merge into master was successful, we skip it now
- */
- if (!GitHelper.isMergedInto(git, prefixedReleaseName, gfConfig.getMaster()))
- {
- git.checkout().setName(gfConfig.getMaster()).call();
- if (squash)
- {
- masterResult = git.merge().setSquash(true).include(releaseBranch).call();
- git.commit().call();
- }
- else
- {
- masterResult = git.merge().setFastForward(MergeCommand.FastForwardMode.NO_FF).include(releaseBranch).call();
- }
- }
- if (!noTag)
- {
- /*
- try to tag the release
- in case a previous attempt to finish this release branch has failed,
- but the tag was successful, we skip it now
- */
- String tagName = gfConfig.getPrefixValue(JGitFlowConstants.PREFIXES.VERSIONTAG.configKey()) + releaseName;
- if (!GitHelper.tagExists(git, tagName))
- {
- git.tag().setName(tagName).setMessage(message).setObjectId(releaseCommit).call();
- }
- }
-
- /**
- * if we have specified an action to carry out before the merge to develop, do it now
- */
- if (preDevelopMergeAction != null) {
- preDevelopMergeAction.call();
- }
-
- /**
- * reload the release branch in case it has changed
- */
- releaseBranch = GitHelper.getLocalBranch(git, prefixedReleaseName);
-
-
-
- /*
- try to merge into develop
- in case a previous attempt to finish this release branch has failed,
- but the merge into develop was successful, we skip it now
- */
- MergeResult developResult = new MergeResult(null,null,new ObjectId[] { null, null }, MergeResult.MergeStatus.ALREADY_UP_TO_DATE, MergeStrategy.RESOLVE,null);
-
- if (!GitHelper.isMergedInto(git, prefixedReleaseName, gfConfig.getDevelop()))
- {
- git.checkout().setName(gfConfig.getDevelop()).call();
- if (squash)
- {
- developResult = git.merge().setSquash(true).include(releaseBranch).call();
- git.commit().call();
- }
- else
- {
- developResult = git.merge().setFastForward(MergeCommand.FastForwardMode.NO_FF).include(releaseBranch).call();
- }
- }
- if (push)
- {
- //push to develop
- RefSpec developSpec = new RefSpec(gfConfig.getDevelop());
- git.push().setRemote(Constants.DEFAULT_REMOTE_NAME).setRefSpecs(developSpec).call();
- //push to master
- RefSpec masterSpec = new RefSpec(gfConfig.getMaster());
- git.push().setRemote(Constants.DEFAULT_REMOTE_NAME).setRefSpecs(masterSpec).call();
- if (!noTag)
- {
- git.push().setRemote(Constants.DEFAULT_REMOTE_NAME).setPushTags().call();
- }
- if (GitHelper.remoteBranchExists(git, prefixedReleaseName))
- {
- RefSpec branchSpec = new RefSpec(prefixedReleaseName);
- git.push().setRemote(Constants.DEFAULT_REMOTE_NAME).setRefSpecs(branchSpec).call();
- }
- }
- if (!keepBranch)
- {
- git.checkout().setName(gfConfig.getDevelop()).call();
- git.branchDelete().setForce(true).setBranchNames(prefixedReleaseName).call();
- if (push && GitHelper.remoteBranchExists(git, prefixedReleaseName))
- {
- RefSpec deleteSpec = new RefSpec(":" + Constants.R_HEADS + prefixedReleaseName);
- git.push().setRemote(Constants.DEFAULT_REMOTE_NAME).setRefSpecs(deleteSpec).call();
- }
- }
- git.checkout().setName(gfConfig.getDevelop()).call();
- return new ReleaseMergeResult(masterResult,developResult);
- }
- catch (GitAPIException e)
- {
- throw new JGitFlowGitAPIException(e);
- }
-
- }
-
- /**
- * Set an action to perform between the merge to master and the merge to develop
- * @param action the action to perform.
- * @return {@code this}
- */
- public ReleaseFinishCommand setPreDevelopMergeAction(ExternalAction action) {
- this.preDevelopMergeAction = action;
- return this;
- }
- /**
- * Set whether to perform a git fetch of the remote branches before doing the merge
- * @param fetch
- * <code>true</code> to do the fetch, <code>false</code>(default) otherwise
- * @return {@code this}
- */
- public ReleaseFinishCommand setFetch(boolean fetch)
- {
- this.fetch = fetch;
- return this;
- }
- /**
- * Set the commit message for the tag creation
- * @param message
- * @return {@code this}
- */
- public ReleaseFinishCommand setMessage(String message)
- {
- this.message = message;
- return this;
- }
- /**
- * Set whether to push the changes to the remote repository
- * @param push
- * <code>true</code> to do the push, <code>false</code>(default) otherwise
- * @return {@code this}
- */
- public ReleaseFinishCommand setPush(boolean push)
- {
- this.push = push;
- return this;
- }
- /**
- * Set whether to keep the local release branch after the merge
- * @param keepBranch
- * <code>true</code> to keep the branch, <code>false</code>(default) otherwise
- * @return {@code this}
- */
- public ReleaseFinishCommand setKeepBranch(boolean keepBranch)
- {
- this.keepBranch = keepBranch;
- return this;
- }
- /**
- * Set whether to turn off tagging
- * @param noTag
- * <code>true</code> to turn off tagging, <code>false</code>(default) otherwise
- * @return {@code this}
- */
- public ReleaseFinishCommand setNoTag(boolean noTag)
- {
- this.noTag = noTag;
- return this;
- }
- /**
- * Set whether to squash all commits into a single commit before the merge
- * @param squash
- * <code>true</code> to squash, <code>false</code>(default) otherwise
- * @return {@code this}
- */
- public ReleaseFinishCommand setSquash(boolean squash)
- {
- this.squash = squash;
- return this;
- }
- }