Skip to content

Commit

Permalink
[FIXED JENKINS-17417] Expand Branch Specifier
Browse files Browse the repository at this point in the history
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 4fc7f44
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 28 deletions.
54 changes: 33 additions & 21 deletions src/main/java/hudson/plugins/git/BranchSpec.java
@@ -1,5 +1,6 @@
package hudson.plugins.git;

import hudson.EnvVars;
import hudson.Extension;
import hudson.model.AbstractDescribableImpl;
import hudson.model.Descriptor;
Expand Down Expand Up @@ -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;
Expand All @@ -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();
Expand Down Expand Up @@ -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
Expand Down
13 changes: 8 additions & 5 deletions src/main/java/hudson/plugins/git/util/DefaultBuildChooser.java
@@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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;
}
Expand All @@ -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();
}
Expand Down Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion src/main/java/hudson/plugins/git/util/GitUtils.java
Expand Up @@ -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);
}
Expand Down
@@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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;
}
Expand Down
55 changes: 55 additions & 0 deletions src/test/java/hudson/plugins/git/TestBranchSpec.java
@@ -1,5 +1,8 @@
package hudson.plugins.git;

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

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

Expand Down Expand Up @@ -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());
Expand Down

1 comment on commit 4fc7f44

@MarkEWaite
Copy link
Contributor

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.