All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.amashchenko.maven.plugin.gitflow.GitFlowHotfixFinishMojo Maven / Gradle / Ivy

Go to download

The Git-Flow Maven Plugin supports various Git workflows, including Vincent Driessen's successful Git branching model and GitHub Flow. This plugin runs Git and Maven commands from the command line. Supports Eclipse Plugins build with Tycho.

The newest version!
/*
 * Copyright 2014-2023 Aleksandr Mashchenko.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.amashchenko.maven.plugin.gitflow;

import java.util.HashMap;
import java.util.Map;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.cli.CommandLineException;

/**
 * The git flow hotfix finish mojo.
 *
 */
@Mojo(name = "hotfix-finish", aggregator = true)
public class GitFlowHotfixFinishMojo extends AbstractGitFlowMojo {

    /** Whether to skip tagging the hotfix in Git. */
    @Parameter(property = "skipTag", defaultValue = "false")
    private boolean skipTag = false;

    /** Whether to keep hotfix branch after finish. */
    @Parameter(property = "keepBranch", defaultValue = "false")
    private boolean keepBranch = false;

    /**
     * Whether to skip calling Maven test goal before merging the branch.
     *
     * @since 1.0.5
     */
    @Parameter(property = "skipTestProject", defaultValue = "false")
    private boolean skipTestProject = false;

    /**
     * Whether to push to the remote.
     *
     * @since 1.3.0
     */
    @Parameter(property = "pushRemote", defaultValue = "true")
    private boolean pushRemote;

    /**
     * Maven goals to execute in the hotfix branch before merging into the
     * production or support branch.
     *
     * @since 1.8.0
     */
    @Parameter(property = "preHotfixGoals")
    private String preHotfixGoals;

    /**
     * Maven goals to execute in the release or support branch after the hotfix.
     *
     * @since 1.8.0
     */
    @Parameter(property = "postHotfixGoals")
    private String postHotfixGoals;

    /**
     * Hotfix version to use in non-interactive mode.
     *
     * @since 1.9.0
     */
    @Parameter(property = "hotfixVersion")
    private String hotfixVersion;

    /**
     * Hotfix branch to use in non-interactive mode. Must start with hotfix branch
     * prefix. The hotfixBranch parameter will be used instead of
     * {@link #hotfixVersion} if both are set.
     *
     * @since 1.16.0
     */
    @Parameter(property = "hotfixBranch")
    private String hotfixBranch;

    /**
     * Whether to make a GPG-signed tag.
     *
     * @since 1.9.0
     */
    @Parameter(property = "gpgSignTag", defaultValue = "false")
    private boolean gpgSignTag = false;

    /**
     * Whether to use snapshot in hotfix.
     *
     * @since 1.10.0
     */
    @Parameter(property = "useSnapshotInHotfix", defaultValue = "false")
    private boolean useSnapshotInHotfix;

    /**
     * Whether to skip merging into the production branch.
     *
     * @since 1.12.0
     */
    @Parameter(property = "skipMergeProdBranch", defaultValue = "false")
    private boolean skipMergeProdBranch = false;

    /**
     * Whether to skip merging into the development branch.
     *
     * @since 1.12.0
     */
    @Parameter(property = "skipMergeDevBranch", defaultValue = "false")
    private boolean skipMergeDevBranch = false;

    /**
     * Controls which branch is merged to development branch. If set to
     * true then hotfix branch will be merged to development branch. If
     * set to false and tag is present ({@link #skipTag} is set to
     * false) then tag will be merged. If there is no tag then
     * production branch will be merged to development branch.
     *
     * @since 1.18.0
     */
    @Parameter(property = "noBackMergeHotfix", defaultValue = "false")
    private boolean noBackMergeHotfix = false;

    /** {@inheritDoc} */
    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        validateConfiguration(preHotfixGoals, postHotfixGoals);

