Skip to content

Commit

Permalink
MultiBranch exclude filter.
Browse files Browse the repository at this point in the history
A regular expression exclude filter for Swarm branches, Stream names and Branch paths.

JENKINS-58346
JENKINS-63625
  • Loading branch information
p4paul committed Sep 13, 2021
1 parent b3847f2 commit 3a44d13
Show file tree
Hide file tree
Showing 12 changed files with 182 additions and 3 deletions.
Expand Up @@ -56,11 +56,15 @@ public abstract class AbstractP4ScmSource extends SCMSource {

private static Logger logger = Logger.getLogger(AbstractP4ScmSource.class.getName());

public static final String defaultExcludes = "a^"; // matches nothing

protected final String credential;

private List<SCMSourceTrait> traits = new ArrayList<>();

private String includes;
private String excludes = defaultExcludes;

private String charset;
private String format;
private Populate populate;
Expand All @@ -85,6 +89,11 @@ public void setIncludes(String includes) {
this.includes = includes;
}

@DataBoundSetter
public void setExcludes(String excludes) {
this.excludes = excludes;
}

@DataBoundSetter
public void setCharset(String charset) {
this.charset = charset;
Expand Down Expand Up @@ -115,6 +124,13 @@ public String getIncludes() {
return includes;
}

public String getExcludes() {
if (excludes == null || excludes.isEmpty()) {
return defaultExcludes;
}
return excludes;
}

public String getCharset() {
return charset;
}
Expand Down
Expand Up @@ -73,6 +73,7 @@ public List<P4SCMHead> getTags(@NonNull TaskListener listener) throws Exception
@Override
public List<P4SCMHead> getHeads(@NonNull TaskListener listener) throws Exception {

Pattern excludesPattern = Pattern.compile(getExcludes());
List<String> paths = getIncludePaths();
List<P4SCMHead> list = new ArrayList<>();

Expand All @@ -97,6 +98,11 @@ public List<P4SCMHead> getHeads(@NonNull TaskListener listener) throws Exception
continue;
}

// check the excludes
if (excludesPattern.matcher(branch).matches()) {
continue;
}

// get filename and check for null
String file = branch.substring(branch.lastIndexOf("/") + 1);
if (file == null || file.isEmpty()) {
Expand Down Expand Up @@ -125,7 +131,7 @@ public Workspace getWorkspace(P4Path path) {

// If there is only one view for external/local then external is treated as local
boolean external = false;
if((externalViews.size() + localViews.size()) > 1) {
if ((externalViews.size() + localViews.size()) > 1) {
external = true;
}

Expand Down
10 changes: 10 additions & 0 deletions src/main/java/org/jenkinsci/plugins/p4/scm/StreamsScmSource.java
Expand Up @@ -15,6 +15,7 @@
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.regex.Pattern;

public class StreamsScmSource extends AbstractP4ScmSource {

Expand Down Expand Up @@ -49,9 +50,18 @@ public List<P4SCMHead> getHeads(@NonNull TaskListener listener) throws Exception
HashSet<P4SCMHead> list = new HashSet<P4SCMHead>();

try (ConnectionHelper p4 = new ConnectionHelper(getOwner(), credential, listener)) {

Pattern excludesPattern = Pattern.compile(getExcludes());

List<IStreamSummary> specs = p4.getStreams(paths);
for (IStreamSummary s : specs) {
String name = s.getName();

// check the excludes
if (excludesPattern.matcher(name).matches()) {
continue;
}

String stream = s.getStream();
P4Path p4Path = new P4Path(stream);
P4SCMHead head = new P4SCMHead(name, p4Path);
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/org/jenkinsci/plugins/p4/scm/SwarmScmSource.java
Expand Up @@ -40,6 +40,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.logging.Logger;
import java.util.regex.Pattern;

import static org.jenkinsci.plugins.p4.review.ReviewProp.P4_CHANGE;
import static org.jenkinsci.plugins.p4.review.ReviewProp.SWARM_BRANCH;
Expand Down Expand Up @@ -117,6 +118,8 @@ protected List<Action> retrieveActions(SCMHead head, SCMHeadEvent event, TaskLis
@Override
public List<P4SCMHead> getTags(@NonNull TaskListener listener) throws Exception {

Pattern excludesPattern = Pattern.compile(getExcludes());

List<P4SCMHead> list = new ArrayList<>();

List<SwarmReviewsAPI.Reviews> reviews = getSwarm().getActiveReviews(project);
Expand All @@ -125,6 +128,11 @@ public List<P4SCMHead> getTags(@NonNull TaskListener listener) throws Exception

List<String> branches = getBranchesInReview(reviewID, project);
for (String branch : branches) {
// check the excludes
if (excludesPattern.matcher(branch).matches()) {
continue;
}

// Get first Swarm path; it MUST include the Jenkinsfile
P4Path p4Path = getPathsInBranch(branch, project);
if (p4Path != null) {
Expand All @@ -143,10 +151,18 @@ public List<P4SCMHead> getTags(@NonNull TaskListener listener) throws Exception
@Override
public List<P4SCMHead> getHeads(@NonNull TaskListener listener) throws Exception {

Pattern excludesPattern = Pattern.compile(getExcludes());

List<P4SCMHead> list = new ArrayList<>();

List<SwarmProjectAPI.Branch> branches = getSwarm().getBranchesInProject(project);
for (SwarmProjectAPI.Branch branch : branches) {

// check the excludes
if (excludesPattern.matcher(branch.getName()).matches()) {
continue;
}

// Get first Swarm path; it MUST include the Jenkinsfile
P4Path p4Path = branch.getPath();

Expand Down
Expand Up @@ -16,6 +16,10 @@
</f:entry>

<f:advanced>
<f:entry title="Exclude Pattern" field="excludes">
<f:textbox default="${descriptor.defaultExcludes}"/>
</f:entry>

<f:entry title="${%Character Set}" field="charset">
<f:select default="none"/>
</f:entry>
Expand Down
@@ -0,0 +1,3 @@
<div>
<p>A regular expression to exclude matching depot paths (processed after the 'Include Filter')</p>
</div>
@@ -1,4 +1,4 @@
<div>
<h2>BETA</h2>
<p>Only supports 'Polling per Change' option.></p>
<p>Only supports 'Polling per Change' option.</p>
</div>
Expand Up @@ -12,6 +12,10 @@
</f:entry>

<f:advanced>
<f:entry title="Exclude Pattern" field="excludes">
<f:textbox default="${descriptor.defaultExcludes}"/>
</f:entry>

<f:entry title="${%Character Set}" field="charset">
<f:select default="none"/>
</f:entry>
Expand Down
@@ -0,0 +1,3 @@
<div>
<p>A regular expression to exclude matching Stream names found from 'Include streams'.</p>
</div>
Expand Up @@ -8,10 +8,14 @@
</f:entry>

<f:entry title="${%Swarm Project}" field="project">
<f:textbox/>
<f:textbox/>
</f:entry>

<f:advanced>
<f:entry title="Exclude Pattern" field="excludes">
<f:textbox default="${descriptor.defaultExcludes}"/>
</f:entry>

<f:entry title="${%Character Set}" field="charset">
<f:select default="none"/>
</f:entry>
Expand Down
@@ -0,0 +1,3 @@
<div>
<p>A regular expression to exclude matching branch names with in the Swarm project</p>
</div>
110 changes: 110 additions & 0 deletions src/test/java/org/jenkinsci/plugins/p4/scm/PerforceSCMSourceTest.java
Expand Up @@ -58,6 +58,7 @@
import static org.hamcrest.core.IsNull.notNullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
Expand Down Expand Up @@ -145,6 +146,59 @@ public void testMultiBranchWithClassic() throws Exception {
assertThat("The branch was built", build.getNumber(), is(1));
}

@Test
public void testExcludesWithClassic() throws Exception {

String project = "excludeClassic";
String base = "//depot/" + project;
String[] branches = new String[]{"br1", "br2"};

sampleProject(base, branches, "Jenkinsfile");

String format = "jenkins-${NODE_NAME}-${JOB_NAME}";
String includes = base + "/...";
BranchesScmSource source = new BranchesScmSource(CREDENTIAL, includes, null, format);
source.setExcludes(".*2");

WorkflowMultiBranchProject multi = jenkins.jenkins.createProject(WorkflowMultiBranchProject.class, project);
multi.getSourcesList().add(new BranchSource(source));
multi.scheduleBuild2(0);
jenkins.waitUntilNoActivity();

assertThat("We now have branches", multi.getItems(), not(containsInAnyOrder()));
assertNotNull(multi.getItem("br1"));
assertEquals(1, multi.getItems().size());
}

@Test
public void testExcludesWithSwarm() throws Exception {

String project = "excludeSwarm";
String base = "//depot/" + project;
String[] branches = new String[]{"br1", "br2"};

sampleProject(base, branches, "Jenkinsfile");

SwarmHelper mockSwarm = sampleSwarmProject(project, base, branches);
assertNotNull(mockSwarm);

String format = "jenkins-${NODE_NAME}-${JOB_NAME}";
SwarmScmSource source = new SwarmScmSource(CREDENTIAL, null, format);
source.setProject(project);
source.setExcludes(".*2");
source.setSwarm(mockSwarm);
source.setPopulate(new AutoCleanImpl());

WorkflowMultiBranchProject multi = jenkins.jenkins.createProject(WorkflowMultiBranchProject.class, project);
multi.getSourcesList().add(new BranchSource(source));
multi.scheduleBuild2(0);
jenkins.waitUntilNoActivity();

assertThat("We now have branches", multi.getItems(), not(containsInAnyOrder()));
assertNotNull(multi.getItem("br1"));
assertEquals(1, multi.getItems().size());
}

@Test
public void testNoMultiStreams() throws Exception {

Expand Down Expand Up @@ -205,6 +259,62 @@ public void testWildPathStreams() throws Exception {
assertThat("We now have branches", multi.getItems(), not(containsInAnyOrder()));
}

@Test
public void testExcludesStreams() throws Exception {

WorkflowMultiBranchProject multi = jenkins.jenkins.createProject(WorkflowMultiBranchProject.class, "excludes-streams");

CredentialsStore folderStore = getFolderStore(multi);
P4BaseCredentials inFolderCredentials = new P4PasswordImpl(
CredentialsScope.GLOBAL, "idInFolder", "desc:passwd", p4d.getRshPort(),
null, "jenkins", "0", "0", null, "jenkins");
folderStore.addCredentials(Domain.global(), inFolderCredentials);

// Get a connection
ConnectionHelper p4 = new ConnectionHelper(inFolderCredentials);
IOptionsServer server = p4.getConnection();

// create a Mainline stream
IStream stream = new Stream();
stream.setOwnerName(server.getUserName());
stream.setStream("//stream/Acme-main");
stream.setName("Acme-main");
stream.setType(IStreamSummary.Type.MAINLINE);

// add a view mapping
ViewMap<IStreamViewMapping> streamView = new ViewMap<>();
streamView.addEntry(new Stream.StreamViewMapping(0, IStreamViewMapping.PathType.SHARE, "...", null));
stream.setStreamView(streamView);
server.createStream(stream);

// Create a Jenkinsfile
String pipeline = ""
+ "pipeline {\n"
+ " agent any\n"
+ " stages {\n"
+ " stage('Test') {\n"
+ " steps {\n"
+ " echo \"Hello\"\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}";
submitStreamFile(jenkins, "//stream/Acme-main/Jenkinsfile", pipeline, "description");

String format = "jenkins-${NODE_NAME}-${JOB_NAME}";
String includes = "//stream/...";
StreamsScmSource source = new StreamsScmSource(CREDENTIAL, includes, null, format);
source.setExcludes("Ace.*");

multi.getSourcesList().add(new BranchSource(source));
multi.scheduleBuild2(0);
jenkins.waitUntilNoActivity();

assertThat("We now have branches", multi.getItems(), not(containsInAnyOrder()));
assertNotNull(multi.getItem("Acme-main"));
assertEquals(1, multi.getItems().size());
}

@Test
public void testSimplePathClassic() throws Exception {

Expand Down

0 comments on commit 3a44d13

Please sign in to comment.