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

com.telenav.cactus.maven.ForkBuildMojo Maven / Gradle / Ivy

There is a newer version: 1.5.49
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// © 2011-2022 Telenav, Inc.
//
// 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
//
// https://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.telenav.cactus.maven;

import com.telenav.cactus.git.Branches;
import com.telenav.cactus.git.Branches.Branch;
import com.telenav.cactus.git.GitCheckout;
import com.telenav.cactus.maven.log.BuildLog;
import com.telenav.cactus.maven.mojobase.BaseMojoGoal;
import com.telenav.cactus.maven.mojobase.ScopedCheckoutsMojo;
import com.telenav.cactus.maven.shared.SharedData;
import com.telenav.cactus.maven.shared.SharedDataKey;
import com.telenav.cactus.maven.tree.ProjectTree;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
import javax.inject.Inject;
import org.apache.maven.plugins.annotations.InstantiationStrategy;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;

import static com.telenav.cactus.maven.common.CactusCommonPropertyNames.PUSH;
import static java.lang.System.currentTimeMillis;

/**
 * Performs the first steps of attempting to automatically merge a development
 * or feature branch into a stable branch - given a branch to merge, it will
 * create a new temporary branch from the stable branch, and merge the
 * development branch into it, failing if the merge creates conflicts.
 * 

* If it succeeds, the build will proceed, and MergeToBranchMojo (which shares * data with this one) can be configured as a packaging step (at any step after * tests run, really) to merge the temporary branches into the stable branch. *

* The use case here is continuous builds which are set up to maintain a * "stable" branch, know about some set of "team" or feature branches, which * automatically update the stable branch from those branches if they are * mergeable and all tests pass, so developers working on other * branches/features have a stable source to merge from which incorporates the * work of their colleagues, and reduce the frequency/severity of "big bang" * merges. *

* To do that, you will want to set up your pom files with a profile that * executes this mojo on the validate phase (or some very early phase in the * build), and execute the MergeToBranchMojo afterwards (which will never run if * the build fails). *

* * @author Tim Boudreau */ @org.apache.maven.plugins.annotations.Mojo( defaultPhase = LifecyclePhase.VALIDATE, requiresDependencyResolution = ResolutionScope.NONE, instantiationStrategy = InstantiationStrategy.KEEP_ALIVE, name = "attempt-merge", threadSafe = true) @BaseMojoGoal("attempt-merge") public class ForkBuildMojo extends ScopedCheckoutsMojo { static final SharedDataKey TEMP_BRANCH_KEY = SharedDataKey.of("tempBranch", String.class); static final SharedDataKey BRANCHED_REPOS_KEY = SharedDataKey.of(GitCheckout[].class); static final SharedDataKey TARGET_BRANCH_KEY = SharedDataKey.of("mergeTo", String.class); @Inject SharedData sharedData; /** * The stable branch to merge into. */ @Parameter(property = "cactus.stable-branch", defaultValue = "develop") private String stableBranch; /** * The branch to merge into the stable branch if the build succeeds. */ @Parameter(property = "cactus.merge-branch", required = true) private String mergeBranch; /** * If true, merge and push to the remote stable branch on success. */ @Parameter(property = PUSH, defaultValue = "false") private boolean push; /** * If true, allow some checkouts not to have a branch with the stable-branch * name, and simply do not move them to a branch - this is primarily for * testing. */ @Parameter(property = "cactus.ignore-no-stable-branch", defaultValue = "true") private boolean ignoreNoStableBranch; /** * If true, perform a git fetch --all on each repository before * checking for and attempting to create branches. Continuous builds will * want this always set to true, but it can slow things down for local * testing of changes to this mojo. */ @Parameter(property = "cactus.fetch-first", defaultValue = "false") private boolean fetchFirst; private String tempBranch; public ForkBuildMojo() { // This needs to run before the build, so the build builds the right // thing super(true); new Exception("Create fork build mojo").printStackTrace(System.out); } @Override protected void execute(BuildLog log, MavenProject project, GitCheckout myCheckout, ProjectTree tree, List checkouts) throws Exception { if (fetchFirst) { fetchAll(checkouts, log.child("fetch")); } // Use LinkedHashSet - our checkout list is pre-sorted deepest-to-shallowest // and we want to maintain that sort order. Map branchesToCreateFrom = new LinkedHashMap<>(); Map branchesToMerge = new LinkedHashMap<>(); for (GitCheckout checkout : checkouts) { Branches branches = tree.branches(checkout); branches.find(stableBranch, false).ifPresent(br -> { branchesToCreateFrom.put(checkout, br); }); branches.find(mergeBranch, true).ifPresentOrElse(br -> { branchesToMerge.put(checkout, br); }, () -> { branches.find(mergeBranch, false).ifPresent(remoteBranch -> { branchesToMerge.put(checkout, remoteBranch); }); }); } if (!ignoreNoStableBranch && !branchesToCreateFrom.keySet().equals( new HashSet<>(checkouts))) { Set absent = new HashSet<>(checkouts); absent.removeAll(branchesToCreateFrom.keySet()); fail("Not all checkouts have a stable branch named '" + stableBranch + "': " + absent); } if (branchesToMerge.isEmpty()) { // Should this fail, or just end execution? fail("Did not find any branches named '" + mergeBranch + "' in " + checkouts); } ifVerbose(() -> { log.warn("Workflow for " + scope() + ":"); branchesToCreateFrom.forEach((co, br) -> { Branch merge = branchesToMerge.get(co); if (merge != null) { log.warn( co.name() + ":\tcreate " + tempBranch + " merging " + mergeBranch + " into " + stableBranch); } }); }); for (Map.Entry e : branchesToCreateFrom.entrySet()) { Branch base = e.getValue(); GitCheckout checkout = e.getKey(); log.info("Create and switch to " + tempBranch + " in " + checkout .name()); boolean success = checkout.createAndSwitchToBranch(tempBranch, Optional.of(base.trackingName()), isPretend()); if (!success) { fail("Failed to create and switch to " + tempBranch + " using " + base); } } List successfullyMerged = new ArrayList<>(); for (Map.Entry e : branchesToMerge.entrySet()) { Branch target = e.getValue(); GitCheckout checkout = e.getKey(); log.info( "Merge " + target + " into " + tempBranch + " for " + checkout .name()); if (!isPretend()) { if (checkout.merge(target.trackingName())) { successfullyMerged.add(checkout); } else { // fail("Failed to merge " + target + " into " + tempBranch); log .warn("Failed to merge " + target + " into " + tempBranch); } } } sharedData.put(BRANCHED_REPOS_KEY, successfullyMerged.toArray( GitCheckout[]::new)); } @Override protected void onValidateParameters(BuildLog log, MavenProject project) throws Exception { validateBranchName(mergeBranch, false); validateBranchName(stableBranch, false); tempBranch = mergeBranch + "_" + stableBranch + "_" + Long.toString(currentTimeMillis(), 36) + "_" + Long .toString(ThreadLocalRandom.current().nextLong(), 36); sharedData.put(TEMP_BRANCH_KEY, tempBranch); sharedData.put(TARGET_BRANCH_KEY, stableBranch); log.info("Temporary branch name " + tempBranch); } private void fetchAll(List checkouts, BuildLog log) { for (GitCheckout checkout : checkouts) { log.info("Fetch all in " + checkout.loggingName()); if (!isPretend()) { checkout.fetchAll(); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy