Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[JENKINS-47874] Support multiple repositories in multi-branch pipeline #262

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
7 changes: 5 additions & 2 deletions src/main/java/jenkins/branch/Branch.java
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,11 @@ public String getSourceId() {
* @return the name of the branch.
*/
public String getName() {
// TODO this could include a uniquifying prefix defined in BranchSource
return head.getName();
CustomNameBranchProperty customName = (CustomNameBranchProperty)this.properties.stream()
.filter(p -> CustomNameBranchProperty.class.isInstance(p)).findFirst().orElse(null);
return customName != null
? customName.generateName(head.getName())
: head.getName();
}

/**
Expand Down
65 changes: 65 additions & 0 deletions src/main/java/jenkins/branch/CustomNameBranchProperty.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package jenkins.branch;

import hudson.Extension;
import hudson.model.Job;
import hudson.model.Run;
import hudson.util.FormValidation;

import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.verb.POST;

/**
* @author Frédéric Laugier
*/
public class CustomNameBranchProperty extends BranchProperty {

private final String pattern;

@DataBoundConstructor
public CustomNameBranchProperty(String pattern) {
super();

if(!checkValidPattern(pattern)) {
throw new IllegalArgumentException(Messages.CustomNameBranchProperty_InvalidPattern());
}

this.pattern = StringUtils.trimToNull(pattern);
}


public String getPattern() {
return this.pattern;
}

@Override
public <P extends Job<P, B>, B extends Run<P, B>> JobDecorator<P, B> jobDecorator(Class<P> clazz) {
return null;
}

private static boolean checkValidPattern(String pattern) {
String value = StringUtils.trimToNull(pattern);
return value == null || value.contains("{}");
}

String generateName(String name) {
return this.pattern != null ? this.pattern.replaceAll("\\{\\}", name) : name;
}

@Extension
public static class DescriptorImpl extends BranchPropertyDescriptor {

@Override
public String getDisplayName() {
return Messages.CustomNameBranchProperty_DisplayName();
}

@POST
public FormValidation doCheckPattern(StaplerRequest request) {
return checkValidPattern(request.getParameter("value"))
? FormValidation.ok()
: FormValidation.error(Messages.CustomNameBranchProperty_InvalidPattern());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define"
xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">

<f:entry title="${%Pattern.Title}" field="pattern" description="${%Pattern.Description}">
<f:textbox/>
</f:entry>

</j:jelly>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

Pattern.Title = Pattern
Pattern.Description = The required '{}' placeholder will be replaced with the remote branch name
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div>
Used to disambiguate multiple sources that may have overlapping branch names.
</div>
2 changes: 2 additions & 0 deletions src/main/resources/jenkins/branch/Messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
#
BaseEmptyView.displayName=Welcome
BranchStatusColumn.displayName=Status
CustomNameBranchProperty.DisplayName=Customize local branch name
CustomNameBranchProperty.InvalidPattern=Your pattern must include the '{}' placeholder
DefaultBranchPropertyStrategy.DisplayName=All branches get the same properties
DescriptionColumn.displayName=Project description
ItemColumn.DisplayName=Name
Expand Down
139 changes: 139 additions & 0 deletions src/test/java/jenkins/branch/CustomNameBranchPropertyTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package jenkins.branch;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;

import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;

import hudson.model.FreeStyleProject;
import hudson.model.TopLevelItem;
import integration.harness.BasicBranchProperty;
import integration.harness.BasicMultiBranchProject;
import jenkins.scm.impl.mock.MockSCMController;
import jenkins.scm.impl.mock.MockSCMDiscoverBranches;
import jenkins.scm.impl.mock.MockSCMSource;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;

/**
* @author Frédéric Laugier
*/
public class CustomNameBranchPropertyTest {
/**
* All tests in this class only create items and do not affect other global configuration, thus we trade test
* execution time for the restriction on only touching items.
*/
@ClassRule
public static JenkinsRule r = new JenkinsRule();

@Before
public void cleanOutAllItems() throws Exception {
for (TopLevelItem i : r.getInstance().getItems()) {
i.delete();
}
}

@Test
public void patternValues() throws Exception {
assertThat(new CustomNameBranchProperty(null).getPattern(), nullValue());
assertThat(new CustomNameBranchProperty("").getPattern(), nullValue());
assertThat(new CustomNameBranchProperty(" ").getPattern(), nullValue());
assertThat(new CustomNameBranchProperty("app-{}").getPattern(), is("app-{}"));
assertThrows(IllegalArgumentException.class, () -> new CustomNameBranchProperty("foobar") );
}

@Test
public void defaultName() throws Exception {
try (final MockSCMController c = MockSCMController.create()) {
c.createRepository("foo");
BasicMultiBranchProject prj = r.jenkins.createProject(BasicMultiBranchProject.class, "foo");
prj.setCriteria(null);
BranchSource source = new BranchSource(new MockSCMSource(c, "foo", new MockSCMDiscoverBranches()));
source.setStrategy(new DefaultBranchPropertyStrategy(new BranchProperty[]{
new CustomNameBranchProperty(null)
}));
prj.getSourcesList().add(source);
prj.scheduleBuild2(0).getFuture().get();
r.waitUntilNoActivity();

FreeStyleProject master = prj.getItem("master");
assertNotNull(master);
assertNotNull(master.getProperty(BasicBranchProperty.class));

Branch branch = master.getProperty(BasicBranchProperty.class).getBranch();
assertNotNull(branch);
assertNotNull(branch.getProperty(CustomNameBranchProperty.class));
}
}

@Test
public void customName() throws Exception {
try (final MockSCMController c = MockSCMController.create()) {
c.createRepository("foo");
BasicMultiBranchProject prj = r.jenkins.createProject(BasicMultiBranchProject.class, "foo");
prj.setCriteria(null);
BranchSource source = new BranchSource(new MockSCMSource(c, "foo", new MockSCMDiscoverBranches()));
source.setStrategy(new DefaultBranchPropertyStrategy(new BranchProperty[]{
new CustomNameBranchProperty("app-{}")
}));
prj.getSourcesList().add(source);
prj.scheduleBuild2(0).getFuture().get();
r.waitUntilNoActivity();

assertNotNull(prj.getItem("app-master"));
assertNull(prj.getItem("master"));
}
}

@Test
public void multipleReplacement() throws Exception {
try (final MockSCMController c = MockSCMController.create()) {
c.createRepository("foo");
BasicMultiBranchProject prj = r.jenkins.createProject(BasicMultiBranchProject.class, "foo");
prj.setCriteria(null);
BranchSource source = new BranchSource(new MockSCMSource(c, "foo", new MockSCMDiscoverBranches()));
source.setStrategy(new DefaultBranchPropertyStrategy(new BranchProperty[]{
new CustomNameBranchProperty("app-{}/{}")
}));
prj.getSourcesList().add(source);
prj.scheduleBuild2(0).getFuture().get();
r.waitUntilNoActivity();

assertNotNull(prj.getItem("app-master/master"));
assertNull(prj.getItem("master"));
assertNull(prj.getItem("app-master"));
assertNull(prj.getItem("master/master"));
}
}
@Test
public void customNameWithMultipleSources() throws Exception {
try (final MockSCMController c = MockSCMController.create()) {
c.createRepository("foo");
c.createRepository("bar");
BasicMultiBranchProject prj = r.jenkins.createProject(BasicMultiBranchProject.class, "foobar");
prj.setCriteria(null);
BranchSource sourceFoo = new BranchSource(new MockSCMSource(c, "foo", new MockSCMDiscoverBranches()));
sourceFoo.setStrategy(new DefaultBranchPropertyStrategy(new BranchProperty[]{
new CustomNameBranchProperty("foo-{}")
}));
BranchSource sourceBar = new BranchSource(new MockSCMSource(c, "bar", new MockSCMDiscoverBranches()));
sourceBar.setStrategy(new DefaultBranchPropertyStrategy(new BranchProperty[]{
new CustomNameBranchProperty("bar-{}")
}));
prj.getSourcesList().add(sourceFoo);
prj.getSourcesList().add(sourceBar);
prj.scheduleBuild2(0).getFuture().get();
r.waitUntilNoActivity();

assertNotNull(prj.getItem("foo-master"));
assertNotNull(prj.getItem("bar-master"));
assertNull(prj.getItem("master"));
}
}
}