Skip to content
Open
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"));
}
}
}