package org.paylogic.jenkins.upmerge; import hudson.EnvVars; import hudson.Extension; import hudson.Launcher; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.BuildListener; import hudson.tasks.BuildStepDescriptor; import hudson.tasks.Builder; import hudson.util.FormValidation; import lombok.extern.java.Log; import net.sf.json.JSONObject; import org.apache.commons.lang.StringUtils; import org.jenkinsci.plugins.envinject.EnvInjectBuilderContributionAction; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; import org.paylogic.jenkins.LogMessageSearcher; import org.paylogic.jenkins.advancedscm.AdvancedSCMManager; import org.paylogic.jenkins.advancedscm.SCMManagerFactory; import org.paylogic.jenkins.upmerge.releasebranch.ReleaseBranch; import java.io.PrintStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; /** * UpmergeBuilder! */ @Log public class UpmergeBuilder extends Builder { public final String commitUsername; @DataBoundConstructor public UpmergeBuilder(String commitUsername) { this.commitUsername = commitUsername; } /** * Here we should do upmerging. * * So: * - Fetch case info using CASE_ID parameter (should be given). * - NOT !! Create 'ReleaseBranch' object from case info and a nextBranch object which is releasebranch.copy().next(); * - Create ReleaseBranch object from current branch, we may expect that the GatekeeperPlugin set the correct one. * - Initiate UpMerge sequence.... * - Try to pull new code from nextBranch.getNext(); * - Try to merge this new code with releaseBranch(); * - Commit this shiny new code. * - Set a flag somewhere, indicating that this upmerge has been done. * - Repeat UpMerge sequence for next releases until there are no moar releases. * - In some post-build thingy, push these new branches if all went well. * - We SHOULD not have to do any cleanup actions, because workspace is updated every build. * - Rely on the FogbugzPlugin (dependency, see pom.xml) to do reporting of our upmerges. * - Trigger new builds on all branches that have been merged. */ @Override public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) { PrintStream l = listener.getLogger(); l.println("----------------------------------------------------------"); l.println("--------------------- Now Upmerging ----------------------"); l.println("----------------------------------------------------------"); try { return this.doPerform(build, launcher, listener); } catch (Exception e) { log.log(Level.SEVERE, "Exception during Gatekeeepring.", e); l.append("Exception occured, build aborting...\n"); LogMessageSearcher.logMessage(listener, e.toString()); return false; } } private boolean doPerform(AbstractBuild build, Launcher launcher, BuildListener listener) throws Exception { PrintStream l = listener.getLogger(); EnvVars envVars = build.getEnvironment(listener); String featureBranch = envVars.get("FEATURE_BRANCH", ""); String targetBranch = envVars.get("TARGET_BRANCH", ""); int usableCaseId = 0; String givenCaseId = envVars.get("CASE_ID", ""); if (givenCaseId != "") { usableCaseId = Integer.parseInt(givenCaseId); } /* Get branch name using AdvancedSCMManager, which we'll need later on as well. */ AdvancedSCMManager amm = SCMManagerFactory.getManager(build, launcher, listener); /* Get a ReleaseBranch compatible object to bump release branch versions with. */ ReleaseBranch releaseBranch = amm.getReleaseBranch(targetBranch); String releaseBranchName = releaseBranch.getName(); /* Do actual upmerging in this loop, until we can't upmerge no more. Will not attempt to Upmerge to branches that were not in the repo branch list. */ // Pull to also get new releases created during tests. amm.pull(null, targetBranch); amm.update(""); amm.mergeHeads("[Jenkins Upmerging] Merged heads on " + releaseBranchName, commitUsername); List<String> branchList = amm.getBranchNames(true); List<String> branchesToPush = new ArrayList<String>(); branchesToPush.add(targetBranch); if (branchList.contains(featureBranch)) { // can be not a branch, but a bookmark branchesToPush.add(featureBranch); } ReleaseBranch nextBranch = releaseBranch.copy(); nextBranch.next(branchList); String nextBranchName = nextBranch.getName(); while(nextBranchName != releaseBranchName) { amm.mergeWorkspaceWith(releaseBranchName, nextBranchName); amm.commit("[Jenkins Upmerging] Merged " + releaseBranchName + " into " + nextBranchName, commitUsername); amm.mergeHeads("[Jenkins Upmerging] Merged heads on " + nextBranchName, commitUsername); LogMessageSearcher.logMessage( listener, "Upmerged " + releaseBranchName + " into " + nextBranchName + "."); branchesToPush.add(nextBranchName); // Bump releases releaseBranch.next(branchList); releaseBranchName = releaseBranch.getName(); nextBranch.next(branchList); nextBranchName = nextBranch.getName(); } // pass branches to push to later build actions Map<String, String> vars = new HashMap<String, String>(); vars.put("BRANCHES_TO_PUSH", StringUtils.join(branchesToPush, ",")); build.addAction(new EnvInjectBuilderContributionAction(vars)); return true; } public DescriptorImpl getDescriptor() { return (DescriptorImpl)super.getDescriptor(); } /** * Descriptor for {@link UpmergeBuilder}. Used as a singleton. Stores global UpmergePlugin settings. */ @Extension public static final class DescriptorImpl extends BuildStepDescriptor<Builder> { public DescriptorImpl() throws Exception { super(); load(); } public boolean isApplicable(Class<? extends AbstractProject> aClass) { return true; } public String getDisplayName() { return "Perform Upmerging of release branches."; } @Override public boolean configure(StaplerRequest req, JSONObject formData) throws FormException { save(); return super.configure(req,formData); } public FormValidation doCheckCommitUsername(@QueryParameter String value) { if (!value.isEmpty()) { return FormValidation.ok(); } else { return FormValidation.error("Required field"); } } } }