diff --git a/src/main/java/jenkins/branch/Branch.java b/src/main/java/jenkins/branch/Branch.java index 05498c47..72c27b3b 100644 --- a/src/main/java/jenkins/branch/Branch.java +++ b/src/main/java/jenkins/branch/Branch.java @@ -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(); } /** diff --git a/src/main/java/jenkins/branch/CustomNameBranchProperty.java b/src/main/java/jenkins/branch/CustomNameBranchProperty.java new file mode 100644 index 00000000..6488e710 --- /dev/null +++ b/src/main/java/jenkins/branch/CustomNameBranchProperty.java @@ -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

, B extends Run> JobDecorator jobDecorator(Class

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()); + } + } +} diff --git a/src/main/resources/jenkins/branch/CustomNameBranchProperty/config.jelly b/src/main/resources/jenkins/branch/CustomNameBranchProperty/config.jelly new file mode 100644 index 00000000..4c5937c0 --- /dev/null +++ b/src/main/resources/jenkins/branch/CustomNameBranchProperty/config.jelly @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/src/main/resources/jenkins/branch/CustomNameBranchProperty/config.properties b/src/main/resources/jenkins/branch/CustomNameBranchProperty/config.properties new file mode 100644 index 00000000..b50dc809 --- /dev/null +++ b/src/main/resources/jenkins/branch/CustomNameBranchProperty/config.properties @@ -0,0 +1,3 @@ + +Pattern.Title = Pattern +Pattern.Description = The required '{}' placeholder will be replaced with the remote branch name diff --git a/src/main/resources/jenkins/branch/CustomNameBranchProperty/help.html b/src/main/resources/jenkins/branch/CustomNameBranchProperty/help.html new file mode 100644 index 00000000..900e25cc --- /dev/null +++ b/src/main/resources/jenkins/branch/CustomNameBranchProperty/help.html @@ -0,0 +1,3 @@ +

+ Used to disambiguate multiple sources that may have overlapping branch names. +
diff --git a/src/main/resources/jenkins/branch/Messages.properties b/src/main/resources/jenkins/branch/Messages.properties index ccd5f17d..c567e8b6 100644 --- a/src/main/resources/jenkins/branch/Messages.properties +++ b/src/main/resources/jenkins/branch/Messages.properties @@ -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 diff --git a/src/test/java/jenkins/branch/CustomNameBranchPropertyTest.java b/src/test/java/jenkins/branch/CustomNameBranchPropertyTest.java new file mode 100644 index 00000000..8ddda365 --- /dev/null +++ b/src/test/java/jenkins/branch/CustomNameBranchPropertyTest.java @@ -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")); + } + } +}