From ff722e4772c5c3e5297e33fe75de3c59471ec299 Mon Sep 17 00:00:00 2001 From: 15knots Date: Fri, 18 Nov 2016 21:54:28 +0100 Subject: [PATCH 01/17] JENKINS-39389 Implementation of jenkins.scm.api.SCMSource --- .../multiplescms/MultiSCMHeadObserver.java | 93 ++++++ .../plugins/multiplescms/MultiSCMSource.java | 272 ++++++++++++++++++ .../multiplescms/MultiSCMSource/config.jelly | 35 +++ 3 files changed, 400 insertions(+) create mode 100644 src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMHeadObserver.java create mode 100644 src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMSource.java create mode 100644 src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCMSource/config.jelly diff --git a/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMHeadObserver.java b/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMHeadObserver.java new file mode 100644 index 0000000..9a221de --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMHeadObserver.java @@ -0,0 +1,93 @@ +/* + * The MIT License + * + * Copyright (c) 2016 Martin Weber + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.jenkinsci.plugins.multiplescms; + +import java.util.HashMap; +import java.util.Map; + +import edu.umd.cs.findbugs.annotations.NonNull; +import jenkins.scm.api.SCMHead; +import jenkins.scm.api.SCMHeadObserver; +import jenkins.scm.api.SCMRevision; +import jenkins.scm.api.SCMSource; + +/** + * @author weber + * + */ +class MultiSCMHeadObserver extends SCMHeadObserver { + + private final Map result; + private Collector currentObserver; + private SCMSource currentSource; + + /** + * + */ + public MultiSCMHeadObserver(int numSources) { + result = new HashMap(numSources, 1.0f); + } + + /** + * Notified before a new SCM source will be observed. Must be invoked prior + * to {@link #observe}. + * + * @param newSource + */ + public void beginObserving(SCMSource newSource) { + currentObserver = new SCMHeadObserver.Collector(); + currentSource = newSource; + } + + /** + * Notified after a SCM source has been successfully observed. Must be + * invoked after to {@link #observe}. + */ + public void endObserving() { + result.put(currentSource, currentObserver); + currentObserver = null; + currentSource = null; + } + + /* + * (non-Javadoc) + * + * @see jenkins.scm.api.SCMHeadObserver#observe(jenkins.scm.api.SCMHead, + * jenkins.scm.api.SCMRevision) + */ + @Override + public void observe(SCMHead head, SCMRevision revision) { + currentObserver.observe(head, revision); + } + + /** + * Returns the collected results. + * + * @return the collected results. + */ + @NonNull + public Map result() { + return result; + } +} diff --git a/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMSource.java b/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMSource.java new file mode 100644 index 0000000..0afbb00 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMSource.java @@ -0,0 +1,272 @@ +/* + * The MIT License + * + * Copyright (c) 2016 Martin Weber + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.jenkinsci.plugins.multiplescms; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; + +import org.kohsuke.stapler.DataBoundConstructor; + +import edu.umd.cs.findbugs.annotations.CheckForNull; +import hudson.Extension; +import hudson.Util; +import hudson.model.Descriptor; +import hudson.model.TaskListener; +import hudson.scm.SCM; +import jenkins.model.Jenkins; +import jenkins.scm.api.SCMHead; +import jenkins.scm.api.SCMHeadObserver; +import jenkins.scm.api.SCMHeadObserver.Collector; +import jenkins.scm.api.SCMRevision; +import jenkins.scm.api.SCMSource; +import jenkins.scm.api.SCMSourceDescriptor; + +/** + * An {@code SCMSource} implementation that monitors and fetches multiple SCMs. + * + * @author Martin Weber + */ +public class MultiSCMSource extends SCMSource { + + private List scmSources; + + @DataBoundConstructor + public MultiSCMSource(@CheckForNull String id, List scmSources) { + super(id); + this.scmSources = Util.fixNull(scmSources); + } + + public List getScmSources() { + return scmSources; + } + + // @DataBoundSetter + public void setScmSources(List scmSources) { + if (scmSources != null) { + this.scmSources.addAll(scmSources); + } + } + + /* + * (non-Javadoc) + * + * @see jenkins.scm.api.SCMSource#retrieve(jenkins.scm.api.SCMHeadObserver, + * hudson.model.TaskListener) + */ + @Override + protected void retrieve(SCMHeadObserver observer, TaskListener listener) throws IOException, InterruptedException { + final MultiSCMHeadObserver multiObserver = new MultiSCMHeadObserver(scmSources.size()); + + listener.getLogger().println("Collecting branches that exist in each SCM..."); + // process all SCMs, but try to not delete existing sub-projects... + boolean retrieveOk = true; + for (SCMSource scmSource : scmSources) { + multiObserver.beginObserving(scmSource); + Method method = getDescriptor().getRetrieveMethod(scmSource.getDescriptor()); + /* + * invoke scmSource.retrieve(observer, listener). Unfortunately, + * that method is not accessible to us, so we use reflection + */ + try { + if (!method.isAccessible()) { + method.setAccessible(true); + } + listener.getLogger().print("* "); + method.invoke(scmSource, multiObserver, listener); + multiObserver.endObserving(); + } catch (InvocationTargetException ex) { + listener.error(ex.getCause().getMessage()); + retrieveOk = false; + } catch (IllegalAccessException ex) { + // Should not happen, we set it accessible above + ex.printStackTrace(); + retrieveOk = false; + } catch (IllegalArgumentException ex) { + ex.printStackTrace(); + retrieveOk = false; + } + } + + if (!retrieveOk) { + listener.error("Failed to fetch one or more SCMs. Builds may fail."); + } + + // retain only branches that exist in every source... + Collector smallestByBranchCount = null; + final Map multiObserverResult = multiObserver.result(); + { + int smallestSize = Integer.MAX_VALUE; + for (Collector entry : multiObserverResult.values()) { + int size = entry.result().size(); + if (size < smallestSize) { + smallestSize = size; + smallestByBranchCount = entry; + } + } + } + + if (smallestByBranchCount != null) { + // gather potential branches... + // NOTE: assume SCMHead has by-branch-name equality + final Set branches = new HashSet(smallestByBranchCount.result().keySet()); + // remove non-existing branches... + for (Iterator iter = branches.iterator(); iter.hasNext();) { + final SCMHead branch = iter.next(); + for (Collector collector : multiObserverResult.values()) { + if (!collector.result().containsKey(branch)) { + iter.remove(); + break; + } + } + } + // feed remaining branches into observer... + for (SCMHead scmHead : branches) { + for (Collector res : multiObserverResult.values()) { + final SCMRevision scmRevision = res.result().get(scmHead); + if (scmRevision != null) { + // listener.getLogger().format(" Branch '%s' in SCM + // %s%n", scmHead.getName(), res.getKey()); + observer.observe(scmRevision.getHead(), scmRevision); + } + } + } + } + listener.getLogger().println("Done collecting branches."); + } + + /* + * (non-Javadoc) + * + * @see jenkins.scm.api.SCMSource#build(jenkins.scm.api.SCMHead, + * jenkins.scm.api.SCMRevision) + */ + @Override + public SCM build(SCMHead head, SCMRevision revision) { + List ret = new ArrayList(scmSources.size()); + for (SCMSource scmSource : scmSources) { + ret.add(scmSource.build(head, revision)); + } + try { + return new MultiSCM(ret); + } catch (IOException neverThrown) { + throw new RuntimeException(neverThrown); + } + } + + /** + * Overridden for better type safety. + */ + @Override + public DescriptorImpl getDescriptor() { + return (DescriptorImpl) super.getDescriptor(); + } + + // ////////////////////////////////////////////////////////////////// + // inner classes + // ////////////////////////////////////////////////////////////////// + @Extension + public static class DescriptorImpl extends SCMSourceDescriptor { + private static Logger logger = Logger.getLogger(MultiSCMSource.class.getName()); + + private Map applicableSCMs; + + @Override + public String getDisplayName() { + return "Multiple SCMs"; + } + + /** + * Get the {@link SCMSourceDescriptor}s that are appropriate for a + * MultiSCMSource. + */ + public Set getScmSourceDescriptors() { + if (applicableSCMs == null) { + applicableSCMs = calcApplicableSCMs(); + } + return applicableSCMs.keySet(); + } + + private Method getRetrieveMethod(Descriptor descriptor) { + // init method map.. + getScmSourceDescriptors(); + return applicableSCMs.get(descriptor); + } + + private Map calcApplicableSCMs() { + Map result = new HashMap(4); + Jenkins j = Jenkins.getInstance(); + if (j != null) { + for (Descriptor d : j.getDescriptorList(SCMSource.class)) { + // Filter MultiSCM itself from the list of choices. + if (!(d instanceof MultiSCMSource.DescriptorImpl)) { + final SCMSourceDescriptor descr = (SCMSourceDescriptor) d; + Method retrieveMethod = determineRetrieveMethod(descr.clazz); + if (retrieveMethod == null) { + // no matching method found in class hierarchy + final String msg = String.format( + "Ignoring SCMSource `%s` since no matching `retrieve` method could be found in class hierarchy.", + descr.clazz.getName()); + logger.warning(msg); + } else { + result.put(descr, retrieveMethod); + } + } + } + } + return result; + } + + /** + * Searches for the protected + * {@link SCMSource#retrieve(SCMHeadObserver observer, TaskListener listener)} + * method of the specified class. Unfortunately for our purpose, that + * retrieve-method is not public. + * + * @return the Method object or null if none could be found + * in the class hierarchy + */ + private static Method determineRetrieveMethod(Class clazz) { + if (clazz != null && SCMSource.class.isAssignableFrom(clazz)) { + try { + return clazz.getDeclaredMethod("retrieve", + new Class[] { SCMHeadObserver.class, TaskListener.class }); + } catch (NoSuchMethodException ex) { + // try super class + return determineRetrieveMethod(clazz.getSuperclass()); + } + } + return null; // no method found in class hierarchy + } + } // DescriptorImpl + +} diff --git a/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCMSource/config.jelly b/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCMSource/config.jelly new file mode 100644 index 0000000..c4be75d --- /dev/null +++ b/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCMSource/config.jelly @@ -0,0 +1,35 @@ + + + + + + + + + From 845506557397ee87c78979617143e46276e0c1ad Mon Sep 17 00:00:00 2001 From: 15knots Date: Sat, 19 Nov 2016 12:50:53 +0100 Subject: [PATCH 02/17] added dependency --- build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.gradle b/build.gradle index 0302465..6eaba3d 100644 --- a/build.gradle +++ b/build.gradle @@ -20,6 +20,9 @@ jenkinsPlugin { url = 'https://wiki.jenkins-ci.org/display/JENKINS/Multiple+SCMs+Plugin' gitHubUrl = 'https://github.com/jenkinsci/multiple-scms-plugin' + dependencies { + jenkinsPlugins 'org.jenkins-ci.plugins:scm-api:1.2@jar' + } // the developers section is optional, and corresponds to the POM developers section developers { developer { From 6f0cb5da8d8b320feae9278156d900086d31f397 Mon Sep 17 00:00:00 2001 From: 15knots Date: Tue, 29 Nov 2016 20:21:57 +0100 Subject: [PATCH 03/17] Make triggering of builds from branch-collecting in multi-branch-project work --- .../multiplescms/MultiSCMHeadObserver.java | 4 +- .../plugins/multiplescms/MultiSCMSource.java | 72 +++++++++++++++++-- 2 files changed, 69 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMHeadObserver.java b/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMHeadObserver.java index 9a221de..36b1d7e 100644 --- a/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMHeadObserver.java +++ b/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMHeadObserver.java @@ -23,7 +23,7 @@ */ package org.jenkinsci.plugins.multiplescms; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import edu.umd.cs.findbugs.annotations.NonNull; @@ -46,7 +46,7 @@ class MultiSCMHeadObserver extends SCMHeadObserver { * */ public MultiSCMHeadObserver(int numSources) { - result = new HashMap(numSources, 1.0f); + result = new LinkedHashMap(numSources, 1.0f); } /** diff --git a/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMSource.java b/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMSource.java index 0afbb00..246a2aa 100644 --- a/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMSource.java +++ b/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMSource.java @@ -27,6 +27,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -150,15 +151,17 @@ protected void retrieve(SCMHeadObserver observer, TaskListener listener) throws } } // feed remaining branches into observer... - for (SCMHead scmHead : branches) { + for (SCMHead branch : branches) { + final List bRevs = new ArrayList(multiObserverResult.size()); for (Collector res : multiObserverResult.values()) { - final SCMRevision scmRevision = res.result().get(scmHead); + final SCMRevision scmRevision = res.result().get(branch); if (scmRevision != null) { - // listener.getLogger().format(" Branch '%s' in SCM - // %s%n", scmHead.getName(), res.getKey()); - observer.observe(scmRevision.getHead(), scmRevision); + bRevs.add(scmRevision); } } + final MultiSCMRevision rev = new MultiSCMRevision(branch, + (SCMRevision[]) bRevs.toArray(new SCMRevision[bRevs.size()])); + observer.observe(branch, rev); } } listener.getLogger().println("Done collecting branches."); @@ -194,6 +197,65 @@ public DescriptorImpl getDescriptor() { // ////////////////////////////////////////////////////////////////// // inner classes // ////////////////////////////////////////////////////////////////// + /** + * An {@code SCMRevision} implementation that hold multiple SCMRevisions. + * + * @author Martin Weber + */ + private static class MultiSCMRevision extends SCMRevision { + private final SCMRevision[] revisions; + + /** + */ + protected MultiSCMRevision(SCMHead head, SCMRevision[] revisions) { + super(head); + if (revisions == null) { + throw new NullPointerException("revisions"); + } + this.revisions = revisions; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + MultiSCMRevision that = (MultiSCMRevision) o; + + if (!getHead().equals(that.getHead())) { + return false; + } + if (!Arrays.equals(revisions, that.revisions)) + return false; + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(revisions); + return result; + } + + @Override + public String toString() { + StringBuilder b = new StringBuilder(); + int iMax = revisions.length - 1; + for (int i = 0; i < revisions.length; i++) { + b.append(revisions[i]); + if (i == iMax) + break; + b.append(":"); + } + return b.toString(); + } + } // MultiSCMRevision + @Extension public static class DescriptorImpl extends SCMSourceDescriptor { private static Logger logger = Logger.getLogger(MultiSCMSource.class.getName()); From b8b71fe2e18ffa287c0e815da4ff392aea58da1c Mon Sep 17 00:00:00 2001 From: 15knots Date: Tue, 29 Nov 2016 20:43:24 +0100 Subject: [PATCH 04/17] simplyfied MultiSCMHeadObserver --- .../multiplescms/MultiSCMHeadObserver.java | 26 ++++++++++++------- .../plugins/multiplescms/MultiSCMSource.java | 15 ++++++----- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMHeadObserver.java b/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMHeadObserver.java index 36b1d7e..2dad710 100644 --- a/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMHeadObserver.java +++ b/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMHeadObserver.java @@ -23,8 +23,8 @@ */ package org.jenkinsci.plugins.multiplescms; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.ArrayList; +import java.util.List; import edu.umd.cs.findbugs.annotations.NonNull; import jenkins.scm.api.SCMHead; @@ -38,7 +38,7 @@ */ class MultiSCMHeadObserver extends SCMHeadObserver { - private final Map result; + private final List result; private Collector currentObserver; private SCMSource currentSource; @@ -46,7 +46,7 @@ class MultiSCMHeadObserver extends SCMHeadObserver { * */ public MultiSCMHeadObserver(int numSources) { - result = new LinkedHashMap(numSources, 1.0f); + result = new ArrayList(numSources); } /** @@ -56,16 +56,19 @@ public MultiSCMHeadObserver(int numSources) { * @param newSource */ public void beginObserving(SCMSource newSource) { - currentObserver = new SCMHeadObserver.Collector(); - currentSource = newSource; + if (newSource != currentSource) { + currentObserver = new SCMHeadObserver.Collector(); + currentSource = newSource; + } } /** * Notified after a SCM source has been successfully observed. Must be - * invoked after to {@link #observe}. + * invoked after to {@link #observe}. May be not invoked, if an error + * occurred in the calling code. */ public void endObserving() { - result.put(currentSource, currentObserver); + result.add(currentObserver); currentObserver = null; currentSource = null; } @@ -82,12 +85,15 @@ public void observe(SCMHead head, SCMRevision revision) { } /** - * Returns the collected results. + * Returns the collected results. For each invocation sequence of + * {@link #beginObserving(SCMSource)}/{@link #endObserving()}, the returned + * list will contain a corresponding {@code Collector} object. + * * * @return the collected results. */ @NonNull - public Map result() { + public List result() { return result; } } diff --git a/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMSource.java b/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMSource.java index 246a2aa..6ea8fda 100644 --- a/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMSource.java +++ b/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMSource.java @@ -124,10 +124,10 @@ protected void retrieve(SCMHeadObserver observer, TaskListener listener) throws // retain only branches that exist in every source... Collector smallestByBranchCount = null; - final Map multiObserverResult = multiObserver.result(); + final List multiSCMCollectors = multiObserver.result(); { int smallestSize = Integer.MAX_VALUE; - for (Collector entry : multiObserverResult.values()) { + for (Collector entry : multiSCMCollectors) { int size = entry.result().size(); if (size < smallestSize) { smallestSize = size; @@ -143,7 +143,7 @@ protected void retrieve(SCMHeadObserver observer, TaskListener listener) throws // remove non-existing branches... for (Iterator iter = branches.iterator(); iter.hasNext();) { final SCMHead branch = iter.next(); - for (Collector collector : multiObserverResult.values()) { + for (Collector collector : multiSCMCollectors) { if (!collector.result().containsKey(branch)) { iter.remove(); break; @@ -152,8 +152,8 @@ protected void retrieve(SCMHeadObserver observer, TaskListener listener) throws } // feed remaining branches into observer... for (SCMHead branch : branches) { - final List bRevs = new ArrayList(multiObserverResult.size()); - for (Collector res : multiObserverResult.values()) { + final List bRevs = new ArrayList(multiSCMCollectors.size()); + for (Collector res : multiSCMCollectors) { final SCMRevision scmRevision = res.result().get(branch); if (scmRevision != null) { bRevs.add(scmRevision); @@ -203,6 +203,10 @@ public DescriptorImpl getDescriptor() { * @author Martin Weber */ private static class MultiSCMRevision extends SCMRevision { + /** + * + */ + private static final long serialVersionUID = 1L; private final SCMRevision[] revisions; /** @@ -225,7 +229,6 @@ public boolean equals(Object o) { } MultiSCMRevision that = (MultiSCMRevision) o; - if (!getHead().equals(that.getHead())) { return false; } From ce105a62ca65d1acde87f850acaf22c80765460c Mon Sep 17 00:00:00 2001 From: 15knots Date: Tue, 29 Nov 2016 20:49:36 +0100 Subject: [PATCH 05/17] Make it easier for users to distinguish between 'Add source' and 'Add to multiple SCM source' --- .../multiplescms/MultiSCMSource/config.jelly | 26 +------------------ 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCMSource/config.jelly b/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCMSource/config.jelly index c4be75d..2297f9d 100644 --- a/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCMSource/config.jelly +++ b/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCMSource/config.jelly @@ -2,34 +2,10 @@ - - - From 05e18f31b4fdabb493eec42525d8b24aef9d358a Mon Sep 17 00:00:00 2001 From: 15knots Date: Tue, 2 Oct 2018 14:53:34 +0200 Subject: [PATCH 06/17] replace deprecated method invocations --- .../plugins/multiplescms/MultiSCM.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCM.java b/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCM.java index fb105e9..9fe813b 100644 --- a/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCM.java +++ b/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCM.java @@ -5,14 +5,13 @@ import hudson.FilePath; import hudson.Launcher; import hudson.model.Action; -import hudson.model.BuildListener; import hudson.model.Saveable; import hudson.model.TaskListener; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.Descriptor; +import hudson.model.Job; import hudson.model.Run; -import hudson.model.Hudson; import hudson.scm.ChangeLogParser; import hudson.scm.PollingResult; import hudson.scm.PollingResult.Change; @@ -21,6 +20,7 @@ import hudson.scm.NullSCM; import hudson.scm.SCM; import hudson.util.DescribableList; +import jenkins.model.Jenkins; import java.io.File; import java.io.FileOutputStream; @@ -32,8 +32,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; -import net.sf.json.JSONArray; - import net.sf.json.JSONObject; import org.apache.commons.io.FileUtils; @@ -44,7 +42,7 @@ public class MultiSCM extends SCM implements Saveable { private DescribableList> scms = - new DescribableList>(this); + new DescribableList<>(this); @DataBoundConstructor public MultiSCM(List scmList) throws IOException { @@ -64,7 +62,8 @@ public SCMRevisionState calcRevisionsFromBuild(AbstractBuild build, MultiSCMRevisionState revisionStates = new MultiSCMRevisionState(); for(SCM scm : scms) { - SCMRevisionState scmState = scm.calcRevisionsFromBuild(build, launcher, listener); + SCMRevisionState scmState = scm.calcRevisionsFromBuild + (build, launcher != null ? build.getWorkspace() : null, launcher, listener); revisionStates.add(scm, build.getWorkspace(), build, scmState); } @@ -90,7 +89,7 @@ public void buildEnvVars(AbstractBuild build, Map env) { } catch(NullPointerException npe) {} - + } } @@ -142,7 +141,7 @@ public void checkout(Run build, Launcher launcher, } scm.checkout(build, launcher, workspace, listener, subChangeLog, workspaceRevision); - List actions = build.getActions(); + List actions = build.getAllActions(); for(Action a : actions) { if(!scmActions.contains(a) && a instanceof SCMRevisionState && !(a instanceof MultiSCMRevisionState)) { scmActions.add(a); @@ -217,7 +216,7 @@ public DescriptorImpl() { public List> getApplicableSCMs(AbstractProject project) { List> scms = new ArrayList>(); - for(SCMDescriptor scm : SCM._for(project)) { + for(SCMDescriptor scm : SCM._for((Job)project)) { // Filter MultiSCM itself from the list of choices. // Theoretically it might work, but I see no practical reason to allow // nested MultiSCM configurations. @@ -267,7 +266,8 @@ public SCM newInstance(StaplerRequest req, JSONObject formData) private static void readItem(StaplerRequest req, JSONObject obj, List dest) throws FormException { String staplerClass = obj.getString("stapler-class"); - Descriptor d = (Descriptor) Hudson.getInstance().getDescriptor(staplerClass); + @SuppressWarnings("unchecked") + Descriptor d = Jenkins.getInstance().getDescriptor(staplerClass); dest.add(d.newInstance(req, obj)); } } From 0698b267c7ebfc72f657c11b0464b0999830f0f3 Mon Sep 17 00:00:00 2001 From: 15knots Date: Tue, 2 Oct 2018 14:59:18 +0200 Subject: [PATCH 07/17] adapt to scm-api 2.0 version 0.7.1 --- build.gradle | 6 +- .../plugins/multiplescms/MultiSCMSource.java | 225 +++++------------- 2 files changed, 68 insertions(+), 163 deletions(-) diff --git a/build.gradle b/build.gradle index 6eaba3d..20865bd 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { // id 'com.github.hierynomus.license' version '0.12.1' } -version = '0.7-SNAPSHOT' +version = '0.7.1-SNAPSHOT' description = 'Allows multiple SCM plugins to be used in a job.' //license { @@ -14,14 +14,14 @@ group = 'org.jenkins-ci.plugins' jenkinsPlugin { // version of Jenkins core this plugin depends on, must be 1.420 or later - coreVersion = '1.580.1' + coreVersion = '1.651.3' //'1.580.1' shortName = 'multiple-scms' displayName = 'Jenkins Multiple SCMs plugin' url = 'https://wiki.jenkins-ci.org/display/JENKINS/Multiple+SCMs+Plugin' gitHubUrl = 'https://github.com/jenkinsci/multiple-scms-plugin' dependencies { - jenkinsPlugins 'org.jenkins-ci.plugins:scm-api:1.2@jar' + jenkinsPlugins 'org.jenkins-ci.plugins:scm-api:2.0.3@jar' } // the developers section is optional, and corresponds to the POM developers section developers { diff --git a/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMSource.java b/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMSource.java index 6ea8fda..f138a92 100644 --- a/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMSource.java +++ b/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMSource.java @@ -24,37 +24,31 @@ package org.jenkinsci.plugins.multiplescms; import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.Set; -import java.util.logging.Logger; - import org.kohsuke.stapler.DataBoundConstructor; import edu.umd.cs.findbugs.annotations.CheckForNull; import hudson.Extension; import hudson.Util; -import hudson.model.Descriptor; import hudson.model.TaskListener; import hudson.scm.SCM; -import jenkins.model.Jenkins; import jenkins.scm.api.SCMHead; +import jenkins.scm.api.SCMHeadEvent; import jenkins.scm.api.SCMHeadObserver; import jenkins.scm.api.SCMHeadObserver.Collector; import jenkins.scm.api.SCMRevision; import jenkins.scm.api.SCMSource; +import jenkins.scm.api.SCMSourceCriteria; import jenkins.scm.api.SCMSourceDescriptor; /** * An {@code SCMSource} implementation that monitors and fetches multiple SCMs. - * + * * @author Martin Weber */ public class MultiSCMSource extends SCMSource { @@ -80,96 +74,74 @@ public void setScmSources(List scmSources) { /* * (non-Javadoc) - * + * * @see jenkins.scm.api.SCMSource#retrieve(jenkins.scm.api.SCMHeadObserver, * hudson.model.TaskListener) */ @Override - protected void retrieve(SCMHeadObserver observer, TaskListener listener) throws IOException, InterruptedException { - final MultiSCMHeadObserver multiObserver = new MultiSCMHeadObserver(scmSources.size()); - - listener.getLogger().println("Collecting branches that exist in each SCM..."); - // process all SCMs, but try to not delete existing sub-projects... - boolean retrieveOk = true; - for (SCMSource scmSource : scmSources) { - multiObserver.beginObserving(scmSource); - Method method = getDescriptor().getRetrieveMethod(scmSource.getDescriptor()); - /* - * invoke scmSource.retrieve(observer, listener). Unfortunately, - * that method is not accessible to us, so we use reflection - */ - try { - if (!method.isAccessible()) { - method.setAccessible(true); - } - listener.getLogger().print("* "); - method.invoke(scmSource, multiObserver, listener); - multiObserver.endObserving(); - } catch (InvocationTargetException ex) { - listener.error(ex.getCause().getMessage()); - retrieveOk = false; - } catch (IllegalAccessException ex) { - // Should not happen, we set it accessible above - ex.printStackTrace(); - retrieveOk = false; - } catch (IllegalArgumentException ex) { - ex.printStackTrace(); - retrieveOk = false; - } - } - - if (!retrieveOk) { - listener.error("Failed to fetch one or more SCMs. Builds may fail."); - } - - // retain only branches that exist in every source... - Collector smallestByBranchCount = null; - final List multiSCMCollectors = multiObserver.result(); - { - int smallestSize = Integer.MAX_VALUE; - for (Collector entry : multiSCMCollectors) { - int size = entry.result().size(); - if (size < smallestSize) { - smallestSize = size; - smallestByBranchCount = entry; - } - } - } - - if (smallestByBranchCount != null) { - // gather potential branches... - // NOTE: assume SCMHead has by-branch-name equality - final Set branches = new HashSet(smallestByBranchCount.result().keySet()); - // remove non-existing branches... - for (Iterator iter = branches.iterator(); iter.hasNext();) { - final SCMHead branch = iter.next(); - for (Collector collector : multiSCMCollectors) { - if (!collector.result().containsKey(branch)) { - iter.remove(); - break; - } - } - } - // feed remaining branches into observer... - for (SCMHead branch : branches) { - final List bRevs = new ArrayList(multiSCMCollectors.size()); - for (Collector res : multiSCMCollectors) { - final SCMRevision scmRevision = res.result().get(branch); - if (scmRevision != null) { - bRevs.add(scmRevision); - } - } - final MultiSCMRevision rev = new MultiSCMRevision(branch, - (SCMRevision[]) bRevs.toArray(new SCMRevision[bRevs.size()])); - observer.observe(branch, rev); - } - } - listener.getLogger().println("Done collecting branches."); + protected void retrieve(SCMSourceCriteria criteria, SCMHeadObserver observer, SCMHeadEvent event, + TaskListener listener) throws IOException, InterruptedException { + final MultiSCMHeadObserver multiObserver = new MultiSCMHeadObserver(scmSources.size()); + + // process all SCMs, but try to not delete existing sub-projects... + for (SCMSource scmSource : scmSources) { + multiObserver.beginObserving(scmSource); + scmSource.fetch(criteria, multiObserver, event, listener); + multiObserver.endObserving(); + } + + // retain only branches that exist in every source... + Collector smallestByBranchCount = null; + final List multiSCMCollectors = multiObserver.result(); + { + int smallestSize = Integer.MAX_VALUE; + for (Collector entry : multiSCMCollectors) { + int size = entry.result().size(); + if (size < smallestSize) { + smallestSize = size; + smallestByBranchCount = entry; + } + } + } + + if (smallestByBranchCount != null) { + listener.getLogger().println("Collecting branches that exist in each SCM..."); + // gather potential branches... + // NOTE: assume SCMHead has by-branch-name equality + final Set branches = new HashSet(smallestByBranchCount.result().keySet()); + // remove non-existing branches... + for (Iterator iter = branches.iterator(); iter.hasNext();) { + final SCMHead branch = iter.next(); + for (Collector collector : multiSCMCollectors) { + if (!collector.result().containsKey(branch)) { + iter.remove(); + break; + } + } + } + for (SCMHead branch : branches) { + listener.getLogger().printf("* Branch `%s` exists in each SCM.%n", branch.getName()); + } + listener.getLogger().println("Done collecting branches."); + // feed remaining branches into observer... + for (SCMHead branch : branches) { + final List bRevs = new ArrayList(multiSCMCollectors.size()); + for (Collector res : multiSCMCollectors) { + final SCMRevision scmRevision = res.result().get(branch); + if (scmRevision != null) { + bRevs.add(scmRevision); + } + } + final MultiSCMRevision rev = new MultiSCMRevision(branch, + bRevs.toArray(new SCMRevision[bRevs.size()])); + observer.observe(branch, rev); + } + } } /* * (non-Javadoc) - * + * * @see jenkins.scm.api.SCMSource#build(jenkins.scm.api.SCMHead, * jenkins.scm.api.SCMRevision) */ @@ -199,12 +171,12 @@ public DescriptorImpl getDescriptor() { // ////////////////////////////////////////////////////////////////// /** * An {@code SCMRevision} implementation that hold multiple SCMRevisions. - * + * * @author Martin Weber */ private static class MultiSCMRevision extends SCMRevision { /** - * + * */ private static final long serialVersionUID = 1L; private final SCMRevision[] revisions; @@ -261,77 +233,10 @@ public String toString() { @Extension public static class DescriptorImpl extends SCMSourceDescriptor { - private static Logger logger = Logger.getLogger(MultiSCMSource.class.getName()); - - private Map applicableSCMs; - @Override public String getDisplayName() { return "Multiple SCMs"; } - - /** - * Get the {@link SCMSourceDescriptor}s that are appropriate for a - * MultiSCMSource. - */ - public Set getScmSourceDescriptors() { - if (applicableSCMs == null) { - applicableSCMs = calcApplicableSCMs(); - } - return applicableSCMs.keySet(); - } - - private Method getRetrieveMethod(Descriptor descriptor) { - // init method map.. - getScmSourceDescriptors(); - return applicableSCMs.get(descriptor); - } - - private Map calcApplicableSCMs() { - Map result = new HashMap(4); - Jenkins j = Jenkins.getInstance(); - if (j != null) { - for (Descriptor d : j.getDescriptorList(SCMSource.class)) { - // Filter MultiSCM itself from the list of choices. - if (!(d instanceof MultiSCMSource.DescriptorImpl)) { - final SCMSourceDescriptor descr = (SCMSourceDescriptor) d; - Method retrieveMethod = determineRetrieveMethod(descr.clazz); - if (retrieveMethod == null) { - // no matching method found in class hierarchy - final String msg = String.format( - "Ignoring SCMSource `%s` since no matching `retrieve` method could be found in class hierarchy.", - descr.clazz.getName()); - logger.warning(msg); - } else { - result.put(descr, retrieveMethod); - } - } - } - } - return result; - } - - /** - * Searches for the protected - * {@link SCMSource#retrieve(SCMHeadObserver observer, TaskListener listener)} - * method of the specified class. Unfortunately for our purpose, that - * retrieve-method is not public. - * - * @return the Method object or null if none could be found - * in the class hierarchy - */ - private static Method determineRetrieveMethod(Class clazz) { - if (clazz != null && SCMSource.class.isAssignableFrom(clazz)) { - try { - return clazz.getDeclaredMethod("retrieve", - new Class[] { SCMHeadObserver.class, TaskListener.class }); - } catch (NoSuchMethodException ex) { - // try super class - return determineRetrieveMethod(clazz.getSuperclass()); - } - } - return null; // no method found in class hierarchy - } } // DescriptorImpl } From b43e9a231d8f0dc4a43ae8a99f1447a417154095 Mon Sep 17 00:00:00 2001 From: 15knots Date: Tue, 16 Oct 2018 23:19:59 +0200 Subject: [PATCH 08/17] fix NPE in.../configure.jelly --- .settings/org.eclipse.jdt.core.prefs | 11 +++--- .../plugins/multiplescms/MultiSCMSource.java | 38 ++++++++++++++++++- .../multiplescms/MultiSCMSource/config.jelly | 8 ++-- .../multiplescms/MultiSCMSource/help.html | 11 ++++++ 4 files changed, 57 insertions(+), 11 deletions(-) create mode 100644 src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCMSource/help.html diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index 735243f..b8947ec 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -1,5 +1,6 @@ -#Thu Feb 17 14:25:30 CST 2011 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.source=1.5 -org.eclipse.jdt.core.compiler.compliance=1.5 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMSource.java b/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMSource.java index f138a92..a6bb4c7 100644 --- a/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMSource.java +++ b/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMSource.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 Martin Weber + * Copyright (c) 2016-2018 Martin Weber * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -30,13 +30,17 @@ import java.util.Iterator; import java.util.List; import java.util.Set; + +import org.jenkinsci.Symbol; import org.kohsuke.stapler.DataBoundConstructor; import edu.umd.cs.findbugs.annotations.CheckForNull; import hudson.Extension; import hudson.Util; import hudson.model.TaskListener; +import hudson.scm.NullSCM; import hudson.scm.SCM; +import hudson.scm.SCMDescriptor; import jenkins.scm.api.SCMHead; import jenkins.scm.api.SCMHeadEvent; import jenkins.scm.api.SCMHeadObserver; @@ -232,11 +236,43 @@ public String toString() { } // MultiSCMRevision @Extension + @Symbol(value = { "multipleSCMs" }) public static class DescriptorImpl extends SCMSourceDescriptor { @Override public String getDisplayName() { return "Multiple SCMs"; } + + /** + * Returns the {@link SCMDescriptor} instances that are appropriate for the current context. + * + * @param context the current context. + * @return the {@link SCMDescriptor} instances + */ + @SuppressWarnings("unused") // used by stapler binding + public static List> getScmSourceDescriptors(/*@AncestorInPath SCMSourceOwner context*/) { + List> result = new ArrayList>(SCM.all()); + for (Iterator> iterator = result.iterator(); iterator.hasNext(); ) { + SCMDescriptor d = iterator.next(); + if (NullSCM.class.equals(d.clazz) || MultiSCM.class.equals(d.clazz)) { + iterator.remove(); + } + } + + // if (context != null && context instanceof Describable) { +// final Descriptor descriptor = ((Describable) context).getDescriptor(); +// if (descriptor instanceof TopLevelItemDescriptor) { +// final TopLevelItemDescriptor topLevelItemDescriptor = (TopLevelItemDescriptor) descriptor; +// for (Iterator> iterator = result.iterator(); iterator.hasNext(); ) { +// SCMDescriptor d = iterator.next(); +// if (!topLevelItemDescriptor.isApplicable(d)) { +// iterator.remove(); +// } +// } +// } +// } + return result; + } } // DescriptorImpl } diff --git a/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCMSource/config.jelly b/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCMSource/config.jelly index 2297f9d..4cf2f95 100644 --- a/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCMSource/config.jelly +++ b/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCMSource/config.jelly @@ -1,11 +1,9 @@ + xmlns:f="/lib/form"> + descriptors="${descriptor.scmSourceDescriptors}" hasHeader="true" + addCaption="${'%Add SCM to Multiple SCMs'}" deleteCaption="${'%Delete SCM'}"/> diff --git a/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCMSource/help.html b/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCMSource/help.html new file mode 100644 index 0000000..d640f45 --- /dev/null +++ b/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCMSource/help.html @@ -0,0 +1,11 @@ +
+Looks for branches in multiple SCMs. A per-branch sub-project will be created, if a branch +is present in each of the configured SCMs.
+The created sub-projects will check out files from each of the SCMs on a job run +(unlike Multibranch-pipeline, which creates sub-projects checking out from a branch seen in +the first SCM, ignoring the other SCMs during checkout), +
+TIP: Concerning git, it will be sufficient to only specify the relevant branches in the +Include branches field in the first SCM in the list below and to leave the +default wildcard * in the additional SCMs untouched. +
\ No newline at end of file From 39d3527febdabe45512381a6b643e6c11c61e571 Mon Sep 17 00:00:00 2001 From: 15knots Date: Tue, 16 Oct 2018 23:29:22 +0200 Subject: [PATCH 09/17] fix NPE in.../configure.jelly --- .../plugins/multiplescms/MultiSCMSource/help.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCMSource/help.html b/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCMSource/help.html index d640f45..dd90fce 100644 --- a/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCMSource/help.html +++ b/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCMSource/help.html @@ -1,9 +1,9 @@
-Looks for branches in multiple SCMs. A per-branch sub-project will be created, if a branch -is present in each of the configured SCMs.
+Looks for branches in multiple SCMs. A per-branch sub-project will be created for any branch +present in each of the configured SCMs.
The created sub-projects will check out files from each of the SCMs on a job run -(unlike Multibranch-pipeline, which creates sub-projects checking out from a branch seen in -the first SCM, ignoring the other SCMs during checkout), +(unlike Multibranch-pipeline, which creates sub-projects checking out from +the first SCM seen owning a branch, and ignoring the other SCMs during checkout),
TIP: Concerning git, it will be sufficient to only specify the relevant branches in the Include branches field in the first SCM in the list below and to leave the From 6bbcf9cbf92e41d0f1b365dbe2e5dcec4a0ce46c Mon Sep 17 00:00:00 2001 From: 15knots Date: Wed, 17 Oct 2018 20:16:40 +0200 Subject: [PATCH 10/17] fix HTML tags --- .../jenkinsci/plugins/multiplescms/MultiSCMSource/help.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCMSource/help.html b/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCMSource/help.html index dd90fce..2547dc0 100644 --- a/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCMSource/help.html +++ b/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCMSource/help.html @@ -1,10 +1,10 @@
Looks for branches in multiple SCMs. A per-branch sub-project will be created for any branch -present in each of the configured SCMs.
+present in each of the configured SCMs.
The created sub-projects will check out files from each of the SCMs on a job run (unlike Multibranch-pipeline, which creates sub-projects checking out from the first SCM seen owning a branch, and ignoring the other SCMs during checkout), -
+
TIP: Concerning git, it will be sufficient to only specify the relevant branches in the Include branches field in the first SCM in the list below and to leave the default wildcard * in the additional SCMs untouched. From fab08046cb472e5d9c8e7f7b599656f30e44c137 Mon Sep 17 00:00:00 2001 From: 15knots Date: Wed, 17 Oct 2018 20:41:17 +0200 Subject: [PATCH 11/17] emit warning in branch indexing log if no branch exists in EACH SCM. --- .../jenkinsci/plugins/multiplescms/MultiSCMSource.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMSource.java b/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMSource.java index a6bb4c7..a717af6 100644 --- a/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMSource.java +++ b/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMSource.java @@ -109,7 +109,7 @@ protected void retrieve(SCMSourceCriteria criteria, SCMHeadObserver observer, SC } if (smallestByBranchCount != null) { - listener.getLogger().println("Collecting branches that exist in each SCM..."); + listener.getLogger().println("Collecting branches..."); // gather potential branches... // NOTE: assume SCMHead has by-branch-name equality final Set branches = new HashSet(smallestByBranchCount.result().keySet()); @@ -123,8 +123,12 @@ protected void retrieve(SCMSourceCriteria criteria, SCMHeadObserver observer, SC } } } - for (SCMHead branch : branches) { - listener.getLogger().printf("* Branch `%s` exists in each SCM.%n", branch.getName()); + if(branches.isEmpty()) { + listener.getLogger().println("!!! None of the branches exists in EACH SCM."); + } else { + for (SCMHead branch : branches) { + listener.getLogger().printf("* Branch `%s` exists in each SCM.%n", branch.getName()); + } } listener.getLogger().println("Done collecting branches."); // feed remaining branches into observer... From f983de1af82fda8460f6d1b47b111d8cd8d12532 Mon Sep 17 00:00:00 2001 From: 15knots Date: Sat, 20 Oct 2018 19:38:58 +0200 Subject: [PATCH 12/17] implement required newer versions of methods --- .../plugins/multiplescms/MultiSCM.java | 67 ++++++++++--------- .../multiplescms/MultiSCMChangeLogParser.java | 24 ++++--- .../multiplescms/MultiSCMChangeLogSet.java | 5 +- 3 files changed, 51 insertions(+), 45 deletions(-) diff --git a/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCM.java b/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCM.java index 9fe813b..b7b01f1 100644 --- a/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCM.java +++ b/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCM.java @@ -1,45 +1,49 @@ package org.jenkinsci.plugins.multiplescms; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringEscapeUtils; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.export.Exported; + import hudson.EnvVars; import hudson.Extension; import hudson.FilePath; import hudson.Launcher; -import hudson.model.Action; -import hudson.model.Saveable; -import hudson.model.TaskListener; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; +import hudson.model.Action; import hudson.model.Descriptor; import hudson.model.Job; import hudson.model.Run; +import hudson.model.Saveable; +import hudson.model.TaskListener; import hudson.scm.ChangeLogParser; +import hudson.scm.NullSCM; import hudson.scm.PollingResult; import hudson.scm.PollingResult.Change; +import hudson.scm.SCM; import hudson.scm.SCMDescriptor; import hudson.scm.SCMRevisionState; -import hudson.scm.NullSCM; -import hudson.scm.SCM; import hudson.util.DescribableList; import jenkins.model.Jenkins; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; import net.sf.json.JSONObject; -import org.apache.commons.io.FileUtils; -import org.apache.commons.lang.StringEscapeUtils; -import org.kohsuke.stapler.DataBoundConstructor; -import org.kohsuke.stapler.StaplerRequest; -import org.kohsuke.stapler.export.Exported; - public class MultiSCM extends SCM implements Saveable { private DescribableList> scms = new DescribableList<>(this); @@ -55,17 +59,16 @@ public List getConfiguredSCMs() { } @Override - public SCMRevisionState calcRevisionsFromBuild(AbstractBuild build, - Launcher launcher, TaskListener listener) throws IOException, - InterruptedException { - - MultiSCMRevisionState revisionStates = new MultiSCMRevisionState(); + public @CheckForNull SCMRevisionState calcRevisionsFromBuild( + @Nonnull Run build, @Nullable FilePath workspace, + @Nullable Launcher launcher, @Nonnull TaskListener listener) throws IOException, InterruptedException { + MultiSCMRevisionState revisionStates = new MultiSCMRevisionState(); - for(SCM scm : scms) { - SCMRevisionState scmState = scm.calcRevisionsFromBuild - (build, launcher != null ? build.getWorkspace() : null, launcher, listener); - revisionStates.add(scm, build.getWorkspace(), build, scmState); - } + for(SCM scm : scms) { + SCMRevisionState scmState = scm.calcRevisionsFromBuild + (build, workspace, launcher, listener); + revisionStates.add(scm, workspace, build, scmState); + } return revisionStates; } diff --git a/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMChangeLogParser.java b/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMChangeLogParser.java index adb25b3..ec15270 100644 --- a/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMChangeLogParser.java +++ b/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMChangeLogParser.java @@ -1,6 +1,7 @@ package org.jenkinsci.plugins.multiplescms; import hudson.model.AbstractBuild; +import hudson.model.Run; import hudson.scm.ChangeLogParser; import hudson.scm.ChangeLogSet; import hudson.scm.ChangeLogSet.Entry; @@ -55,15 +56,17 @@ public MultiSCMChangeLogParser(List scms) { private class LogSplitter extends DefaultHandler { private final MultiSCMChangeLogSet changeLogs; - private final AbstractBuild build; + private final Run build; + private RepositoryBrowser browser; private final File tempFile; private String scmClass; private StringBuffer buffer; - public LogSplitter(AbstractBuild build, String tempFilePath) { - changeLogs = new MultiSCMChangeLogSet(build); + public LogSplitter(Run build, RepositoryBrowser browser, String tempFilePath) { + changeLogs = new MultiSCMChangeLogSet(build, browser); this.tempFile= new File(tempFilePath); this.build = build; + this.browser = browser; } @Override @@ -107,16 +110,16 @@ public void endElement(String uri, String localName, String qName) /* * Due to XSTREAM serialization scmRepositoryBrowsers may be null. */ - RepositoryBrowser browser = null; - if (scmRepositoryBrowsers != null) { - browser = scmRepositoryBrowsers.get(scmClass); - } +// RepositoryBrowser browser = null; +// if (scmRepositoryBrowsers != null) { +// browser = scmRepositoryBrowsers.get(scmClass); +// } if(parser != null) { ChangeLogSet cls; if (browser != null) { cls = parser.parse(build, browser, tempFile); } else { - cls = parser.parse(build, tempFile); + cls = parser.parse(build, browser, tempFile); } changeLogs.add(scmClass, scmDisplayNames.get(scmClass), cls); @@ -139,8 +142,7 @@ public ChangeLogSet getChangeLogSets() { } @Override - public ChangeLogSet parse(AbstractBuild build, File changelogFile) - throws IOException, SAXException { + public ChangeLogSet parse(Run build, RepositoryBrowser browser, File changelogFile) throws IOException, SAXException { if(scmLogParsers == null) return ChangeLogSet.createEmpty(build); @@ -154,7 +156,7 @@ public ChangeLogSet parse(AbstractBuild build, File changelogFi throw new SAXException("Could not create parser", e); } - LogSplitter splitter = new LogSplitter(build, changelogFile.getPath() + ".temp2"); + LogSplitter splitter = new LogSplitter(build, browser, changelogFile.getPath() + ".temp2"); parser.parse(changelogFile, splitter); return splitter.getChangeLogSets(); } diff --git a/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMChangeLogSet.java b/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMChangeLogSet.java index d91f0ca..510984f 100644 --- a/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMChangeLogSet.java +++ b/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMChangeLogSet.java @@ -7,6 +7,7 @@ import java.util.List; import hudson.model.AbstractBuild; +import hudson.model.Run; import hudson.scm.RepositoryBrowser; import hudson.scm.ChangeLogSet; import hudson.scm.ChangeLogSet.Entry; @@ -17,8 +18,8 @@ public class MultiSCMChangeLogSet extends ChangeLogSet { private final HashMap changes; private final Set kinds; - protected MultiSCMChangeLogSet(AbstractBuild build) { - super(build); + protected MultiSCMChangeLogSet(Run build, RepositoryBrowser browser) { + super(build, browser); changes = new HashMap(); kinds = new HashSet(); } From 3950a26757f8f294f0d30a4fd5c55749ec5683e7 Mon Sep 17 00:00:00 2001 From: 15knots Date: Sun, 21 Oct 2018 20:53:46 +0200 Subject: [PATCH 13/17] add (possible) fix for JENKINS-52837 --- .../plugins/multiplescms/MultiSCM/config.jelly | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCM/config.jelly b/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCM/config.jelly index 9e924f4..481529f 100644 --- a/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCM/config.jelly +++ b/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCM/config.jelly @@ -6,10 +6,20 @@ - + + + From 22e18b8af1a2f8d80519dbf88ae8bc92d1fe6d66 Mon Sep 17 00:00:00 2001 From: 15knots Date: Sun, 21 Oct 2018 21:23:54 +0200 Subject: [PATCH 14/17] return SCMSourceDescriptorS instead of SCMDescriptorS --- .../plugins/multiplescms/MultiSCMSource.java | 50 ++++++++----------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMSource.java b/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMSource.java index a717af6..6ea5917 100644 --- a/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMSource.java +++ b/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCMSource.java @@ -36,6 +36,7 @@ import edu.umd.cs.findbugs.annotations.CheckForNull; import hudson.Extension; +import hudson.ExtensionList; import hudson.Util; import hudson.model.TaskListener; import hudson.scm.NullSCM; @@ -49,6 +50,7 @@ import jenkins.scm.api.SCMSource; import jenkins.scm.api.SCMSourceCriteria; import jenkins.scm.api.SCMSourceDescriptor; +import jenkins.scm.impl.NullSCMSource; /** * An {@code SCMSource} implementation that monitors and fetches multiple SCMs. @@ -247,36 +249,24 @@ public String getDisplayName() { return "Multiple SCMs"; } - /** - * Returns the {@link SCMDescriptor} instances that are appropriate for the current context. - * - * @param context the current context. - * @return the {@link SCMDescriptor} instances - */ - @SuppressWarnings("unused") // used by stapler binding - public static List> getScmSourceDescriptors(/*@AncestorInPath SCMSourceOwner context*/) { - List> result = new ArrayList>(SCM.all()); - for (Iterator> iterator = result.iterator(); iterator.hasNext(); ) { - SCMDescriptor d = iterator.next(); - if (NullSCM.class.equals(d.clazz) || MultiSCM.class.equals(d.clazz)) { - iterator.remove(); - } - } - - // if (context != null && context instanceof Describable) { -// final Descriptor descriptor = ((Describable) context).getDescriptor(); -// if (descriptor instanceof TopLevelItemDescriptor) { -// final TopLevelItemDescriptor topLevelItemDescriptor = (TopLevelItemDescriptor) descriptor; -// for (Iterator> iterator = result.iterator(); iterator.hasNext(); ) { -// SCMDescriptor d = iterator.next(); -// if (!topLevelItemDescriptor.isApplicable(d)) { -// iterator.remove(); -// } -// } -// } -// } - return result; + /** + * Returns the {@link SCMSourceDescriptor} instances that are appropriate + * for the current context. + * + * @return the {@link SCMDescriptor} instances + */ + @SuppressWarnings("unused") // used by stapler binding + public static List getScmSourceDescriptors() { + List result = new ArrayList( + ExtensionList.lookup(SCMSourceDescriptor.class)); + for (Iterator iterator = result.iterator(); iterator.hasNext();) { + SCMSourceDescriptor d = iterator.next(); + if (NullSCMSource.class.equals(d.clazz) || MultiSCMSource.class.equals(d.clazz)) { + iterator.remove(); } - } // DescriptorImpl + } + return result; + } + } // DescriptorImpl } From 7ffebe14ef2b4f905b9cef5c8593cc9f0cdf49f4 Mon Sep 17 00:00:00 2001 From: 15knots Date: Wed, 24 Oct 2018 19:22:54 +0200 Subject: [PATCH 15/17] adjust help text to newer versions of git plugin --- .../jenkinsci/plugins/multiplescms/MultiSCMSource/help.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCMSource/help.html b/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCMSource/help.html index 2547dc0..9d3aabe 100644 --- a/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCMSource/help.html +++ b/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCMSource/help.html @@ -1,11 +1,11 @@
Looks for branches in multiple SCMs. A per-branch sub-project will be created for any branch present in each of the configured SCMs.
-The created sub-projects will check out files from each of the SCMs on a job run +The created sub-projects will check out files from each SCM on a job run (unlike Multibranch-pipeline, which creates sub-projects checking out from -the first SCM seen owning a branch, and ignoring the other SCMs during checkout), +the first SCM seen owning a branch, ignoring the other SCMs during checkout),
TIP: Concerning git, it will be sufficient to only specify the relevant branches in the -Include branches field in the first SCM in the list below and to leave the +Filter by name behaviour of the first SCM in the list below and to leave the default wildcard * in the additional SCMs untouched.
\ No newline at end of file From d626fbee964e5c04a65ca943782c7702b4d46a19 Mon Sep 17 00:00:00 2001 From: 15knots Date: Sat, 27 Oct 2018 17:19:43 +0200 Subject: [PATCH 16/17] clean up, NPE fix did not work --- .../multiplescms/MultiSCM/config.jelly | 32 ++++++------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCM/config.jelly b/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCM/config.jelly index 481529f..0182126 100644 --- a/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCM/config.jelly +++ b/src/main/resources/org/jenkinsci/plugins/multiplescms/MultiSCM/config.jelly @@ -1,25 +1,11 @@ - - - - - - - - + + - - - + descriptors="${descriptor.getApplicableSCMs(it)}" + items="${instance.configuredSCMs}" + addCaption="Add SCM" + deleteCaption="Delete SCM" /> + + \ No newline at end of file From 92c10e0076f2b91f4ae7e5efe947c5315cf873af Mon Sep 17 00:00:00 2001 From: 15knots Date: Tue, 30 Oct 2018 16:49:16 +0100 Subject: [PATCH 17/17] fix JENKINS-52837 (NPE when applying descriptor visibility) --- .../plugins/multiplescms/MultiSCM.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCM.java b/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCM.java index b7b01f1..a32573a 100644 --- a/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCM.java +++ b/src/main/java/org/jenkinsci/plugins/multiplescms/MultiSCM.java @@ -216,17 +216,17 @@ public DescriptorImpl() { // TODO Auto-generated constructor stub } - public List> getApplicableSCMs(AbstractProject project) { + public List> getApplicableSCMs(Object item) { List> scms = new ArrayList>(); - - for(SCMDescriptor scm : SCM._for((Job)project)) { - // Filter MultiSCM itself from the list of choices. - // Theoretically it might work, but I see no practical reason to allow - // nested MultiSCM configurations. - if(!(scm instanceof DescriptorImpl) && !(scm instanceof NullSCM.DescriptorImpl)) - scms.add(scm); + if (item instanceof Job) { + for(SCMDescriptor scm : SCM._for((Job)item)) { + // Filter MultiSCM itself from the list of choices. + // Theoretically it might work, but I see no practical reason to allow + // nested MultiSCM configurations. + if(!(scm instanceof DescriptorImpl) && !(scm instanceof NullSCM.DescriptorImpl)) + scms.add(scm); + } } - return scms; }