diff --git a/pom.xml b/pom.xml index 69e68824..35ced172 100644 --- a/pom.xml +++ b/pom.xml @@ -81,10 +81,16 @@ io.jenkins.tools.bom bom-${jenkins.baseline}.x - 5043.v855ff4819a_0f + 5659.vecf9e2dc5a_ed pom import + + + org.jenkins-ci.plugins + scm-api + 721.v54b_c43b_da_1db_ + diff --git a/src/main/java/jenkins/branch/MultiBranchProject.java b/src/main/java/jenkins/branch/MultiBranchProject.java index f49ca2c0..0f1b5b87 100644 --- a/src/main/java/jenkins/branch/MultiBranchProject.java +++ b/src/main/java/jenkins/branch/MultiBranchProject.java @@ -204,14 +204,7 @@ public void onLoad(ItemGroup parent, String name) throws IOExcep state.reset(); } // optimize lookup of sources by building a temporary map that is equivalent to getSCMSource(id) in results - Map sourceMap = new HashMap<>(); - for (BranchSource source : sources) { - SCMSource s = source.getSource(); - String id = s.getId(); - if (!sourceMap.containsKey(id)) { // only the first match should win - sourceMap.put(id, s); - } - } + Map sourceMap = buildSourceMap(); for (P item : getItems(factory::isProject)) { Branch oldBranch = factory.getBranch(item); SCMSource source = sourceMap.get(oldBranch.getSourceId()); @@ -250,6 +243,28 @@ public void onLoad(ItemGroup parent, String name) throws IOExcep } } + private Map buildSourceMap() { + Map sourceMap = new HashMap<>(); + int count = 1; + for (BranchSource source : sources) { + SCMSource s = source.getSource(); + String id = s.getId(); + if (id.isBlank()) { + // Generate ids of the form "1", "2", … on demand + while (sourceMap.containsKey(Integer.toString(count))) { + count++; + } + id = Integer.toString(count); + s.setId(id); + LOGGER.fine(() -> "assigned id to " + s + " in " + this); + } + if (!sourceMap.containsKey(id)) { // only the first match should win + sourceMap.put(id, s); + } + } + return sourceMap; + } + /** * Consolidated initialization code. */ @@ -426,6 +441,7 @@ public void setSourcesList(List sources) throws IOException { if (this.sources.isEmpty() || sources.isEmpty()) { // easy this.sources.replaceBy(sources); + buildSourceMap(); return; } Set oldIds = sourceIds(this.sources); @@ -477,12 +493,29 @@ public void setSourcesList(List sources) throws IOException { private Set sourceIds(List sources) { Set result = new HashSet<>(); + int count = 1; for (BranchSource s : sources) { - result.add(s.getSource().getId()); + var id = s.getSource().getId(); + if (id.isBlank()) { + // Generate ids of the form "1", "2", … on demand + while (result.contains(Integer.toString(count))) { + count++; + } + id = Integer.toString(count); + s.getSource().setId(id); + LOGGER.fine(() -> "assigned id to " + s.getSource() + " in " + this); + } + result.add(id); } return result; } + @Override + public synchronized void save() throws IOException { + buildSourceMap(); + super.save(); + } + private boolean equalButForId(SCMSource a, SCMSource b) { if (!a.getClass().equals(b.getClass())) { return false; diff --git a/src/test/java/integration/BrandingTest.java b/src/test/java/integration/BrandingTest.java index 5265f172..61980fba 100644 --- a/src/test/java/integration/BrandingTest.java +++ b/src/test/java/integration/BrandingTest.java @@ -692,6 +692,14 @@ protected List retrieveActions(@NonNull SCMRevision revision, SCMHeadEve } @Extension() - public static class DescriptorImpl extends MockSCMSource.DescriptorImpl {} + public static class DescriptorImpl extends MockSCMSource.DescriptorImpl { + + @NonNull + @Override + public String getDisplayName() { + return "Branded Mock SCM"; + } + + } } } diff --git a/src/test/java/jenkins/branch/MultiBranchProjectSourcesTest.java b/src/test/java/jenkins/branch/MultiBranchProjectSourcesTest.java new file mode 100644 index 00000000..a27d678f --- /dev/null +++ b/src/test/java/jenkins/branch/MultiBranchProjectSourcesTest.java @@ -0,0 +1,81 @@ +/* + * The MIT License + * + * Copyright 2025 CloudBees, Inc. + * + * 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 jenkins.branch; + +import integration.harness.BasicMultiBranchProject; +import java.util.List; +import java.util.logging.Level; +import jenkins.scm.impl.mock.MockSCMSource; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.LoggerRule; +import org.jvnet.hudson.test.recipes.LocalData; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; + +public final class MultiBranchProjectSourcesTest { + + @Rule public final JenkinsRule r = new JenkinsRule(); + @Rule public final LoggerRule logging = new LoggerRule().record(MultiBranchProject.class, Level.FINE); + + @LocalData + @Test public void oldMBPWithIds() throws Exception { + assertThat(idsOf(r.jenkins.getItemByFullName("mbp", BasicMultiBranchProject.class)), contains("30d15538-4f60-437f-81cd-9c46ee6e7ec6", "01d26901-3b39-45ac-915e-e65d806fbcba")); + } + + @LocalData + @Test public void oldMBPWithoutIds() throws Exception { + assertThat(idsOf(r.jenkins.getItemByFullName("mbp", BasicMultiBranchProject.class)), contains("1", "2")); + } + + @Test public void setSourcesList() throws Exception { + var mbp = r.createProject(BasicMultiBranchProject.class, "mbp"); + var bs1 = new BranchSource(new MockSCMSource("c", "r1")); + var bs2 = new BranchSource(new MockSCMSource("c", "r2")); + mbp.setSourcesList(List.of(bs1, bs2)); + assertThat(idsOf(mbp), contains("1", "2")); + mbp.setSourcesList(List.of(bs2)); + assertThat(idsOf(mbp), contains("2")); + var bs3 = new BranchSource(new MockSCMSource("c", "r3")); + mbp.setSourcesList(List.of(bs1, bs2, bs3)); + assertThat(idsOf(mbp), contains("1", "2", "3")); + } + + @Test public void getSourcesListAdd() throws Exception { + var mbp = r.createProject(BasicMultiBranchProject.class, "mbp"); + var bs1 = new BranchSource(new MockSCMSource("c", "r1")); + var bs2 = new BranchSource(new MockSCMSource("c", "r2")); + mbp.getSourcesList().add(bs1); + assertThat(idsOf(mbp), contains("1")); + mbp.getSourcesList().add(bs2); + assertThat(idsOf(mbp), contains("1", "2")); + } + + private static List idsOf(MultiBranchProject mbp) { + return mbp.getSources().stream().map(bs -> bs.getSource().getId()).toList(); + } + +} diff --git a/src/test/resources/jenkins/branch/MultiBranchProjectSourcesTest/oldMBPWithIds/jobs/mbp/config.xml b/src/test/resources/jenkins/branch/MultiBranchProjectSourcesTest/oldMBPWithIds/jobs/mbp/config.xml new file mode 100644 index 00000000..c33852b7 --- /dev/null +++ b/src/test/resources/jenkins/branch/MultiBranchProjectSourcesTest/oldMBPWithIds/jobs/mbp/config.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + true + -1 + -1 + false + + + false + + + + + 30d15538-4f60-437f-81cd-9c46ee6e7ec6 + c6fb4b23-2907-4f7c-9337-e07d687ecccb + xxx + + + + + + + + + + + 01d26901-3b39-45ac-915e-e65d806fbcba + c6fb4b23-2907-4f7c-9337-e07d687ecccb + yyy + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/jenkins/branch/MultiBranchProjectSourcesTest/oldMBPWithoutIds/jobs/mbp/config.xml b/src/test/resources/jenkins/branch/MultiBranchProjectSourcesTest/oldMBPWithoutIds/jobs/mbp/config.xml new file mode 100644 index 00000000..a73a9795 --- /dev/null +++ b/src/test/resources/jenkins/branch/MultiBranchProjectSourcesTest/oldMBPWithoutIds/jobs/mbp/config.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + true + -1 + -1 + false + + + false + + + + + c6fb4b23-2907-4f7c-9337-e07d687ecccb + xxx + + + + + + + + + + + c6fb4b23-2907-4f7c-9337-e07d687ecccb + yyy + + + + + + + + + + + + + + + \ No newline at end of file