Skip to content

Commit 044a876

Browse files
authored
Merge pull request #567 from jglick/SCMSource.id
Set sequential `id`s for `SCMSource`s in `MultiBranchProject`
2 parents 598ecbb + 43ebe35 commit 044a876

File tree

6 files changed

+247
-11
lines changed

6 files changed

+247
-11
lines changed

pom.xml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,16 @@
8181
<dependency>
8282
<groupId>io.jenkins.tools.bom</groupId>
8383
<artifactId>bom-${jenkins.baseline}.x</artifactId>
84-
<version>5043.v855ff4819a_0f</version>
84+
<version>5659.vecf9e2dc5a_ed</version>
8585
<type>pom</type>
8686
<scope>import</scope>
8787
</dependency>
88+
<!-- TODO until in BOM -->
89+
<dependency>
90+
<groupId>org.jenkins-ci.plugins</groupId>
91+
<artifactId>scm-api</artifactId>
92+
<version>721.v54b_c43b_da_1db_</version>
93+
</dependency>
8894
</dependencies>
8995
</dependencyManagement>
9096
<dependencies>

src/main/java/jenkins/branch/MultiBranchProject.java

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -204,14 +204,7 @@ public void onLoad(ItemGroup<? extends Item> parent, String name) throws IOExcep
204204
state.reset();
205205
}
206206
// optimize lookup of sources by building a temporary map that is equivalent to getSCMSource(id) in results
207-
Map<String, SCMSource> sourceMap = new HashMap<>();
208-
for (BranchSource source : sources) {
209-
SCMSource s = source.getSource();
210-
String id = s.getId();
211-
if (!sourceMap.containsKey(id)) { // only the first match should win
212-
sourceMap.put(id, s);
213-
}
214-
}
207+
Map<String, SCMSource> sourceMap = buildSourceMap();
215208
for (P item : getItems(factory::isProject)) {
216209
Branch oldBranch = factory.getBranch(item);
217210
SCMSource source = sourceMap.get(oldBranch.getSourceId());
@@ -250,6 +243,28 @@ public void onLoad(ItemGroup<? extends Item> parent, String name) throws IOExcep
250243
}
251244
}
252245

