Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.telenav.cactus.maven.MergeBranchMojo Maven / Gradle / Ivy
Go to download
The cactus-maven-plugin project codifies Telenav Open Source processes.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// © 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.mastfrog.function.throwing.ThrowingTriConsumer;
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.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 org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import static com.telenav.cactus.maven.common.CactusCommonPropertyNames.DEFAULT_DEVELOPMENT_BRANCH;
import static com.telenav.cactus.maven.common.CactusCommonPropertyNames.PUSH;
import static org.apache.maven.plugins.annotations.InstantiationStrategy.SINGLETON;
import static org.apache.maven.plugins.annotations.LifecyclePhase.VALIDATE;
import static org.apache.maven.plugins.annotations.ResolutionScope.NONE;
/**
*
* @author Tim Boudreau
*/
@org.apache.maven.plugins.annotations.Mojo(
defaultPhase = VALIDATE,
requiresDependencyResolution = NONE,
instantiationStrategy = SINGLETON,
name = "merge", threadSafe = true)
@BaseMojoGoal("merge")
public class MergeBranchMojo extends ScopedCheckoutsMojo
{
/**
* Name of the branch to merge from - if unset, uses the current
* branch.
*/
@Parameter(property = "cactus.merge.from")
String mergeFrom;
/**
* The branch to merge into - the default is "develop".
*/
@Parameter(property = "cactus.merge.into", defaultValue = DEFAULT_DEVELOPMENT_BRANCH)
String mergeInto;
/**
* A second branch to merge into - e.g. merge a release branch back to
* "develop" but first also merge it into release/current.
*/
@Parameter(property = "cactus.also.merge.into")
String alsoMergeInto;
/**
* Delete the merged branch after a successful merge.
*/
@Parameter(property = "cactus.delete.merged.branch", defaultValue = "false")
boolean deleteMergedBranch;
/**
* If true, create a git tag named from the latter portion of the target
* branch name following any / character, if the merge succeeds.
*/
@Parameter(property = "cactus.tag", defaultValue = "true")
boolean tag;
/**
* If true, push on success.
*/
@Parameter(property = PUSH, defaultValue = "false")
boolean push;
@Override
protected void onValidateParameters(BuildLog log, MavenProject project)
throws Exception
{
validateBranchName(mergeFrom, true);
validateBranchName(mergeInto, false);
validateBranchName(alsoMergeInto, true);
switch (scope())
{
case FAMILY:
break;
default:
if (families().isEmpty())
{
fail("Cannot use cactus.families exception cactus.scope=family");
}
}
}
@Override
protected void execute(BuildLog log, MavenProject project,
GitCheckout myCheckout, ProjectTree tree,
List checkouts) throws Exception
{
// Pending: We could use git merge-tree, scanning for conflict
// markers in the diff it emits, to detect if a merge will fail.
// For what this mojo will be used for, that is likely to be rare.
//
// Get maps of GitCheckout:Branch for from, to and also
withBranches(tree, checkouts, (toMergeFrom, toMergeTo, alsoMergeTo) ->
{
if (toMergeFrom.isEmpty() || toMergeTo.isEmpty())
{
log.warn("No checkouts found to merge");
return;
}
log.info("Have " + toMergeFrom.size() + " checkouts to merge.");
Set checkoutsToMerge = new HashSet<>(toMergeTo.keySet());
checkoutsToMerge.retainAll(toMergeFrom.keySet());
// Do the thing, one repo at a time:
for (GitCheckout checkout : checkoutsToMerge)
{
Branch from = toMergeFrom.get(checkout);
Branch to = toMergeTo.get(checkout);
Branch also = alsoMergeTo.get(checkout);
// If we have secondary branch to merge to, do that first,
// so we leave the repo on the final destination branch
if (also != null)
{
log.info(
"First merge " + from + " into " + also + " in " + checkout
.loggingName());
ifNotPretending(() ->
{
if (also.isRemote())
{
log.info(
"Branch " + also.trackingName() + " does not exist "
+ "locally. Creating it.");
checkout.createAndSwitchToBranch(also.name(),
Optional.of(also.trackingName()));
}
else
{
checkout.switchToBranch(also.name());
}
checkout.merge(from.name());
});
if (push)
{
log.info("Push " + checkout.loggingName());
ifNotPretending(checkout::push);
}
}
log.info("Merge " + from + " into " + to + " in " + checkout
.loggingName());
ifNotPretending(() ->
{
// Get on the target branch
checkout.switchToBranch(to.name());
// Do the merge
checkout.merge(from.name());
});
if (tag)
{
// Strip any leading feature/ or whatever from the branch name
String newTag = tagName(from);
log.info(
"Tag " + checkout.loggingName() + " with " + newTag);
ifNotPretending(() ->
{
// Use tag -f to force - this would be a silly thing to
// fail on.
checkout.tag(newTag, true);
});
}
if (deleteMergedBranch)
{
// Nuke it.
log.info("Delete branch " + from + " in " + checkout
.loggingName());
ifNotPretending(() ->
{
checkout.deleteBranch(from.name(), to.name(), false);
});
}
if (push)
{
// The target branch may not exists, which requires a
// different push call
boolean remoteBranchExists = tree.branches(checkout).find(to
.name(), false).isPresent();
if (remoteBranchExists)
{
checkout.push();
}
else
{
checkout.pushCreatingBranch();
}
}
}
});
}
private void withBranches(ProjectTree tree, List checkouts,
ThrowingTriConsumer, Map, Map> c)
throws Exception
{
if (isIncludeRoot() && !checkouts.contains(tree.root()))
{
checkouts = new ArrayList<>(checkouts);
checkouts.add(tree.root());
}
try
{
// Collect all our branch info
Map toMergeFrom = new LinkedHashMap<>();
Map toMergeTo = new LinkedHashMap<>();
Map additionalDestinations = new LinkedHashMap<>();
for (GitCheckout checkout : checkouts)
{
Branches branches = tree.branches(checkout);
Optional branchToMergeFrom;
if (mergeFrom != null)
{
// We have an explicit branch
branchToMergeFrom = branches.find(mergeFrom, true);
}
else
{
// Just use whatever the local branch is
branchToMergeFrom = branches.currentBranch();
}
// If something is missing, that just means it was not touched
// in whatever performed the changes
branchToMergeFrom.ifPresent(branch -> toMergeFrom.put(checkout, branch));
branches.find(mergeInto, true).ifPresent(
branchToMergeTo
-> toMergeTo.put(checkout,
branchToMergeTo));
if (alsoMergeInto != null)
{
branches.find(alsoMergeInto, true)
.or(() -> branches.find(alsoMergeInto, false))
.ifPresent(also ->
{
additionalDestinations.put(checkout, also);
});
}
}
// Winnow out those we don't have a branch for on both sides:
Set common = new HashSet<>(toMergeTo.keySet());
common.retainAll(toMergeFrom.keySet());
Set toRemove = new HashSet<>(toMergeTo.keySet());
toRemove.addAll(toMergeFrom.keySet());
toRemove.removeAll(common);
for (GitCheckout rem : toRemove)
{
toMergeFrom.remove(rem);
toMergeTo.remove(rem);
}
toRemove.clear();
// We also may have picked up repos where we would merge a branch
// into itself, if the target branch name was unspecified. Prune
// them too.
toMergeFrom.forEach((repo, branch) ->
{
if (branch.equals(toMergeTo.get(repo)))
{
toRemove.add(repo);
}
});
for (GitCheckout rem : toRemove)
{
toMergeFrom.remove(rem);
toMergeTo.remove(rem);
}
c.accept(toMergeFrom, toMergeTo, additionalDestinations);
}
finally
{
tree.invalidateCache();
}
}
private String tagName(Branch fromBranch)
{
String nm = fromBranch.name();
int ix = nm.lastIndexOf('/');
if (ix > 0 && ix < nm.length() - 1)
{
return nm.substring(ix + 1);
}
return nm;
}
}