diff --git a/src/main/java/hudson/plugins/git/GitSCM.java b/src/main/java/hudson/plugins/git/GitSCM.java index 39c3f67c9e..982d5de11b 100644 --- a/src/main/java/hudson/plugins/git/GitSCM.java +++ b/src/main/java/hudson/plugins/git/GitSCM.java @@ -512,12 +512,12 @@ public List getParamExpandedRepos(Run build, TaskListener li } /** - * Expand Parameters in the supplied remote repository with the parameter values provided in the given environment variables } + * Expand Parameters in the supplied remote repository with the parameter values provided in the given environment variables * @param env Environment variables with parameter values * @param remoteRepository Remote repository with parameters * @return remote repository with expanded parameters */ - public RemoteConfig getParamExpandedRepo(EnvVars env, RemoteConfig remoteRepository){ + public RemoteConfig getParamExpandedRepo(EnvVars env, RemoteConfig remoteRepository) { List refSpecs = getRefSpecs(remoteRepository, env); return newRemoteConfig( getParameterString(remoteRepository.getName(), env), diff --git a/src/main/java/hudson/plugins/git/extensions/impl/CloneOption.java b/src/main/java/hudson/plugins/git/extensions/impl/CloneOption.java index 2f5d1abf0e..887d14911c 100644 --- a/src/main/java/hudson/plugins/git/extensions/impl/CloneOption.java +++ b/src/main/java/hudson/plugins/git/extensions/impl/CloneOption.java @@ -14,7 +14,9 @@ import hudson.plugins.git.util.GitUtils; import hudson.slaves.NodeProperty; import java.io.IOException; +import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Objects; import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.RemoteConfig; @@ -26,6 +28,7 @@ import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.CheckForNull; /** * @author Kohsuke Kawaguchi @@ -139,6 +142,9 @@ public void decorateCloneCommand(GitSCM scm, Run build, GitClient git, Tas listener.getLogger().println("Avoid fetching tags"); cmd.tags(false); } + + Node node = GitUtils.workspaceToNode(git.getWorkTree()); + if (honorRefspec) { listener.getLogger().println("Honoring refspec on initial clone"); // Read refspec configuration from the first configured repository. @@ -148,13 +154,12 @@ public void decorateCloneCommand(GitSCM scm, Run build, GitClient git, Tas // configuration is treated as authoritative. // Git plugin does not support multiple independent repositories // in a single job definition. + EnvVars buildEnv = build.getEnvironment(listener); RemoteConfig rc = scm.getRepositories().get(0); - List refspecs = rc.getFetchRefSpecs(); - cmd.refspecs(refspecs); + cmd.refspecs(getRefSpecs(rc, buildEnv)); } cmd.timeout(timeout); - Node node = GitUtils.workspaceToNode(git.getWorkTree()); EnvVars env = build.getEnvironment(listener); Computer comp = node.toComputer(); if (comp != null) { @@ -166,6 +171,18 @@ public void decorateCloneCommand(GitSCM scm, Run build, GitClient git, Tas cmd.reference(env.expand(reference)); } + private static String getParameterString(@CheckForNull String original, @NonNull EnvVars env) { + return env.expand(original); + } + + private static List getRefSpecs(RemoteConfig repo, EnvVars env) { + List refSpecs = new ArrayList<>(); + for (RefSpec refSpec : repo.getFetchRefSpecs()) { + refSpecs.add(new RefSpec(getParameterString(refSpec.toString(), env))); + } + return refSpecs; + } + /** * {@inheritDoc} */ diff --git a/src/test/java/hudson/plugins/git/extensions/impl/CloneOptionHonorRefSpecTest.java b/src/test/java/hudson/plugins/git/extensions/impl/CloneOptionHonorRefSpecTest.java new file mode 100644 index 0000000000..74661e4bdc --- /dev/null +++ b/src/test/java/hudson/plugins/git/extensions/impl/CloneOptionHonorRefSpecTest.java @@ -0,0 +1,149 @@ +package hudson.plugins.git.extensions.impl; + +import hudson.Functions; +import hudson.model.FreeStyleBuild; +import hudson.model.FreeStyleProject; +import hudson.model.ParametersDefinitionProperty; +import hudson.model.Result; +import hudson.model.StringParameterDefinition; +import hudson.plugins.git.AbstractGitTestCase; +import hudson.plugins.git.BranchSpec; +import hudson.plugins.git.GitSCM; +import hudson.plugins.git.UserRemoteConfig; +import hudson.tasks.BatchFile; +import hudson.tasks.Builder; +import hudson.tasks.Shell; +import org.jenkinsci.plugins.gitclient.JGitTool; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.jvnet.hudson.test.Issue; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.*; + +@RunWith(Parameterized.class) +public class CloneOptionHonorRefSpecTest extends AbstractGitTestCase { + + private final String refSpecName; + private final Boolean honorRefSpec; + + private static final Random random = new Random(); + private FreeStyleProject project; + private String refSpecExpectedValue; + + public CloneOptionHonorRefSpecTest(String refSpecName, Boolean honorRefSpec) { + this.refSpecName = refSpecName; + this.honorRefSpec = honorRefSpec; + } + + @Parameterized.Parameters(name = "{0}-{1}") + public static Collection permuteRefSpecVariable() { + List values = new ArrayList<>(); + // Should behave same with honor refspec enabled or disabled + boolean honorRefSpec = random.nextBoolean(); + + String[] keys = { + "JOB_NAME", // Variable set by Jenkins + (Functions.isWindows() ? "USERNAME" : "USER"), // Variable set by the operating system + "USER_SELECTED_BRANCH_NAME" // Parametrised build param + }; + + for (String refSpecName : keys) { + Object[] combination = {refSpecName, true}; + values.add(combination); + honorRefSpec = !honorRefSpec; + } + + return values; + } + + private static Builder createEnvEchoBuilder(String envVarName) { + if (Functions.isWindows()) { + return new BatchFile(String.format("echo %s=%%%s%%", envVarName, envVarName)); + } + return new Shell(String.format("echo \"%s=${%s}\"", envVarName, envVarName)); + } + + @Before + public void setUp() throws Exception { + super.setUp(); + + // Setup job beforehand to get expected value of the environment variable + project = createFreeStyleProject(); + project.addProperty(new ParametersDefinitionProperty( + new StringParameterDefinition("USER_SELECTED_BRANCH_NAME", "user_branch") + )); + project.getBuildersList().add(createEnvEchoBuilder(refSpecName)); + + final FreeStyleBuild b = project.scheduleBuild2(0).get(); + rule.assertBuildStatus(Result.SUCCESS, b); + + List logs = b.getLog(50); + for (String line : logs) { + if (line.startsWith(refSpecName + '=')) { + refSpecExpectedValue = line.split("=")[1]; + } + } + + if (refSpecExpectedValue == null) { + throw new Exception("Could not obtain env var expected value"); + } + } + + /** + * This test confirms behavior of refspecs on initial clone with expanded + * variables. When an environment variable reference is embedded in the + * refspec, it should be expanded in all cases. + * + * @throws Exception on error + */ + @Test + @Issue("JENKINS-56063") + public void testRefSpecWithExpandedVariables() throws Exception { + // Create initial commit + final String commitFile1 = "commitFile1"; + commit(commitFile1, johnDoe, "Commit in master branch"); + + // Create branch and make initial commit + git.checkout().ref("master").branch(refSpecExpectedValue).execute(); + commit(commitFile1, johnDoe, "Commit in '" + refSpecExpectedValue + "' branch"); + + // Use the variable reference in the refspec + // Should be expanded by the clone option whether or not honor refspec is enabled + List repos = new ArrayList<>(); + repos.add(new UserRemoteConfig( + testRepo.gitDir.getAbsolutePath(), + "origin", + "+refs/heads/${" + refSpecName + "}:refs/remotes/origin/${" + refSpecName + "}", null)); + + /* Use the variable or its value as the branch name. + * Same result expected in either case. + */ + String branchName = random.nextBoolean() ? "${" + refSpecName + "}" : refSpecExpectedValue; + GitSCM scm = new GitSCM( + repos, + Collections.singletonList(new BranchSpec(branchName)), + false, Collections.emptyList(), + null, random.nextBoolean() ? JGitTool.MAGIC_EXENAME : null, + Collections.emptyList()); + project.setScm(scm); + + // Same result expected whether refspec honored or not + CloneOption cloneOption = new CloneOption(false, null, null); + cloneOption.setHonorRefspec(honorRefSpec); + scm.getExtensions().add(cloneOption); + + FreeStyleBuild b = build(project, Result.SUCCESS, commitFile1); + + // Check that unexpanded refspec name is not in the log + List buildLog = b.getLog(50); + assertThat(buildLog, not(hasItem(containsString("${" + refSpecName + "}")))); + } +}