Skip to content
Permalink
Browse files
[FIXED JENKINS-17417] Expand Branch Specifier
Expand Branch Specifier with build environment variables.

Note that the Branch Specifier is not expanded for repository polling
because no build environment is available at that time.
  • Loading branch information
hrubi committed Dec 4, 2013
1 parent dc73e25 commit 4fc7f4447c990082e8322c42ebafd1f33a2e60aa
Showing 5 changed files with 105 additions and 28 deletions.
@@ -1,5 +1,6 @@
package hudson.plugins.git;

import hudson.EnvVars;
import hudson.Extension;
import hudson.model.AbstractDescribableImpl;
import hudson.model.Descriptor;
@@ -27,7 +28,6 @@ public class BranchSpec extends AbstractDescribableImpl<BranchSpec> implements S
private static final long serialVersionUID = -6177158367915899356L;

private String name;
private transient Pattern pattern;

public String getName() {
return name;
@@ -48,54 +48,69 @@ public BranchSpec(String name) {
}

public String toString() {
return pattern + " (" + name + ")";
return name;
}

public boolean matches(String item) {
return getPattern().matcher(item).matches();
EnvVars env = new EnvVars();
return matches(item, env);
}

public boolean matches(String item, EnvVars env) {
return getPattern(env).matcher(item).matches();
}

public List<String> filterMatching(Collection<String> branches) {
EnvVars env = new EnvVars();
return filterMatching(branches, env);
}

public List<String> filterMatching(Collection<String> branches, EnvVars env) {
List<String> items = new ArrayList<String>();

for(String b : branches) {
if(matches(b))
if(matches(b, env))
items.add(b);
}

return items;
}

public List<Branch> filterMatchingBranches(Collection<Branch> branches) {
EnvVars env = new EnvVars();
return filterMatchingBranches(branches, env);
}

public List<Branch> filterMatchingBranches(Collection<Branch> branches, EnvVars env) {
List<Branch> items = new ArrayList<Branch>();

for(Branch b : branches) {
if(matches(b.getName()))
if(matches(b.getName(), env))
items.add(b);
}

return items;
}

private String getExpandedName(EnvVars env) {
return env.expand(name);
}

private Pattern getPattern() {
// return the saved pattern if available
if (pattern != null)
return pattern;

private Pattern getPattern(EnvVars env) {
String expandedName = getExpandedName(env);
// use regex syntax directly if name starts with colon
if (name.startsWith(":") && name.length() > 1) {
String regexSubstring = name.substring(1, name.length());
pattern = Pattern.compile(regexSubstring);
return pattern;
if (expandedName.startsWith(":") && expandedName.length() > 1) {
String regexSubstring = expandedName.substring(1, expandedName.length());
return Pattern.compile(regexSubstring);
}

// if an unqualified branch was given add a "*/" so it will match branches
// from remote repositories as the user probably intended
String qualifiedName;
if (!name.contains("**") && !name.contains("/"))
qualifiedName = "*/" + name;
if (!expandedName.contains("**") && !expandedName.contains("/"))
qualifiedName = "*/" + expandedName;
else
qualifiedName = name;
qualifiedName = expandedName;

// build a pattern into this builder
StringBuilder builder = new StringBuilder();
@@ -140,10 +155,7 @@ private Pattern getPattern() {
builder.append("[^/]*");
}

// save the pattern
pattern = Pattern.compile(builder.toString());

return pattern;
return Pattern.compile(builder.toString());
}

@Extension
@@ -1,6 +1,7 @@
package hudson.plugins.git.util;

import hudson.Extension;
import hudson.EnvVars;
import hudson.model.TaskListener;
import hudson.plugins.git.*;
import hudson.remoting.VirtualChannel;
@@ -48,7 +49,7 @@ public Collection<Revision> getCandidateRevisions(boolean isPollCall, String sin
// if the branch name contains more wildcards then the simple usecase
// does not apply and we need to skip to the advanced usecase
if (singleBranch == null || singleBranch.contains("*"))
return getAdvancedCandidateRevisions(isPollCall,listener,new GitUtils(listener,git),data);
return getAdvancedCandidateRevisions(isPollCall,listener,new GitUtils(listener,git),data, context);

// check if we're trying to build a specific commit
// this only makes sense for a build, there is no
@@ -167,7 +168,9 @@ private Revision objectId2Revision(String singleBranch, ObjectId sha1) {
* @throws IOException
* @throws GitException
*/
private List<Revision> getAdvancedCandidateRevisions(boolean isPollCall, TaskListener listener, GitUtils utils, BuildData data) throws GitException, IOException, InterruptedException {
private List<Revision> getAdvancedCandidateRevisions(boolean isPollCall, TaskListener listener, GitUtils utils, BuildData data, BuildChooserContext context) throws GitException, IOException, InterruptedException {
EnvVars env = context.getBuild().getEnvironment();

// 1. Get all the (branch) revisions that exist
List<Revision> revs = new ArrayList<Revision>(utils.getAllBranchRevisions());
verbose(listener, "Starting with all the branches: {0}", revs);
@@ -182,7 +185,7 @@ private List<Revision> getAdvancedCandidateRevisions(boolean isPollCall, TaskLis
Branch b = j.next();
boolean keep = false;
for (BranchSpec bspec : gitSCM.getBranches()) {
if (bspec.matches(b.getName())) {
if (bspec.matches(b.getName(), env)) {
keep = true;
break;
}
@@ -198,7 +201,7 @@ private List<Revision> getAdvancedCandidateRevisions(boolean isPollCall, TaskLis
if (r.getBranches().size() > 1) {
for (Iterator<Branch> j = r.getBranches().iterator(); j.hasNext();) {
Branch b = j.next();
if (HEAD.matches(b.getName())) {
if (HEAD.matches(b.getName(), env)) {
verbose(listener, "Ignoring {0} because there''s named branch for this revision", b.getName());
j.remove();
}
@@ -241,7 +244,7 @@ private List<Revision> getAdvancedCandidateRevisions(boolean isPollCall, TaskLis
// with fast-forward merges between branches
if (!isPollCall && revs.isEmpty() && lastBuiltRevision != null) {
verbose(listener, "Nothing seems worth building, so falling back to the previously built revision: {0}", data.getLastBuiltRevision());
return Collections.singletonList(utils.sortBranchesForRevision(lastBuiltRevision, gitSCM.getBranches()));
return Collections.singletonList(utils.sortBranchesForRevision(lastBuiltRevision, gitSCM.getBranches(), env));
}

// 5. sort them by the date of commit, old to new
@@ -82,13 +82,18 @@ public Revision getRevisionForSHA1(ObjectId sha1) throws GitException, IOExcepti
}

public Revision sortBranchesForRevision(Revision revision, List<BranchSpec> branchOrder) {
EnvVars env = new EnvVars();
return sortBranchesForRevision(revision, branchOrder, env);
}

public Revision sortBranchesForRevision(Revision revision, List<BranchSpec> branchOrder, EnvVars env) {
ArrayList<Branch> orderedBranches = new ArrayList<Branch>(revision.getBranches().size());
ArrayList<Branch> revisionBranches = new ArrayList<Branch>(revision.getBranches());

for(BranchSpec branchSpec : branchOrder) {
for (Iterator<Branch> i = revisionBranches.iterator(); i.hasNext();) {
Branch b = i.next();
if (branchSpec.matches(b.getName())) {
if (branchSpec.matches(b.getName(), env)) {
i.remove();
orderedBranches.add(b);
}
@@ -1,6 +1,7 @@
package hudson.plugins.git.util;

import hudson.Extension;
import hudson.EnvVars;
import hudson.model.TaskListener;
import hudson.plugins.git.*;
import hudson.remoting.VirtualChannel;
@@ -41,6 +42,7 @@ public Collection<Revision> getCandidateRevisions(boolean isPollCall,
String singleBranch, GitClient git, TaskListener listener,
BuildData buildData, BuildChooserContext context) throws GitException, IOException, InterruptedException {

EnvVars env = context.getBuild().getEnvironment();
GitUtils utils = new GitUtils(listener, git);
List<Revision> branchRevs = new ArrayList<Revision>(utils.getAllBranchRevisions());
List<BranchSpec> specifiedBranches = gitSCM.getBranches();
@@ -56,7 +58,7 @@ public Collection<Revision> getCandidateRevisions(boolean isPollCall,
// Check whether this branch matches a branch spec from the job config
for (BranchSpec spec : specifiedBranches) {
// If the branch matches, throw it away as we do *not* want to build it
if (spec.matches(branch.getName()) || HEAD.matches(branch.getName())) {
if (spec.matches(branch.getName(), env) || HEAD.matches(branch.getName(), env)) {
j.remove();
break;
}
@@ -1,5 +1,8 @@
package hudson.plugins.git;

import hudson.EnvVars;
import java.util.HashMap;

import junit.framework.Assert;
import junit.framework.TestCase;

@@ -46,6 +49,58 @@ public void testMatch() {
Assert.assertFalse(p.matches("origin/my-branch/b1"));
}

public void testMatchEnv() {
HashMap<String, String> envMap = new HashMap<String, String>();
envMap.put("master", "master");
envMap.put("origin", "origin");
envMap.put("dev", "dev");
envMap.put("magnayn", "magnayn");
envMap.put("mybranch", "my.branch");
envMap.put("anyLong", "**");
envMap.put("anyShort", "*");
EnvVars env = new EnvVars(envMap);

BranchSpec l = new BranchSpec("${master}");
Assert.assertTrue(l.matches("origin/master", env));
Assert.assertFalse(l.matches("origin/something/master", env));
Assert.assertFalse(l.matches("master", env));
Assert.assertFalse(l.matches("dev", env));


BranchSpec est = new BranchSpec("${origin}/*/${dev}");

Assert.assertFalse(est.matches("origintestdev", env));
Assert.assertTrue(est.matches("origin/test/dev", env));
Assert.assertFalse(est.matches("origin/test/release", env));
Assert.assertFalse(est.matches("origin/test/somthing/release", env));

BranchSpec s = new BranchSpec("${origin}/*");

Assert.assertTrue(s.matches("origin/master", env));

BranchSpec m = new BranchSpec("**/${magnayn}/*");

Assert.assertTrue(m.matches("origin/magnayn/b1", env));
Assert.assertTrue(m.matches("remote/origin/magnayn/b1", env));

BranchSpec n = new BranchSpec("*/${mybranch}/*");

Assert.assertTrue(n.matches("origin/my.branch/b1", env));
Assert.assertFalse(n.matches("origin/my-branch/b1", env));
Assert.assertFalse(n.matches("remote/origin/my.branch/b1", env));

BranchSpec o = new BranchSpec("${anyLong}");

Assert.assertTrue(o.matches("origin/my.branch/b1", env));
Assert.assertTrue(o.matches("origin/my-branch/b1", env));
Assert.assertTrue(o.matches("remote/origin/my.branch/b1", env));

BranchSpec p = new BranchSpec("${anyShort}");

Assert.assertTrue(p.matches("origin/x", env));
Assert.assertFalse(p.matches("origin/my-branch/b1", env));
}

public void testEmptyName() {
BranchSpec branchSpec = new BranchSpec("");
assertEquals("**",branchSpec.getName());

1 comment on commit 4fc7f44

@MarkEWaite
Copy link

@MarkEWaite MarkEWaite commented on 4fc7f44 Feb 3, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was the change to the toString() method really helpful?

I think that toString() output was useful for me in the past when i could see both the branch specification of the job definition (the pattern) and the branch selected by the pattern (name).

Please sign in to comment.