246+
private Map<String, SCMSource> buildSourceMap() {
247+
Map<String, SCMSource> sourceMap = new HashMap<>();
248+
int count = 1;
249+
for (BranchSource source : sources) {
250+
SCMSource s = source.getSource();
251+
String id = s.getId();
252+
if (id.isBlank()) {
253+
// Generate ids of the form "1", "2", … on demand
254+
while (sourceMap.containsKey(Integer.toString(count))) {
255+
count++;
256+
}
257+
id = Integer.toString(count);
258+
s.setId(id);
259+
LOGGER.fine(() -> "assigned id to " + s + " in " + this);
260+
}
261+
if (!sourceMap.containsKey(id)) { // only the first match should win
262+
sourceMap.put(id, s);
263+
}
264+
}
265+
return sourceMap;
266+
}
267+
253268
/**
254269
* Consolidated initialization code.
255270
*/
@@ -426,6 +441,7 @@ public void setSourcesList(List<BranchSource> sources) throws IOException {
426441
if (this.sources.isEmpty() || sources.isEmpty()) {
427442
// easy
428443
this.sources.replaceBy(sources);
444+
buildSourceMap();
429445
return;
430446
}
431447
Set<String> oldIds = sourceIds(this.sources);
@@ -477,12 +493,29 @@ public void setSourcesList(List<BranchSource> sources) throws IOException {
477493

478494
private Set<String> sourceIds(List<BranchSource> sources) {
479495
Set<String> result = new HashSet<>();
496+
int count = 1;
480497
for (BranchSource s : sources) {
481-
result.add(s.getSource().getId());
498+
var id = s.getSource().getId();
499+
if (id.isBlank()) {
500+
// Generate ids of the form "1", "2", … on demand
501+
while (result.contains(Integer.toString(count))) {
502+
count++;
503+
}
504+
id = Integer.toString(count);
505+
s.getSource().setId(id);
506+
LOGGER.fine(() -> "assigned id to " + s.getSource() + " in " + this);
507+
}
508+
result.add(id);
482509
}
483510
return result;
484511
}
485512

513+
@Override
514+
public synchronized void save() throws IOException {
515+
buildSourceMap();
516+
super.save();
517+
}
518+
486519
private boolean equalButForId(SCMSource a, SCMSource b) {
487520
if (!a.getClass().equals(b.getClass())) {
488521
return false;

src/test/java/integration/BrandingTest.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,14 @@ protected List<Action> retrieveActions(@NonNull SCMRevision revision, SCMHeadEve
692692
}
693693

694694
@Extension()
695-
public static class DescriptorImpl extends MockSCMSource.DescriptorImpl {}
695+
public static class DescriptorImpl extends MockSCMSource.DescriptorImpl {
696+
697+
@NonNull
698+
@Override
699+
public String getDisplayName() {
700+
return "Branded Mock SCM";
701+
}
702+
703+
}
696704
}
697705
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright 2025 CloudBees, Inc.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
25+
package jenkins.branch;
26+
27+
import integration.harness.BasicMultiBranchProject;
28+
import java.util.List;
29+
import java.util.logging.Level;
30+
import jenkins.scm.impl.mock.MockSCMSource;
31+
import org.junit.Rule;
32+
import org.junit.Test;
33+
import org.jvnet.hudson.test.JenkinsRule;
34+
import org.jvnet.hudson.test.LoggerRule;
35+
import org.jvnet.hudson.test.recipes.LocalData;
36+
import static org.hamcrest.MatcherAssert.assertThat;
37+
import static org.hamcrest.Matchers.contains;
38+
39+
public final class MultiBranchProjectSourcesTest {
40+
41+
@Rule public final JenkinsRule r = new JenkinsRule();
42+
@Rule public final LoggerRule logging = new LoggerRule().record(MultiBranchProject.class, Level.FINE);
43+
44+
@LocalData
45+
@Test public void oldMBPWithIds() throws Exception {
46+
assertThat(idsOf(r.jenkins.getItemByFullName("mbp", BasicMultiBranchProject.class)), contains("30d15538-4f60-437f-81cd-9c46ee6e7ec6", "01d26901-3b39-45ac-915e-e65d806fbcba"));
47+
}
48+
49+
@LocalData
50+
@Test public void oldMBPWithoutIds() throws Exception {
51+
assertThat(idsOf(r.jenkins.getItemByFullName("mbp", BasicMultiBranchProject.class)), contains("1", "2"));
52+
}
53+
54+
@Test public void setSourcesList() throws Exception {
55+
var mbp = r.createProject(BasicMultiBranchProject.class, "mbp");
56+
var bs1 = new BranchSource(new MockSCMSource("c", "r1"));
57+
var bs2 = new BranchSource(new MockSCMSource("c", "r2"));
58+
mbp.setSourcesList(List.of(bs1, bs2));
59+
assertThat(idsOf(mbp), contains("1", "2"));
60+
mbp.setSourcesList(List.of(bs2));
61+
assertThat(idsOf(mbp), contains("2"));
62+
var bs3 = new BranchSource(new MockSCMSource("c", "r3"));
63+
mbp.setSourcesList(List.of(bs1, bs2, bs3));
64+
assertThat(idsOf(mbp), contains("1", "2", "3"));
65+
}
66+
67+
@Test public void getSourcesListAdd() throws Exception {
68+
var mbp = r.createProject(BasicMultiBranchProject.class, "mbp");
69+
var bs1 = new BranchSource(new MockSCMSource("c", "r1"));
70+
var bs2 = new BranchSource(new MockSCMSource("c", "r2"));
71+
mbp.getSourcesList().add(bs1);
72+
assertThat(idsOf(mbp), contains("1"));
73+
mbp.getSourcesList().add(bs2);
74+
assertThat(idsOf(mbp), contains("1", "2"));
75+
}
76+
77+
private static List<String> idsOf(MultiBranchProject<?, ?> mbp) {
78+
return mbp.getSources().stream().map(bs -> bs.getSource().getId()).toList();
79+
}
80+
81+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?xml version='1.1' encoding='UTF-8'?>
2+
<integration.harness.BasicMultiBranchProject>
3+
<actions/>
4+
<description></description>
5+
<properties/>
6+
<folderViews class="jenkins.branch.MultiBranchProjectViewHolder">
7+
<owner class="integration.harness.BasicMultiBranchProject" reference="../.."/>
8+
</folderViews>
9+
<healthMetrics/>
10+
<icon class="jenkins.branch.MetadataActionFolderIcon">
11+
<owner class="integration.harness.BasicMultiBranchProject" reference="../.."/>
12+
</icon>
13+
<orphanedItemStrategy class="com.cloudbees.hudson.plugins.folder.computed.DefaultOrphanedItemStrategy" plugin="cloudbees-folder@6.1053.vd62fb_b_f7367b_">
14+
<pruneDeadBranches>true</pruneDeadBranches>
15+
<daysToKeep>-1</daysToKeep>
16+
<numToKeep>-1</numToKeep>
17+
<abortBuilds>false</abortBuilds>
18+
</orphanedItemStrategy>
19+
<triggers/>
20+
<disabled>false</disabled>
21+
<sources class="jenkins.branch.MultiBranchProject$BranchSourceList">
22+
<data>
23+
<jenkins.branch.BranchSource>
24+
<source class="jenkins.scm.impl.mock.MockSCMSource">
25+
<id>30d15538-4f60-437f-81cd-9c46ee6e7ec6</id>
26+
<controllerId>c6fb4b23-2907-4f7c-9337-e07d687ecccb</controllerId>
27+
<repository>xxx</repository>
28+
<traits>
29+
<jenkins.scm.impl.mock.MockSCMDiscoverBranches/>
30+
</traits>
31+
</source>
32+
<strategy class="jenkins.branch.DefaultBranchPropertyStrategy">
33+
<properties class="empty-list"/>
34+
</strategy>
35+
</jenkins.branch.BranchSource>
36+
<jenkins.branch.BranchSource>
37+
<source class="jenkins.scm.impl.mock.MockSCMSource">
38+
<id>01d26901-3b39-45ac-915e-e65d806fbcba</id>
39+
<controllerId>c6fb4b23-2907-4f7c-9337-e07d687ecccb</controllerId>
40+
<repository>yyy</repository>
41+
<traits>
42+
<jenkins.scm.impl.mock.MockSCMDiscoverBranches/>
43+
</traits>
44+
</source>
45+
<strategy class="jenkins.branch.DefaultBranchPropertyStrategy">
46+
<properties class="empty-list"/>
47+
</strategy>
48+
</jenkins.branch.BranchSource>
49+
</data>
50+
<owner class="integration.harness.BasicMultiBranchProject" reference="../.."/>
51+
</sources>
52+
<factory class="integration.harness.BasicBranchProjectFactory">
53+
<owner class="integration.harness.BasicMultiBranchProject" reference="../.."/>
54+
</factory>
55+
</integration.harness.BasicMultiBranchProject>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?xml version='1.1' encoding='UTF-8'?>
2+
<integration.harness.BasicMultiBranchProject>
3+
<actions/>
4+
<description></description>
5+
<properties/>
6+
<folderViews class="jenkins.branch.MultiBranchProjectViewHolder">
7+
<owner class="integration.harness.BasicMultiBranchProject" reference="../.."/>
8+
</folderViews>
9+
<healthMetrics/>
10+
<icon class="jenkins.branch.MetadataActionFolderIcon">
11+
<owner class="integration.harness.BasicMultiBranchProject" reference="../.."/>
12+
</icon>
13+
<orphanedItemStrategy class="com.cloudbees.hudson.plugins.folder.computed.DefaultOrphanedItemStrategy" plugin="cloudbees-folder@6.1053.vd62fb_b_f7367b_">
14+
<pruneDeadBranches>true</pruneDeadBranches>
15+
<daysToKeep>-1</daysToKeep>
16+
<numToKeep>-1</numToKeep>
17+
<abortBuilds>false</abortBuilds>
18+
</orphanedItemStrategy>
19+
<triggers/>
20+
<disabled>false</disabled>
21+
<sources class="jenkins.branch.MultiBranchProject$BranchSourceList">
22+
<data>
23+
<jenkins.branch.BranchSource>
24+
<source class="jenkins.scm.impl.mock.MockSCMSource">
25+
<controllerId>c6fb4b23-2907-4f7c-9337-e07d687ecccb</controllerId>
26+
<repository>xxx</repository>
27+
<traits>
28+
<jenkins.scm.impl.mock.MockSCMDiscoverBranches/>
29+
</traits>
30+
</source>
31+
<strategy class="jenkins.branch.DefaultBranchPropertyStrategy">
32+
<properties class="empty-list"/>
33+
</strategy>
34+
</jenkins.branch.BranchSource>
35+
<jenkins.branch.BranchSource>
36+
<source class="jenkins.scm.impl.mock.MockSCMSource">
37+
<controllerId>c6fb4b23-2907-4f7c-9337-e07d687ecccb</controllerId>
38+
<repository>yyy</repository>
39+
<traits>
40+
<jenkins.scm.impl.mock.MockSCMDiscoverBranches/>
41+
</traits>
42+
</source>
43+
<strategy class="jenkins.branch.DefaultBranchPropertyStrategy">
44+
<properties class="empty-list"/>
45+
</strategy>
46+
</jenkins.branch.BranchSource>
47+
</data>
48+
<owner class="integration.harness.BasicMultiBranchProject" reference="../.."/>
49+
</sources>
50+
<factory class="integration.harness.BasicBranchProjectFactory">
51+
<owner class="integration.harness.BasicMultiBranchProject" reference="../.."/>
52+
</factory>
53+
</integration.harness.BasicMultiBranchProject>

0 commit comments

Comments
 (0)