Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/main/java/hudson/plugins/git/BranchSpec.java
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ private Pattern getPattern(EnvVars env, String repositoryName) {
String regexSubstring = expandedName.substring(1, expandedName.length());
return Pattern.compile(regexSubstring);
}
if (expandedName.startsWith("refs/changes/")) {
return Pattern.compile(Pattern.quote(expandedName));
}
if (repositoryName != null) {
// remove the "refs/.../" stuff from the branch-spec if necessary
String pattern = cutRefs(expandedName)
Expand Down
89 changes: 66 additions & 23 deletions src/main/java/jenkins/plugins/git/GitSCMFileSystem.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@
import java.util.concurrent.locks.Lock;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import jenkins.scm.api.SCMFile;
import jenkins.scm.api.SCMFileSystem;
import jenkins.scm.api.SCMHead;
Expand Down Expand Up @@ -267,6 +270,9 @@
|| gscm.getBranches().get(0).getName().matches(
"^((\\Q" + Constants.R_TAGS + "\\E.*)|([^/]+)|(\\*/[^/*]+(/[^/*]+)*))$"
)
|| gscm.getBranches().get(0).getName().matches(

Check warning on line 273 in src/main/java/jenkins/plugins/git/GitSCMFileSystem.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 273 is only partially covered, one branch is missing
"^((\\Qrefs/changes/\\E.*)|([^/]+)|(\\*/[^/*]+(/[^/*]+)*))$"
)
);
// we only support where the branch spec is obvious and not a wildcard
}
Expand All @@ -287,40 +293,82 @@
}

static class HeadNameResult {
final String headName;
final String prefix;

private HeadNameResult(String headName, String prefix) {
this.headName = headName;
this.prefix = prefix;
final String remoteHeadName;

@rsandell rsandell Jun 28, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm trying to remember if this class gets stored somewhere on disk, like the project config or pipeline context/program.
I don't think so, but if that's the case then an upgrade would break those builds currently running. That might be ok though.

final String refspec;
final SCMRevision rev;

private HeadNameResult(String remoteHeadName, String refspec, SCMRevision rev) {
this.remoteHeadName = remoteHeadName;
this.refspec = refspec;
this.rev = rev;
}

static HeadNameResult calculate(@NonNull BranchSpec branchSpec,
@CheckForNull SCMRevision rev,
@CheckForNull EnvVars env) {
@CheckForNull String refSpec,
@CheckForNull EnvVars env,
@CheckForNull String remoteName) {

String branchSpecExpandedName = branchSpec.getName();
if (env != null) {
branchSpecExpandedName = env.expand(branchSpecExpandedName);
}
String refspecExpandedName = refSpec;
if (env != null && refspecExpandedName != null) {
refspecExpandedName = env.expand(refspecExpandedName);
}

// default to a branch (refs/heads)
String prefix = Constants.R_HEADS;
// check for a tag
if (branchSpecExpandedName.startsWith(Constants.R_TAGS)) {
prefix = Constants.R_TAGS;
} else if (branchSpecExpandedName.startsWith("refs/changes")) {
prefix = "refs/changes/";
} else {
// check for FETCH_HEAD
if (branchSpecExpandedName.equals(Constants.FETCH_HEAD) && refspecExpandedName != null &&

Check warning on line 330 in src/main/java/jenkins/plugins/git/GitSCMFileSystem.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 330 is only partially covered, one branch is missing
!refspecExpandedName.equals("")) {

Check warning on line 331 in src/main/java/jenkins/plugins/git/GitSCMFileSystem.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 331 is only partially covered, one branch is missing
prefix = null;
} else {
// check for commit-id
final String regex = "^[a-fA-F0-9]{40}$";
final Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE);
final Matcher matcher = pattern.matcher(branchSpecExpandedName);

if (matcher.find()) {
// commit-id
prefix = null;
rev = new AbstractGitSCMSource.SCMRevisionImpl(new SCMHead(branchSpecExpandedName), branchSpecExpandedName);
}
}
}

String headName;
String calculatedHeadName = branchSpecExpandedName;
if (rev != null && env != null) {
headName = env.expand(rev.getHead().getName());
calculatedHeadName = env.expand(rev.getHead().getName());
} else {
if (branchSpecExpandedName.startsWith(prefix)) {
headName = branchSpecExpandedName.substring(prefix.length());
if (prefix != null && branchSpecExpandedName.startsWith(prefix)) {
calculatedHeadName = branchSpecExpandedName.substring(prefix.length());
} else if (branchSpecExpandedName.startsWith("*/")) {
headName = branchSpecExpandedName.substring(2);
calculatedHeadName = branchSpecExpandedName.substring(2);
}
}

if (refspecExpandedName == null || refspecExpandedName.equals("")) {

Check warning on line 358 in src/main/java/jenkins/plugins/git/GitSCMFileSystem.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 358 is only partially covered, one branch is missing
if (prefix.equals(Constants.R_TAGS)) {
refspecExpandedName = "+" + prefix + calculatedHeadName + ":" + prefix + calculatedHeadName;
} else {
headName = branchSpecExpandedName;
refspecExpandedName = "+" + prefix + calculatedHeadName + ":" + Constants.R_REMOTES + remoteName + "/" + calculatedHeadName;
}
}
return new HeadNameResult(headName, prefix);

String remoteHead = calculatedHeadName;
if (prefix != null && prefix.equals(Constants.R_HEADS)) {
remoteHead = Constants.R_REMOTES + remoteName + "/" + calculatedHeadName;
}

return new HeadNameResult(remoteHead, refspecExpandedName, rev);
}
}

Expand Down Expand Up @@ -397,16 +445,11 @@
listener.getLogger().println("URI syntax exception for '" + remoteName + "' " + ex);
}

