Skip to content

Commit d92fdd5

Browse files
committed
Add option to always include branches, regardless of whether a pull request exists for those branches or not.
1 parent 4718b9b commit d92fdd5

File tree

4 files changed

+287
-4
lines changed

4 files changed

+287
-4
lines changed

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/trait/BranchDiscoveryTrait.java

Lines changed: 139 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,17 @@
2929
import com.cloudbees.jenkins.plugins.bitbucket.Messages;
3030
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequest;
3131
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepository;
32+
import edu.umd.cs.findbugs.annotations.CheckForNull;
3233
import edu.umd.cs.findbugs.annotations.NonNull;
3334
import hudson.Extension;
35+
import hudson.Util;
36+
import hudson.model.Item;
37+
import hudson.util.FormValidation;
3438
import hudson.util.ListBoxModel;
3539
import java.io.IOException;
40+
import java.util.regex.Pattern;
41+
import java.util.regex.PatternSyntaxException;
42+
import jenkins.model.Jenkins;
3643
import jenkins.scm.api.SCMHead;
3744
import jenkins.scm.api.SCMHeadCategory;
3845
import jenkins.scm.api.SCMHeadOrigin;
@@ -48,7 +55,11 @@
4855
import org.jenkinsci.Symbol;
4956
import org.kohsuke.accmod.Restricted;
5057
import org.kohsuke.accmod.restrictions.NoExternalUse;
58+
import org.kohsuke.stapler.AncestorInPath;
5159
import org.kohsuke.stapler.DataBoundConstructor;
60+
import org.kohsuke.stapler.DataBoundSetter;
61+
import org.kohsuke.stapler.QueryParameter;
62+
import org.kohsuke.stapler.interceptor.RequirePOST;
5263

5364
/**
5465
* A {@link Discovery} trait for bitbucket that will discover branches on the repository.
@@ -67,6 +78,17 @@ public class BranchDiscoveryTrait extends SCMSourceTrait {
6778
*/
6879
private final int strategyId;
6980

81+
/**
82+
* Regex of branches that should always be included regardless of whether a merge request exists or not.
83+
*/
84+
private String branchesAlwaysIncludedRegex;
85+
86+
/**
87+
* The compiled {@link Pattern} of the branchesAlwaysIncludedRegex.
88+
*/
89+
@CheckForNull
90+
private transient Pattern branchesAlwaysIncludedRegexPattern;
91+
7092
/**
7193
* Constructor for stapler.
7294
*
@@ -96,6 +118,36 @@ public int getStrategyId() {
96118
return strategyId;
97119
}
98120

121+
/**
122+
* Returns the branchesAlwaysIncludedRegex.
123+
*
124+
* @return the branchesAlwaysIncludedRegex.
125+
*/
126+
public String getBranchesAlwaysIncludedRegex() {
127+
return branchesAlwaysIncludedRegex;
128+
}
129+
130+
/**
131+
* Sets the branchesAlwaysIncludedRegex.
132+
*/
133+
@DataBoundSetter
134+
public void setBranchesAlwaysIncludedRegex(@CheckForNull String branchesAlwaysIncludedRegex) {
135+
this.branchesAlwaysIncludedRegex = Util.fixEmptyAndTrim(branchesAlwaysIncludedRegex);
136+
}
137+
138+
/**
139+
* Returns the compiled {@link Pattern} of the branchesAlwaysIncludedRegex.
140+
*
141+
* @return the branchesAlwaysIncludedRegexPattern.
142+
*/
143+
public Pattern getBranchesAlwaysIncludedRegexPattern() {
144+
if (branchesAlwaysIncludedRegex != null && branchesAlwaysIncludedRegexPattern == null) {
145+
branchesAlwaysIncludedRegexPattern = Pattern.compile(branchesAlwaysIncludedRegex);
146+
}
147+
148+
return branchesAlwaysIncludedRegexPattern;
149+
}
150+
99151
/**
100152
* Returns {@code true} if building branches that are not filed as a PR.
101153
*
@@ -127,11 +179,11 @@ protected void decorateContext(SCMSourceContext<?, ?> context) {
127179
switch (strategyId) {
128180
case 1:
129181
ctx.wantOriginPRs(true);
130-
ctx.withFilter(new ExcludeOriginPRBranchesSCMHeadFilter());
182+
ctx.withFilter(new ExcludeOriginPRBranchesSCMHeadFilter(getBranchesAlwaysIncludedRegexPattern()));
131183
break;
132184
case 2:
133185
ctx.wantOriginPRs(true);
134-
ctx.withFilter(new OnlyOriginPRBranchesSCMHeadFilter());
186+
ctx.withFilter(new OnlyOriginPRBranchesSCMHeadFilter(getBranchesAlwaysIncludedRegexPattern()));
135187
break;
136188
case 3:
137189
default:
@@ -179,6 +231,27 @@ public ListBoxModel doFillStrategyIdItems() {
179231
result.add(Messages.BranchDiscoveryTrait_allBranches(), "3");
180232
return result;
181233
}
234+
235+
@NonNull
236+
@Restricted(NoExternalUse.class)
237+
@RequirePOST
238+
public FormValidation doCheckBranchesAlwaysIncludedRegex(@CheckForNull @AncestorInPath Item context, @QueryParameter String value) {
239+
if(context == null) {
240+
Jenkins.get().checkPermission(Jenkins.MANAGE);
241+
} else {
242+
context.checkPermission(Item.CONFIGURE);
243+
}
244+
245+
if (value == null || value.isBlank()) {
246+
return FormValidation.ok();
247+
}
248+
try {
249+
Pattern.compile(value);
250+
return FormValidation.ok();
251+
} catch (PatternSyntaxException ex) {
252+
return FormValidation.error(ex.getMessage());
253+
}
254+
}
182255
}
183256

184257
/**
@@ -220,12 +293,41 @@ public boolean isApplicableToOrigin(@NonNull Class<? extends SCMHeadOrigin> orig
220293
* Filter that excludes branches that are also filed as a pull request.
221294
*/
222295
public static class ExcludeOriginPRBranchesSCMHeadFilter extends SCMHeadFilter {
296+
297+
/**
298+
* The compiled {@link Pattern} of the branchesAlwaysIncludedRegex.
299+
*/
300+
private final Pattern branchesAlwaysIncludedRegexPattern;
301+
302+
public ExcludeOriginPRBranchesSCMHeadFilter() {
303+
branchesAlwaysIncludedRegexPattern = null;
304+
}
305+
306+
/**
307+
* Constructor
308+
*
309+
* @param branchesAlwaysIncludedRegexPattern the branchesAlwaysIncludedRegexPattern.
310+
*/
311+
public ExcludeOriginPRBranchesSCMHeadFilter(Pattern branchesAlwaysIncludedRegexPattern) {
312+
this.branchesAlwaysIncludedRegexPattern = branchesAlwaysIncludedRegexPattern;
313+
}
314+
223315
/**
224316
* {@inheritDoc}
225317
*/
226318
@Override
227319
public boolean isExcluded(@NonNull SCMSourceRequest request, @NonNull SCMHead head) {
228320
if (head instanceof BranchSCMHead && request instanceof BitbucketSCMSourceRequest) {
321+
if (branchesAlwaysIncludedRegexPattern != null
322+
&& branchesAlwaysIncludedRegexPattern
323+
.matcher(head.getName())
324+
.matches()) {
325+
request.listener()
326+
.getLogger()
327+
.println("Include branch " + head.getName()
328+
+ " because branch name matches always included pattern");
329+
return false;
330+
}
229331
BitbucketSCMSourceRequest req = (BitbucketSCMSourceRequest) request;
230332
String fullName = req.getRepoOwner() + "/" + req.getRepository();
231333
try {
@@ -251,12 +353,42 @@ public boolean isExcluded(@NonNull SCMSourceRequest request, @NonNull SCMHead he
251353
* Filter that excludes branches that are not also filed as a pull request.
252354
*/
253355
public static class OnlyOriginPRBranchesSCMHeadFilter extends SCMHeadFilter {
356+
357+
/**
358+
* The compiled {@link Pattern} of the branchesAlwaysIncludedRegex.
359+
*/
360+
private final Pattern branchesAlwaysIncludedRegexPattern;
361+
362+
public OnlyOriginPRBranchesSCMHeadFilter() {
363+
branchesAlwaysIncludedRegexPattern = null;
364+
}
365+
366+
/**
367+
* Constructor
368+
*
369+
* @param branchesAlwaysIncludedRegexPattern the branchesAlwaysIncludedRegexPattern.
370+
*/
371+
public OnlyOriginPRBranchesSCMHeadFilter(Pattern branchesAlwaysIncludedRegexPattern) {
372+
this.branchesAlwaysIncludedRegexPattern = branchesAlwaysIncludedRegexPattern;
373+
}
374+
254375
/**
255376
* {@inheritDoc}
256377
*/
257378
@Override
258379
public boolean isExcluded(@NonNull SCMSourceRequest request, @NonNull SCMHead head) {
259380
if (head instanceof BranchSCMHead && request instanceof BitbucketSCMSourceRequest) {
381+
if (branchesAlwaysIncludedRegexPattern != null
382+
&& branchesAlwaysIncludedRegexPattern
383+
.matcher(head.getName())
384+
.matches()) {
385+
request.listener()
386+
.getLogger()
387+
.println("Include branch " + head.getName()
388+
+ " because branch name matches always included pattern");
389+
return false;
390+
}
391+
260392
BitbucketSCMSourceRequest req = (BitbucketSCMSourceRequest) request;
261393
String fullName = req.getRepoOwner() + "/" + req.getRepository();
262394
try {
@@ -267,8 +399,11 @@ public boolean isExcluded(@NonNull SCMSourceRequest request, @NonNull SCMHead he
267399
return false;
268400
}
269401
}
270-
request.listener().getLogger().println("Discard branch " + head.getName()
271-
+ " because current strategy excludes branches that are not also filed as a pull request");
402+
request.listener()
403+
.getLogger()
404+
.println(
405+
"Discard branch " + head.getName()
406+
+ " because current strategy excludes branches that are not also filed as a pull request");
272407
return true;
273408
} catch (IOException | InterruptedException e) {
274409
// should never happens because data in the requests has been already initialised

src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/trait/BranchDiscoveryTrait/config.jelly

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,7 @@
55
<f:entry title="${%Strategy}" field="strategyId">
66
<f:select default="1"/>
77
</f:entry>
8+
<f:entry title="${%Branches to always include}" field="branchesAlwaysIncludedRegex">
9+
<f:textbox/>
10+
</f:entry>
811
</j:jelly>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<div>
2+
Regular expression of branches that should always be included regardless of whether a pull request exists or not for those branches.
3+
</div>

0 commit comments

Comments
 (0)