diff --git a/src/main/java/hudson/plugins/git/BranchSpec.java b/src/main/java/hudson/plugins/git/BranchSpec.java index 2c6ce09367..fba6efdaeb 100644 --- a/src/main/java/hudson/plugins/git/BranchSpec.java +++ b/src/main/java/hudson/plugins/git/BranchSpec.java @@ -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) diff --git a/src/main/java/jenkins/plugins/git/GitSCMFileSystem.java b/src/main/java/jenkins/plugins/git/GitSCMFileSystem.java index 2165734450..071091a0fb 100644 --- a/src/main/java/jenkins/plugins/git/GitSCMFileSystem.java +++ b/src/main/java/jenkins/plugins/git/GitSCMFileSystem.java @@ -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; @@ -267,6 +270,9 @@ public boolean supports(SCM source) { || gscm.getBranches().get(0).getName().matches( "^((\\Q" + Constants.R_TAGS + "\\E.*)|([^/]+)|(\\*/[^/*]+(/[^/*]+)*))$" ) + || gscm.getBranches().get(0).getName().matches( + "^((\\Qrefs/changes/\\E.*)|([^/]+)|(\\*/[^/*]+(/[^/*]+)*))$" + ) ); // we only support where the branch spec is obvious and not a wildcard } @@ -287,40 +293,82 @@ public boolean supportsDescriptor(SCMSourceDescriptor descriptor) { } static class HeadNameResult { - final String headName; - final String prefix; - - private HeadNameResult(String headName, String prefix) { - this.headName = headName; - this.prefix = prefix; + final String remoteHeadName; + 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 && + !refspecExpandedName.equals("")) { + 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("")) { + 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); } } @@ -397,16 +445,11 @@ public SCMFileSystem build(@NonNull Item owner, @NonNull SCM scm, @CheckForNull 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(); } diff --git a/src/main/resources/hudson/plugins/git/BranchSpec/help-name.html b/src/main/resources/hudson/plugins/git/BranchSpec/help-name.html index 7c39aee1fd..3548b4846e 100644 --- a/src/main/resources/hudson/plugins/git/BranchSpec/help-name.html +++ b/src/main/resources/hudson/plugins/git/BranchSpec/help-name.html @@ -47,6 +47,10 @@ Tracks/checks out the specified tag.
E.g. refs/tags/git-2.3.0 +
  • refs/changes/<gerritChange>
    + Tracks/checks out the specified gerrit change.
    + E.g. refs/changes/91/45391/1 +
  • <commitId>
    Checks out the specified commit.
    E.g. 5062ac843f2b947733e6a3b105977056821bd352, 5062ac84, ... diff --git a/src/test/java/hudson/plugins/git/BranchSpecTest.java b/src/test/java/hudson/plugins/git/BranchSpecTest.java index 835cf16a5b..27b82e6bac 100644 --- a/src/test/java/hudson/plugins/git/BranchSpecTest.java +++ b/src/test/java/hudson/plugins/git/BranchSpecTest.java @@ -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)).*"); diff --git a/src/test/java/jenkins/plugins/git/GitSCMFileSystemTest.java b/src/test/java/jenkins/plugins/git/GitSCMFileSystemTest.java index ffee98ff89..6bef3b8ff7 100644 --- a/src/test/java/jenkins/plugins/git/GitSCMFileSystemTest.java +++ b/src/test/java/jenkins/plugins/git/GitSCMFileSystemTest.java @@ -32,11 +32,15 @@ import hudson.plugins.git.GitException; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.IOException; +import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; +import java.util.List; import java.util.Set; import java.util.TreeSet; +import hudson.plugins.git.UserRemoteConfig; import jenkins.plugins.git.junit.jupiter.WithGitSampleRepo; import jenkins.scm.api.SCMFile; import jenkins.scm.api.SCMFileSystem; @@ -68,6 +72,8 @@ import static org.junit.jupiter.api.Assertions.*; import static org.hamcrest.MatcherAssert.assertThat; +import jenkins.plugins.git.GitSCMFileSystem.BuilderImpl.HeadNameResult; + /** * Tests for {@link AbstractGitSCMSource} */ @@ -410,6 +416,10 @@ void create_SCMFileSystem_from_tag() throws Exception { sampleRepo.git("commit", "--all", "--message=dev"); sampleRepo.git("tag", "v1.0"); SCMFileSystem fs = SCMFileSystem.of(r.createFreeStyleProject(), new GitSCM(GitSCM.createRepoList(sampleRepo.toString(), null), Collections.singletonList(new BranchSpec("refs/tags/v1.0")), null, null, Collections.emptyList())); + assertEquals("modified", getFileContent(fs, "dir/subdir/file", "modified")); + } + + public String getFileContent(SCMFileSystem fs, String path, String expectedContent) throws IOException, InterruptedException { assertThat(fs, notNullValue()); assertThat(fs.getRoot(), notNullValue()); Iterable children = fs.getRoot().children(); @@ -432,7 +442,64 @@ void create_SCMFileSystem_from_tag() throws Exception { SCMFile file = iterator.next(); assertThat(iterator.hasNext(), is(false)); assertThat(file.getName(), is("file")); - assertThat(file.contentAsString(), is("modified")); + return file.contentAsString(); + } + + public static List createRepoListWithRefspec(String url, String refspec) { + List repoList = new ArrayList<>(); + repoList.add(new UserRemoteConfig(url, null, refspec, null)); + return repoList; + } + + @Test + public void create_SCMFileSystem_from_commit() throws Exception { + sampleRepo.init(); + sampleRepo.git("checkout", "-b", "dev"); + sampleRepo.mkdirs("dir/subdir"); + sampleRepo.git("mv", "file", "dir/subdir/file"); + sampleRepo.write("dir/subdir/file", "modified"); + sampleRepo.git("commit", "--all", "--message=dev"); + String modifiedCommit = sampleRepo.head(); + sampleRepo.write("dir/subdir/file", "modified again"); + sampleRepo.git("commit", "--all", "--message=dev"); + SCMFileSystem fs = SCMFileSystem.of(r.createFreeStyleProject(), new GitSCM(createRepoListWithRefspec(sampleRepo.toString(), "dev"), Collections.singletonList(new BranchSpec(modifiedCommit)), null, null, Collections.emptyList())); + assertEquals(modifiedCommit, fs.getRevision().toString()); + assertEquals("modified", getFileContent(fs, "dir/subdir/file", "modified")); + } + + @Test + public void create_SCMFileSystem_from_FETCH_HEAD() throws Exception { + sampleRepo.init(); + sampleRepo.git("checkout", "-b", "dev"); + sampleRepo.mkdirs("dir/subdir"); + sampleRepo.git("mv", "file", "dir/subdir/file"); + sampleRepo.write("dir/subdir/file", "modified"); + sampleRepo.git("commit", "--all", "--message=dev"); + SCMFileSystem fs = SCMFileSystem.of(r.createFreeStyleProject(), new GitSCM(createRepoListWithRefspec(sampleRepo.toString(), "dev"), Collections.singletonList(new BranchSpec(Constants.FETCH_HEAD)), null, null, Collections.emptyList())); + assertEquals("modified", getFileContent(fs, "dir/subdir/file", "modified")); + } + + @Test + public void testSupportsGerritChangeRef() throws Exception { + // Create a dummy GitSCM configured with a Gerrit change refspec + GitSCM scm = new GitSCM( + GitSCM.createRepoList("https://example.com/repo.git", null), + Collections.singletonList(new BranchSpec("refs/changes/91/45391/1")), + null, null, Collections.emptyList()); + + GitSCMFileSystem.BuilderImpl builder = new GitSCMFileSystem.BuilderImpl(); + assertTrue(builder.supports(scm), "Builder should support refs/changes/ branch specs"); + } + + @Test + public void testHeadNameResultCalculateGerritChange() { + BranchSpec spec = new BranchSpec("refs/changes/91/45391/1"); + + HeadNameResult result = + HeadNameResult.calculate(spec, null, null, null, "origin"); + + assertEquals("91/45391/1", result.remoteHeadName); + assertTrue(result.refspec.startsWith("+refs/changes/")); } @Issue("JENKINS-52964") @@ -444,36 +511,75 @@ void filesystem_supports_descriptor() throws Exception { @Issue("JENKINS-42971") @Test - void calculate_head_name_with_env() throws Exception { - GitSCMFileSystem.BuilderImpl.HeadNameResult result1 = GitSCMFileSystem.BuilderImpl.HeadNameResult.calculate(new BranchSpec("${BRANCH}"), null, - new EnvVars("BRANCH", "master-a")); - assertEquals("master-a", result1.headName); - assertEquals(Constants.R_HEADS, result1.prefix); - - GitSCMFileSystem.BuilderImpl.HeadNameResult result2 = GitSCMFileSystem.BuilderImpl.HeadNameResult.calculate(new BranchSpec("${BRANCH}"), null, - new EnvVars("BRANCH", "refs/heads/master-b")); - assertEquals("master-b", result2.headName); - assertEquals(Constants.R_HEADS, result2.prefix); - - GitSCMFileSystem.BuilderImpl.HeadNameResult result3 = GitSCMFileSystem.BuilderImpl.HeadNameResult.calculate(new BranchSpec("refs/heads/${BRANCH}"), null, - new EnvVars("BRANCH", "master-c")); - assertEquals("master-c", result3.headName); - assertEquals(Constants.R_HEADS, result3.prefix); - - GitSCMFileSystem.BuilderImpl.HeadNameResult result4 = GitSCMFileSystem.BuilderImpl.HeadNameResult.calculate(new BranchSpec("${BRANCH}"), null, - null); - assertEquals("${BRANCH}", result4.headName); - assertEquals(Constants.R_HEADS, result4.prefix); - - GitSCMFileSystem.BuilderImpl.HeadNameResult result5 = GitSCMFileSystem.BuilderImpl.HeadNameResult.calculate(new BranchSpec("*/${BRANCH}"), null, - new EnvVars("BRANCH", "master-d")); - assertEquals("master-d", result5.headName); - assertEquals(Constants.R_HEADS, result5.prefix); - - GitSCMFileSystem.BuilderImpl.HeadNameResult result6 = GitSCMFileSystem.BuilderImpl.HeadNameResult.calculate(new BranchSpec("*/master-e"), null, - new EnvVars("BRANCH", "dummy")); - assertEquals("master-e", result6.headName); - assertEquals(Constants.R_HEADS, result6.prefix); + public void calculate_head_name_with_env() throws Exception { + String remote = "origin"; + HeadNameResult result1 = HeadNameResult.calculate(new BranchSpec("${BRANCH}"), null, null, new EnvVars("BRANCH", "master-a"), remote); + assertEquals("refs/remotes/origin/master-a", result1.remoteHeadName); + assertTrue(result1.refspec.startsWith("+" + Constants.R_HEADS)); + + HeadNameResult result2 = HeadNameResult.calculate(new BranchSpec("${BRANCH}"), null, null, new EnvVars("BRANCH", "refs/heads/master-b"), remote); + assertEquals("refs/remotes/origin/master-b", result2.remoteHeadName); + assertTrue(result2.refspec.startsWith("+" + Constants.R_HEADS)); + + HeadNameResult result3 = HeadNameResult.calculate(new BranchSpec("refs/heads/${BRANCH}"), null, null, new EnvVars("BRANCH", "master-c"), remote); + assertEquals("refs/remotes/origin/master-c", result3.remoteHeadName); + assertTrue(result3.refspec.startsWith("+" + Constants.R_HEADS)); + + HeadNameResult result4 = HeadNameResult.calculate(new BranchSpec("${BRANCH}"), null, null, null, remote); + assertEquals("refs/remotes/origin/${BRANCH}", result4.remoteHeadName); + assertTrue(result4.refspec.startsWith("+" + Constants.R_HEADS)); + + HeadNameResult result5 = HeadNameResult.calculate(new BranchSpec("*/${BRANCH}"), null, null, new EnvVars("BRANCH", "master-d"), remote); + assertEquals("refs/remotes/origin/master-d", result5.remoteHeadName); + assertTrue(result5.refspec.startsWith("+" + Constants.R_HEADS)); + + HeadNameResult result6 = HeadNameResult.calculate(new BranchSpec("*/master-e"), null, null, new EnvVars("BRANCH", "dummy"), remote); + assertEquals("refs/remotes/origin/master-e", result6.remoteHeadName); + assertTrue(result6.refspec.startsWith("+" + Constants.R_HEADS)); + } + + @Test + public void calculate_head_name() throws Exception { + String remote = "origin"; + HeadNameResult result1 = HeadNameResult.calculate(new BranchSpec("branch"), null, null, null, remote); + assertEquals("refs/remotes/origin/branch", result1.remoteHeadName); + assertEquals("+refs/heads/branch:refs/remotes/origin/branch", result1.refspec); + + HeadNameResult result2 = HeadNameResult.calculate(new BranchSpec("refs/heads/branch"), null, null, null, remote); + assertEquals("refs/remotes/origin/branch", result2.remoteHeadName); + assertEquals("+refs/heads/branch:refs/remotes/origin/branch", result2.refspec); + + HeadNameResult result3 = HeadNameResult.calculate(new BranchSpec("refs/tags/my-tag"), null, null, null, remote); + assertEquals("my-tag", result3.remoteHeadName); + assertEquals("+refs/tags/my-tag:refs/tags/my-tag", result3.refspec); + } + + @Test + public void calculate_head_name_with_refspec_commit() throws Exception { + String remote = "origin"; + String commit = "0123456789" + "0123456789" + "0123456789" + "0123456789"; + String branch = "branch"; + HeadNameResult result1 = HeadNameResult.calculate(new BranchSpec(commit), null, branch, null, remote); + assertEquals(commit, result1.remoteHeadName); + assertEquals(branch, result1.refspec); + + HeadNameResult result2 = HeadNameResult.calculate(new BranchSpec("${BRANCH}"), null, "${REFSPEC}", + new EnvVars("BRANCH", commit, "REFSPEC", branch), remote); + assertEquals(commit, result2.remoteHeadName); + assertEquals(branch, result2.refspec); + } + + @Test + public void calculate_head_name_with_refspec_FETCH_HEAD() throws Exception { + String remote = "origin"; + HeadNameResult result1 = HeadNameResult.calculate(new BranchSpec(Constants.FETCH_HEAD), null, "refs/changes/1/2/3", null, remote); + assertEquals(Constants.FETCH_HEAD, result1.remoteHeadName); + assertEquals("refs/changes/1/2/3", result1.refspec); + + HeadNameResult result2 = HeadNameResult.calculate(new BranchSpec("${BRANCH}"), null, "${REFSPEC}", + new EnvVars("BRANCH", Constants.FETCH_HEAD, "REFSPEC", "refs/changes/1/2/3"), remote); + assertEquals(Constants.FETCH_HEAD, result2.remoteHeadName); + assertEquals("refs/changes/1/2/3", result2.refspec); } /* GitSCMFileSystem in git plugin 4.14.0 reported a null pointer @@ -486,8 +592,8 @@ void null_pointer_exception() throws Exception { ObjectId git260 = client.revParse(GIT_2_6_0_TAG); AbstractGitSCMSource.SCMRevisionImpl rev260 = new AbstractGitSCMSource.SCMRevisionImpl(new SCMHead("origin"), git260.getName()); - GitSCMFileSystem.BuilderImpl.HeadNameResult result1 = GitSCMFileSystem.BuilderImpl.HeadNameResult.calculate(new BranchSpec("master-f"), rev260, null); - assertEquals("master-f", result1.headName); - assertEquals(Constants.R_HEADS, result1.prefix); + HeadNameResult result1 = HeadNameResult.calculate(new BranchSpec("master-f"), rev260, null, null, "origin"); + assertEquals("refs/remotes/origin/master-f", result1.remoteHeadName); + assertTrue(result1.refspec.startsWith("+" + Constants.R_HEADS)); } }