HeadNameResult headNameResult = HeadNameResult.calculate(branchSpec, rev, env);
HeadNameResult headNameResult = HeadNameResult.calculate(branchSpec, rev, config.getRefspec(), env, remoteName);
client.fetch_().prune(true).from(remoteURI, Collections.singletonList(new RefSpec(headNameResult.refspec))).execute();

client.fetch_().prune(true).from(remoteURI, Collections.singletonList(new RefSpec(
"+" + headNameResult.prefix + headNameResult.headName + ":" + Constants.R_REMOTES + remoteName + "/"
+ headNameResult.headName))).execute();

listener.getLogger().println("Done.");
return new GitSCMFileSystem(client, remote, Constants.R_REMOTES + remoteName + "/" + headNameResult.headName, (AbstractGitSCMSource.SCMRevisionImpl) rev);
} catch (GitException x) {
throw new IOException(x);
listener.getLogger().println("Done with " + remoteName + " using " + headNameResult.remoteHeadName + ".");
return new GitSCMFileSystem(client, remote, headNameResult.remoteHeadName, (AbstractGitSCMSource.SCMRevisionImpl) headNameResult.rev);
} finally {
cacheLock.unlock();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@
Tracks/checks out the specified tag.<br/>
E.g. <code>refs/tags/git-2.3.0</code>
</li>
<li> <strong><code>refs/changes/&lt;gerritChange&gt;</code></strong><br/>
Tracks/checks out the specified gerrit change.<br/>
E.g. <code>refs/changes/91/45391/1</code>
</li>
<li> <strong><code>&lt;commitId&gt;</code></strong><br/>
Checks out the specified commit.<br/>
E.g. <code>5062ac843f2b947733e6a3b105977056821bd352</code>, <code>5062ac84</code>, ...
Expand Down
8 changes: 8 additions & 0 deletions src/test/java/hudson/plugins/git/BranchSpecTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,14 @@ void testUsesRefsHeads() {
assertFalse(m.matches("remote/origin/jane"));
}

@Test
public void testMatchesGerritChangeRef() {
BranchSpec spec = new BranchSpec("refs/changes/91/45391/1");

assertTrue(spec.matches("refs/changes/91/45391/1"));
assertFalse(spec.matches("refs/heads/refs/changes/91/45391/1"));
}

@Test
void testUsesJavaPatternDirectlyIfPrefixedWithColon() {
BranchSpec m = new BranchSpec(":^(?!(origin/prefix)).*");
Expand Down
Loading
Loading