        try {
            // check uncommitted changes
            checkUncommittedChanges();

            String hotfixBranchName = null;
            if (settings.isInteractiveMode()) {
                hotfixBranchName = promptBranchName();
            } else if (StringUtils.isNotBlank(hotfixBranch)) {
                if (!hotfixBranch.startsWith(gitFlowConfig.getHotfixBranchPrefix())) {
                    throw new MojoFailureException("The hotfixBranch parameter doesn't start with hotfix branch prefix.");
                }
                if (!gitCheckBranchExists(hotfixBranch)) {
                    throw new MojoFailureException("Hotfix branch with name '" + hotfixBranch + "' doesn't exist. Cannot finish hotfix.");
                }
                hotfixBranchName = hotfixBranch;
            } else if (StringUtils.isNotBlank(hotfixVersion)) {
                final String branch = gitFlowConfig.getHotfixBranchPrefix() + hotfixVersion;
                if (!gitCheckBranchExists(branch)) {
                    throw new MojoFailureException("Hotfix branch with name '" + branch + "' doesn't exist. Cannot finish hotfix.");
                }
                hotfixBranchName = branch;
            }

            if (StringUtils.isBlank(hotfixBranchName)) {
                throw new MojoFailureException("Hotfix branch name to finish is blank.");
            }

            // support branch hotfix
            String supportBranchName = null;
            boolean supportHotfix = hotfixBranchName
                    .startsWith(gitFlowConfig.getHotfixBranchPrefix() + gitFlowConfig.getSupportBranchPrefix());
            // get support branch name w/o version part
            if (supportHotfix) {
                supportBranchName = hotfixBranchName.substring(gitFlowConfig.getHotfixBranchPrefix().length());
                supportBranchName = supportBranchName.substring(0, supportBranchName.lastIndexOf('/'));
            }

            // fetch and check remote
            if (fetchRemote) {
                gitFetchRemoteAndCompareCreate(hotfixBranchName);

                if (supportBranchName != null) {
                    gitFetchRemoteAndCompareCreate(supportBranchName);
                } else {
                    if (notSameProdDevName()) {
                        gitFetchRemoteAndCompareCreate(gitFlowConfig.getDevelopmentBranch());
                    }
                    gitFetchRemoteAndCompareCreate(gitFlowConfig.getProductionBranch());

                    // release branch
                    String remoteReleases = gitFetchAndFindRemoteBranches(gitFlowConfig.getReleaseBranchPrefix(), false);
                    if (StringUtils.isNotBlank(remoteReleases)) {
                        // remove remote name with slash from branch name
                        String remoteRelease = remoteReleases.substring(gitFlowConfig.getOrigin().length() + 1);

                        if (StringUtils.countMatches(remoteRelease, gitFlowConfig.getReleaseBranchPrefix()) > 1) {
                            throw new MojoFailureException("More than one remote release branch exists. Cannot finish hotfix.");
                        }

                        gitFetchRemoteAndCompareCreate(remoteRelease);
                    }
                }
            }

            // git checkout hotfix/...
            gitCheckout(hotfixBranchName);

            if (!skipTestProject) {
                mvnCleanTest();
            }

            // maven goals before merge
            if (StringUtils.isNotBlank(preHotfixGoals)) {
                mvnRun(preHotfixGoals);
            }

            String currentHotfixVersion = getCurrentProjectVersion();

            Map messageProperties = new HashMap<>();
            messageProperties.put("version", currentHotfixVersion);

            if (useSnapshotInHotfix && ArtifactUtils.isSnapshot(currentHotfixVersion)) {
                String commitVersion = currentHotfixVersion.replace("-" + Artifact.SNAPSHOT_VERSION, "");

                mvnSetVersions(commitVersion);

                messageProperties.put("version", commitVersion);

                gitCommit(commitMessages.getHotfixFinishMessage(), messageProperties);
            }

            if (supportBranchName != null) {
                gitCheckout(supportBranchName);
                // git merge --no-ff hotfix/...
                gitMergeNoff(hotfixBranchName, commitMessages.getHotfixFinishSupportMergeMessage(), messageProperties);
            } else if (!skipMergeProdBranch) {
                // git checkout production
                gitCheckout(gitFlowConfig.getProductionBranch());
                // git merge --no-ff hotfix/...
                gitMergeNoff(hotfixBranchName, commitMessages.getHotfixFinishMergeMessage(), messageProperties);
            }

            final String currentVersion = getCurrentProjectVersion();

            final String tagVersion = (tychoBuild || useSnapshotInHotfix) && ArtifactUtils.isSnapshot(currentVersion)
                    ? currentVersion.replace("-" + Artifact.SNAPSHOT_VERSION, "")
                    : currentVersion;
            if (!skipTag) {
                Map properties = new HashMap<>();
                properties.put("version", tagVersion);

                // git tag -a ...
                gitTag(gitFlowConfig.getVersionTagPrefix() + tagVersion,
                        commitMessages.getTagHotfixMessage(), gpgSignTag, properties);
            }

            if (skipMergeProdBranch && (supportBranchName == null)) {
                // switch to production branch so hotfix branch can be deleted
                gitCheckout(gitFlowConfig.getProductionBranch());
            }

            // maven goals after merge
            if (StringUtils.isNotBlank(postHotfixGoals)) {
                mvnRun(postHotfixGoals);
            }

            // check whether release branch exists
            final String releaseBranch = gitFindBranches(gitFlowConfig.getReleaseBranchPrefix(), true);

            if (supportBranchName == null) {
                // if release branch exists merge hotfix changes into it
                if (StringUtils.isNotBlank(releaseBranch)) {
                    // git checkout release
                    gitCheckout(releaseBranch);
                    String releaseBranchVersion = getCurrentProjectVersion();

                    if (!currentVersion.equals(releaseBranchVersion)) {
                        // set version to avoid merge conflict
                        mvnSetVersions(currentVersion);
                        gitCommit(commitMessages.getUpdateReleaseToAvoidConflictsMessage());
                    }

                    messageProperties.put("version", currentVersion);

                    // git merge --no-ff hotfix/...
                    gitMergeNoff(hotfixBranchName, commitMessages.getHotfixFinishReleaseMergeMessage(),
                            messageProperties);

                    if (!currentVersion.equals(releaseBranchVersion)) {
                        mvnSetVersions(releaseBranchVersion);
                        gitCommit(commitMessages.getUpdateReleaseBackPreMergeStateMessage());
                    }
                } else if (!skipMergeDevBranch) {
                    GitFlowVersionInfo developVersionInfo = new GitFlowVersionInfo(
                            currentVersion, getVersionPolicy());
                    if (notSameProdDevName()) {
                        // git checkout develop
                        gitCheckout(gitFlowConfig.getDevelopmentBranch());

                        developVersionInfo = new GitFlowVersionInfo(getCurrentProjectVersion(), getVersionPolicy());

                        // set version to avoid merge conflict
                        mvnSetVersions(currentVersion);
                        gitCommit(commitMessages.getHotfixVersionUpdateMessage());

                        messageProperties.put("version", currentVersion);

                        final String refToMerge;
                        if (skipMergeProdBranch || noBackMergeHotfix) {
                            refToMerge = hotfixBranchName;
                        } else if (!skipTag) {
                            refToMerge = gitFlowConfig.getVersionTagPrefix() + tagVersion;
                        } else {
                            refToMerge = gitFlowConfig.getProductionBranch();
                        }
                        gitMergeNoff(refToMerge, commitMessages.getHotfixFinishDevMergeMessage(), messageProperties);

                        // which version to increment
                        GitFlowVersionInfo hotfixVersionInfo = new GitFlowVersionInfo(
                                currentVersion, getVersionPolicy());
                        if (developVersionInfo
                                .compareTo(hotfixVersionInfo) < 0) {
                            developVersionInfo = hotfixVersionInfo;
                        }
                    }

                    // get next snapshot version
                    final String nextSnapshotVersion = developVersionInfo.getSnapshotVersionString();

                    if (StringUtils.isBlank(nextSnapshotVersion)) {
                        throw new MojoFailureException(
                                "Next snapshot version is blank.");
                    }

                    mvnSetVersions(nextSnapshotVersion);

                    Map properties = new HashMap<>();
                    properties.put("version", nextSnapshotVersion);

                    gitCommit(commitMessages.getHotfixFinishMessage(), properties);
                }
            }

            if (installProject) {
                mvnCleanInstall();
            }

            if (pushRemote) {
                if (supportBranchName != null) {
                    gitPush(supportBranchName, !skipTag);
                } else {
                    gitPush(gitFlowConfig.getProductionBranch(), !skipTag);

                    if (StringUtils.isNotBlank(releaseBranch)) {
                        gitPush(releaseBranch, !skipTag);
                    } else if (StringUtils.isBlank(releaseBranch)
                            && notSameProdDevName()) { // if no release branch
                        gitPush(gitFlowConfig.getDevelopmentBranch(), !skipTag);
                    }
                }

                // push tag
                if (!skipTag && skipMergeDevBranch && skipMergeProdBranch && StringUtils.isBlank(releaseBranch)) {
                    gitPush(gitFlowConfig.getVersionTagPrefix() + tagVersion, false);
                }

                if (!keepBranch) {
                    gitPushDelete(hotfixBranchName);
                }
            }

            if (!keepBranch) {
                if (skipMergeProdBranch) {
                    // force delete as upstream merge is skipped
                    gitBranchDeleteForce(hotfixBranchName);
                } else {
                    // git branch -d hotfix/...
                    gitBranchDelete(hotfixBranchName);
                }
            }
        } catch (Exception e) {
            throw new MojoFailureException("hotfix-finish", e);
        }
    }

    private String promptBranchName() throws MojoFailureException, CommandLineException {
        String hotfixBranches = gitFindBranches(gitFlowConfig.getHotfixBranchPrefix(), false);

        // find hotfix support branches
        if (!gitFlowConfig.getHotfixBranchPrefix().endsWith("/")) {
            String supportHotfixBranches = gitFindBranches(gitFlowConfig.getHotfixBranchPrefix() + "*/*", false);
            hotfixBranches = hotfixBranches + supportHotfixBranches;
        }

        if (StringUtils.isBlank(hotfixBranches)) {
            throw new MojoFailureException("There are no hotfix branches.");
        }

        String[] branches = hotfixBranches.split("\\r?\\n");

        return prompter.prompt(branches, null, "Hotfix branches:", "Choose hotfix branch to finish");
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy