diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/trait/BranchDiscoveryTrait.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/trait/BranchDiscoveryTrait.java
index 0f92db212..a7ce6e0e5 100644
--- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/trait/BranchDiscoveryTrait.java
+++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/trait/BranchDiscoveryTrait.java
@@ -29,10 +29,17 @@
import com.cloudbees.jenkins.plugins.bitbucket.Messages;
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequest;
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepository;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
+import hudson.Util;
+import hudson.model.Item;
+import hudson.util.FormValidation;
import hudson.util.ListBoxModel;
import java.io.IOException;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+import jenkins.model.Jenkins;
import jenkins.scm.api.SCMHead;
import jenkins.scm.api.SCMHeadCategory;
import jenkins.scm.api.SCMHeadOrigin;
@@ -48,7 +55,11 @@
import org.jenkinsci.Symbol;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
+import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.DataBoundConstructor;
+import org.kohsuke.stapler.DataBoundSetter;
+import org.kohsuke.stapler.QueryParameter;
+import org.kohsuke.stapler.interceptor.RequirePOST;
/**
* A {@link Discovery} trait for bitbucket that will discover branches on the repository.
@@ -67,6 +78,17 @@ public class BranchDiscoveryTrait extends SCMSourceTrait {
*/
private final int strategyId;
+ /**
+ * Regex of branches that should always be included regardless of whether a merge request exists or not.
+ */
+ private String branchesAlwaysIncludedRegex;
+
+ /**
+ * The compiled {@link Pattern} of the branchesAlwaysIncludedRegex.
+ */
+ @CheckForNull
+ private transient Pattern branchesAlwaysIncludedRegexPattern;
+
/**
* Constructor for stapler.
*
@@ -96,6 +118,36 @@ public int getStrategyId() {
return strategyId;
}
+ /**
+ * Returns the branchesAlwaysIncludedRegex.
+ *
+ * @return the branchesAlwaysIncludedRegex.
+ */
+ public String getBranchesAlwaysIncludedRegex() {
+ return branchesAlwaysIncludedRegex;
+ }
+
+ /**
+ * Sets the branchesAlwaysIncludedRegex.
+ */
+ @DataBoundSetter
+ public void setBranchesAlwaysIncludedRegex(@CheckForNull String branchesAlwaysIncludedRegex) {
+ this.branchesAlwaysIncludedRegex = Util.fixEmptyAndTrim(branchesAlwaysIncludedRegex);
+ }
+
+ /**
+ * Returns the compiled {@link Pattern} of the branchesAlwaysIncludedRegex.
+ *
+ * @return the branchesAlwaysIncludedRegexPattern.
+ */
+ public Pattern getBranchesAlwaysIncludedRegexPattern() {
+ if (branchesAlwaysIncludedRegex != null && branchesAlwaysIncludedRegexPattern == null) {
+ branchesAlwaysIncludedRegexPattern = Pattern.compile(branchesAlwaysIncludedRegex);
+ }
+
+ return branchesAlwaysIncludedRegexPattern;
+ }
+
/**
* Returns {@code true} if building branches that are not filed as a PR.
*
@@ -127,11 +179,11 @@ protected void decorateContext(SCMSourceContext, ?> context) {
switch (strategyId) {
case 1:
ctx.wantOriginPRs(true);
- ctx.withFilter(new ExcludeOriginPRBranchesSCMHeadFilter());
+ ctx.withFilter(new ExcludeOriginPRBranchesSCMHeadFilter(getBranchesAlwaysIncludedRegexPattern()));
break;
case 2:
ctx.wantOriginPRs(true);
- ctx.withFilter(new OnlyOriginPRBranchesSCMHeadFilter());
+ ctx.withFilter(new OnlyOriginPRBranchesSCMHeadFilter(getBranchesAlwaysIncludedRegexPattern()));
break;
case 3:
default:
@@ -179,6 +231,27 @@ public ListBoxModel doFillStrategyIdItems() {
result.add(Messages.BranchDiscoveryTrait_allBranches(), "3");
return result;
}
+
+ @NonNull
+ @Restricted(NoExternalUse.class)
+ @RequirePOST
+ public FormValidation doCheckBranchesAlwaysIncludedRegex(@CheckForNull @AncestorInPath Item context, @QueryParameter String value) {
+ if(context == null) {
+ Jenkins.get().checkPermission(Jenkins.MANAGE);
+ } else {
+ context.checkPermission(Item.CONFIGURE);
+ }
+
+ if (value == null || value.isBlank()) {
+ return FormValidation.ok();
+ }
+ try {
+ Pattern.compile(value);
+ return FormValidation.ok();
+ } catch (PatternSyntaxException ex) {
+ return FormValidation.error(ex.getMessage());
+ }
+ }
}
/**
@@ -220,12 +293,41 @@ public boolean isApplicableToOrigin(@NonNull Class extends SCMHeadOrigin> orig
* Filter that excludes branches that are also filed as a pull request.
*/
public static class ExcludeOriginPRBranchesSCMHeadFilter extends SCMHeadFilter {
+
+ /**
+ * The compiled {@link Pattern} of the branchesAlwaysIncludedRegex.
+ */
+ private final Pattern branchesAlwaysIncludedRegexPattern;
+
+ public ExcludeOriginPRBranchesSCMHeadFilter() {
+ branchesAlwaysIncludedRegexPattern = null;
+ }
+
+ /**
+ * Constructor
+ *
+ * @param branchesAlwaysIncludedRegexPattern the branchesAlwaysIncludedRegexPattern.
+ */
+ public ExcludeOriginPRBranchesSCMHeadFilter(Pattern branchesAlwaysIncludedRegexPattern) {
+ this.branchesAlwaysIncludedRegexPattern = branchesAlwaysIncludedRegexPattern;
+ }
+
/**
* {@inheritDoc}
*/
@Override
public boolean isExcluded(@NonNull SCMSourceRequest request, @NonNull SCMHead head) {
if (head instanceof BranchSCMHead && request instanceof BitbucketSCMSourceRequest) {
+ if (branchesAlwaysIncludedRegexPattern != null
+ && branchesAlwaysIncludedRegexPattern
+ .matcher(head.getName())
+ .matches()) {
+ request.listener()
+ .getLogger()
+ .println("Include branch " + head.getName()
+ + " because branch name matches always included pattern");
+ return false;
+ }
BitbucketSCMSourceRequest req = (BitbucketSCMSourceRequest) request;
String fullName = req.getRepoOwner() + "/" + req.getRepository();
try {
@@ -251,12 +353,42 @@ public boolean isExcluded(@NonNull SCMSourceRequest request, @NonNull SCMHead he
* Filter that excludes branches that are not also filed as a pull request.
*/
public static class OnlyOriginPRBranchesSCMHeadFilter extends SCMHeadFilter {
+
+ /**
+ * The compiled {@link Pattern} of the branchesAlwaysIncludedRegex.
+ */
+ private final Pattern branchesAlwaysIncludedRegexPattern;
+
+ public OnlyOriginPRBranchesSCMHeadFilter() {
+ branchesAlwaysIncludedRegexPattern = null;
+ }
+
+ /**
+ * Constructor
+ *
+ * @param branchesAlwaysIncludedRegexPattern the branchesAlwaysIncludedRegexPattern.
+ */
+ public OnlyOriginPRBranchesSCMHeadFilter(Pattern branchesAlwaysIncludedRegexPattern) {
+ this.branchesAlwaysIncludedRegexPattern = branchesAlwaysIncludedRegexPattern;
+ }
+
/**
* {@inheritDoc}
*/
@Override
public boolean isExcluded(@NonNull SCMSourceRequest request, @NonNull SCMHead head) {
if (head instanceof BranchSCMHead && request instanceof BitbucketSCMSourceRequest) {
+ if (branchesAlwaysIncludedRegexPattern != null
+ && branchesAlwaysIncludedRegexPattern
+ .matcher(head.getName())
+ .matches()) {
+ request.listener()
+ .getLogger()
+ .println("Include branch " + head.getName()
+ + " because branch name matches always included pattern");
+ return false;
+ }
+
BitbucketSCMSourceRequest req = (BitbucketSCMSourceRequest) request;
String fullName = req.getRepoOwner() + "/" + req.getRepository();
try {
@@ -267,8 +399,11 @@ public boolean isExcluded(@NonNull SCMSourceRequest request, @NonNull SCMHead he
return false;
}
}
- request.listener().getLogger().println("Discard branch " + head.getName()
- + " because current strategy excludes branches that are not also filed as a pull request");
+ request.listener()
+ .getLogger()
+ .println(
+ "Discard branch " + head.getName()
+ + " because current strategy excludes branches that are not also filed as a pull request");
return true;
} catch (IOException | InterruptedException e) {
// should never happens because data in the requests has been already initialised
diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/trait/BranchDiscoveryTrait/config.jelly b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/trait/BranchDiscoveryTrait/config.jelly
index 3076844a2..59ad933da 100644
--- a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/trait/BranchDiscoveryTrait/config.jelly
+++ b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/trait/BranchDiscoveryTrait/config.jelly
@@ -5,4 +5,7 @@