Skip to content

Commit 2d42f2c

Browse files
committed
Feature: include/exclude branches of multi-branch pipelines
Closes #246
1 parent 4df2499 commit 2d42f2c

File tree

8 files changed

+273
-6
lines changed

8 files changed

+273
-6
lines changed

build-monitor-plugin/pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@
136136
<dependency>
137137
<groupId>org.jenkins-ci.plugins</groupId>
138138
<artifactId>cloudbees-folder</artifactId>
139-
<version>5.16</version>
139+
<version>5.12</version>
140140
<optional>true</optional>
141141
</dependency>
142142
<dependency>

build-monitor-plugin/src/main/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/BuildMonitorView.java

+20-5
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,16 @@ public String currentOrder() {
8686
return currentConfig().getOrder().getClass().getSimpleName();
8787
}
8888

89+
@SuppressWarnings("unused") // used in the configure-entries.jelly form
90+
public String branchesToInclude() {
91+
return currentConfig().getBranchesToInclude();
92+
}
93+
94+
@SuppressWarnings("unused") // used in the configure-entries.jelly form
95+
public String branchesToExclude() {
96+
return currentConfig().getBranchesToExclude();
97+
}
98+
8999
@SuppressWarnings("unused") // used in the configure-entries.jelly form
90100
public boolean isDisplayCommitters() {
91101
return currentConfig().shouldDisplayCommitters();
@@ -112,9 +122,13 @@ protected void submit(StaplerRequest req) throws ServletException, IOException,
112122
synchronized (this) {
113123

114124
String requestedOrdering = req.getParameter("order");
125+
String branchesToInclude = req.getParameter("branchesToInclude");
126+
String branchesToExclude = req.getParameter("branchesToExclude");
115127
title = req.getParameter("title");
116128

117129
currentConfig().setDisplayCommitters(json.optBoolean("displayCommitters", true));
130+
currentConfig().setBranchesToInclude(branchesToInclude);
131+
currentConfig().setBranchesToExclude(branchesToExclude);
118132

119133
try {
120134
currentConfig().setOrder(orderIn(requestedOrdering));
@@ -144,13 +158,10 @@ private boolean isGiven(String value) {
144158
private List<JobView> jobViews() {
145159
JobViews views = new JobViews(new StaticJenkinsAPIs(), currentConfig());
146160

147-
//A little bit of evil to make the type system happy.
148-
@SuppressWarnings("unchecked")
149-
List<Job<?, ?>> projects = new ArrayList(filter(super.getItems(), Job.class));
150-
List<JobView> jobs = new ArrayList<JobView>();
151-
161+
List<Job<?, ?>> projects = currentJobFilter().filterJobs(this.getItems());
152162
Collections.sort(projects, currentConfig().getOrder());
153163

164+
List<JobView> jobs = new ArrayList<JobView>(projects.size());
154165
for (Job project : projects) {
155166
jobs.add(views.viewOf(project));
156167
}
@@ -179,6 +190,10 @@ else if (deserailisingFromAnOlderFormat()) {
179190
return config;
180191
}
181192

193+
private JobFilter currentJobFilter() {
194+
return new JobFilter(currentConfig());
195+
}
196+
182197
private boolean creatingAFreshView() {
183198
return config == null && order == null;
184199
}

build-monitor-plugin/src/main/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/Config.java

+22
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,32 @@ public void setDisplayCommitters(boolean flag) {
4444
public String toString() {
4545
return Objects.toStringHelper(this)
4646
.add("order", order.getClass().getSimpleName())
47+
.add("branchesToInclude", branchesToInclude)
48+
.add("branchesToExclude", branchesToExclude)
4749
.toString();
4850
}
4951

5052
// --
5153

5254
private Comparator<Job<?, ?>> order;
55+
56+
private String branchesToInclude;
57+
58+
public String getBranchesToInclude() {
59+
return branchesToInclude;
60+
}
61+
62+
public void setBranchesToInclude(String branchesToInclude) {
63+
this.branchesToInclude = branchesToInclude;
64+
}
65+
66+
private String branchesToExclude;
67+
68+
public String getBranchesToExclude() {
69+
return branchesToExclude;
70+
}
71+
72+
public void setBranchesToExclude(String branchesToExclude) {
73+
this.branchesToExclude = branchesToExclude;
74+
}
5375
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package com.smartcodeltd.jenkinsci.plugins.buildmonitor;
2+
3+
import com.google.common.base.Strings;
4+
import hudson.model.Item;
5+
import hudson.model.ItemGroup;
6+
import hudson.model.Job;
7+
import hudson.model.TopLevelItem;
8+
9+
import java.util.ArrayList;
10+
import java.util.Collection;
11+
import java.util.List;
12+
import java.util.regex.Pattern;
13+
14+
import static hudson.Util.filter;
15+
16+
public class JobFilter {
17+
private final Pattern includePattern;
18+
private final Pattern excludePattern;
19+
private Class<? extends ItemGroup> abstractFolderClass;
20+
21+
public JobFilter(Config config) {
22+
String include = config.getBranchesToInclude();
23+
String exclude = config.getBranchesToExclude();
24+
includePattern = Strings.isNullOrEmpty(include) ? null : Pattern.compile(include);
25+
excludePattern = Strings.isNullOrEmpty(exclude) ? null : Pattern.compile(exclude);
26+
try {
27+
abstractFolderClass = Class.forName("com.cloudbees.hudson.plugins.folder.AbstractFolder")
28+
.asSubclass(ItemGroup.class);
29+
} catch (ClassNotFoundException e) {
30+
abstractFolderClass = null;
31+
}
32+
}
33+
34+
public List<Job<?, ?>> filterJobs(Collection<? extends Item> items) {
35+
List<Job<?, ?>> jobs = new ArrayList<Job<?, ?>>();
36+
37+
if (items == null) {
38+
return jobs;
39+
}
40+
41+
if (abstractFolderClass != null) {
42+
for (ItemGroup<?> folder : filter(items, abstractFolderClass)) {
43+
Collection<?> folderItems = folder.getItems();
44+
if (folderItems == null) {
45+
continue;
46+
}
47+
List<Job> groupJobs = filter(folderItems, Job.class);
48+
for (Job job : groupJobs) {
49+
String relativename = job.getRelativeNameFrom(folder);
50+
boolean shouldInclude = includePattern == null || includePattern.matcher(relativename).find();
51+
boolean shouldExclude = excludePattern != null && excludePattern.matcher(relativename).find();
52+
if (shouldInclude && !shouldExclude) {
53+
jobs.add(job);
54+
}
55+
}
56+
}
57+
}
58+
59+
for (Job job : filter(items, Job.class)) {
60+
jobs.add(job);
61+
}
62+
63+
return jobs;
64+
}
65+
66+
}

build-monitor-plugin/src/main/resources/com/smartcodeltd/jenkinsci/plugins/buildmonitor/BuildMonitorView/configure-entries.jelly

+8
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,14 @@
5656
<f:textbox name="title" value="${it.title}"/>
5757
</f:entry>
5858

59+
<f:entry title="${%Branches to Include}" help="${descriptor.getHelpFile('branchesToInclude')}">
60+
<f:textbox name="branchesToInclude" field="branchesToInclude" value="${it.branchesToInclude()}"/>
61+
</f:entry>
62+
63+
<f:entry title="${%Branches to Exclude}" help="${descriptor.getHelpFile('branchesToExclude')}">
64+
<f:textbox name="branchesToExclude" field="branchesToExclude" value="${it.branchesToExclude()}"/>
65+
</f:entry>
66+
5967
<f:entry title="${%Ordered by}">
6068
<select name="order" class="setting-input">
6169
<f:option value="ByName" selected='${it.currentOrder()=="ByName"}'>${%Name}</f:option>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<div>
2+
<p>
3+
The Exclude Branches setting controls which branches of a multi-branch build to hide.
4+
If set, branches whose names match the value will be excluded, even if they match the Include Branches setting.
5+
If not set, no branches are excluded.
6+
</p>
7+
<p>
8+
Matching is done by treating the value as an unanchored regular expression.
9+
</p>
10+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<div>
2+
<p>
3+
The Include Branches setting controls which branches of a multi-branch build to include on the screen.
4+
If set, only branches whose names match the value will be included. If not set, all branches are included.
5+
</p>
6+
<p>
7+
Matching is done by treating the value as an unanchored regular expression.
8+
</p>
9+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package com.smartcodeltd.jenkinsci.plugins.buildmonitor;
2+
3+
import com.cloudbees.hudson.plugins.folder.AbstractFolder;
4+
import hudson.model.ItemGroup;
5+
import hudson.model.Job;
6+
import hudson.model.TopLevelItem;
7+
import org.hamcrest.BaseMatcher;
8+
import org.hamcrest.Description;
9+
import org.hamcrest.Matcher;
10+
import org.junit.Test;
11+
12+
import java.util.Arrays;
13+
import java.util.Collections;
14+
import java.util.List;
15+
16+
import static org.hamcrest.core.Is.is;
17+
import static org.junit.Assert.assertThat;
18+
import static org.mockito.Matchers.anyObject;
19+
import static org.mockito.Mockito.mock;
20+
import static org.mockito.Mockito.when;
21+
22+
public class JobFilterTest {
23+
@Test
24+
public void shouldHandleNull() {
25+
JobFilter filter = configuredJobFilter(null, null);
26+
List<Job<?, ?>> jobs = filter.filterJobs(null);
27+
assertThat("should be empty", jobs.isEmpty(), is(true));
28+
}
29+
30+
@Test
31+
public void shouldHandleFolderWithNullItems() {
32+
JobFilter filter = configuredJobFilter(null, "nope");
33+
final AbstractFolder folder = mockedFolderWithNullItems();
34+
List<Job<?, ?>> jobs = filter.filterJobs(Collections.singleton(folder));
35+
assertThat("should be empty", jobs.isEmpty(), is(true));
36+
}
37+
38+
@Test
39+
public void shouldNotAddStuff() {
40+
JobFilter filter = configuredJobFilter(null, null);
41+
List<Job<?, ?>> jobs = filter.filterJobs(Collections.<TopLevelItem>emptySet());
42+
assertThat("should be empty", jobs.isEmpty(), is(true));
43+
}
44+
45+
@Test
46+
public void shouldCopyDirectJobs() {
47+
JobFilter filter = configuredJobFilter(null, null);
48+
final Job<?, ?> job = mockedJob("foo");
49+
List<Job<?, ?>> jobs = filter.filterJobs(Collections.singleton(job));
50+
assertThat("should contain one element", jobs.size(), is(1));
51+
assertThat(jobs.get(0), jobIsSame(job));
52+
}
53+
54+
@Test
55+
public void shouldIncludeJobsOfFoldersByDefault() {
56+
JobFilter filter = configuredJobFilter(null, null);
57+
final Job<?, ?> job = mockedJob("job");
58+
final AbstractFolder folder = mockedFolder(job);
59+
List<Job<?, ?>> jobs = filter.filterJobs(Collections.singleton(folder));
60+
assertThat("should contain one element", jobs.size(), is(1));
61+
assertThat(jobs.get(0), jobIsSame(job));
62+
}
63+
64+
@Test
65+
public void shouldNotIncludeJobsThatDontMatchTheIncludePattern() {
66+
JobFilter filter = configuredJobFilter("special", null);
67+
final Job<?, ?> job = mockedJob("job");
68+
final AbstractFolder folder = mockedFolder(job);
69+
List<Job<?, ?>> jobs = filter.filterJobs(Collections.singleton(folder));
70+
assertThat("should be empty", jobs.isEmpty(), is(true));
71+
}
72+
73+
@Test
74+
public void shouldExcludeMatchingJobs() {
75+
JobFilter filter = configuredJobFilter(null, "nope");
76+
final Job<?, ?> job = mockedJob("nope");
77+
final AbstractFolder folder = mockedFolder(job);
78+
List<Job<?, ?>> jobs = filter.filterJobs(Collections.singleton(folder));
79+
assertThat("should be empty", jobs.isEmpty(), is(true));
80+
}
81+
82+
@Test
83+
public void excludeShouldWin() {
84+
JobFilter filter = configuredJobFilter("a", "b");
85+
final Job<?, ?> job = mockedJob("ab");
86+
final AbstractFolder folder = mockedFolder(job);
87+
List<Job<?, ?>> jobs = filter.filterJobs(Collections.singleton(folder));
88+
assertThat("should be empty", jobs.isEmpty(), is(true));
89+
}
90+
91+
private Job<?, ?> mockedJob(String relativeName) {
92+
final Job<?, ?> job = mock(Job.class);
93+
when(job.getRelativeNameFrom((ItemGroup) anyObject())).thenReturn(relativeName);
94+
return job;
95+
}
96+
97+
private AbstractFolder mockedFolder(Job<?, ?>... jobs) {
98+
final AbstractFolder folder = mock(AbstractFolder.class);
99+
when(folder.getItems()).thenReturn(Arrays.asList(jobs));
100+
return folder;
101+
}
102+
103+
private AbstractFolder mockedFolderWithNullItems() {
104+
final AbstractFolder folder = mock(AbstractFolder.class);
105+
when(folder.getItems()).thenReturn(null);
106+
return folder;
107+
}
108+
109+
private JobFilter configuredJobFilter(String include, String exclude) {
110+
Config config = new Config();
111+
config.setBranchesToInclude(include);
112+
config.setBranchesToExclude(exclude);
113+
return new JobFilter(config);
114+
}
115+
116+
private static Matcher<Job> jobIsSame(Job job) {
117+
return new JobIsSame(job);
118+
}
119+
120+
static class JobIsSame extends BaseMatcher<Job> {
121+
private final Job<?, ?> job;
122+
123+
JobIsSame(Job<?, ?> job) {
124+
this.job = job;
125+
}
126+
127+
@Override
128+
public void describeTo(Description description) {
129+
description.appendText("object was not the same as the expected one");
130+
}
131+
132+
@Override
133+
public boolean matches(Object item) {
134+
return item == job;
135+
}
136+
}
137+
}

0 commit comments

Comments
 (0)