diff --git a/src/main/java/org/jenkinsci/plugins/gitclient/CliGitAPIImpl.java b/src/main/java/org/jenkinsci/plugins/gitclient/CliGitAPIImpl.java
index ff07100eed..1070faf655 100644
--- a/src/main/java/org/jenkinsci/plugins/gitclient/CliGitAPIImpl.java
+++ b/src/main/java/org/jenkinsci/plugins/gitclient/CliGitAPIImpl.java
@@ -2582,7 +2582,11 @@ private String getPathToExe(String userGitExe) {
exe = userGitExe + ".exe";
}
- String[] pathDirs = System.getenv("PATH").split(File.pathSeparator);
+ String path = getEnvVar("PATH");
+ if (path == null) {
+ path = "";
+ }
+ String[] pathDirs = path.split(File.pathSeparator);
for (String pathDir : pathDirs) {
File exeFile = new File(pathDir, exe);
@@ -2603,8 +2607,13 @@ private String getPathToExe(String userGitExe) {
return null;
}
+ /* Package protected for testing */
+ String getEnvVar(String envVar) {
+ return System.getenv(envVar);
+ }
+
private File getFileFromEnv(String envVar, String suffix) {
- String envValue = System.getenv(envVar);
+ String envValue = getEnvVar(envVar);
if (envValue == null) {
return null;
}
@@ -2689,6 +2698,15 @@ public File getSSHExecutable() {
}
}
+ // Check the system PATH last, the ssh of the git installation is preferred
+ String sshPath = getPathToExe("ssh");
+ if (sshPath != null) {
+ sshexe = new File(sshPath);
+ if (sshexe.exists()) {
+ return sshexe;
+ }
+ }
+
throw new RuntimeException(
"ssh executable not found. The git plugin only supports official git client https://git-scm.com/download/win");
}
diff --git a/src/test/java/org/jenkinsci/plugins/gitclient/CliGitAPISshExecutableTest.java b/src/test/java/org/jenkinsci/plugins/gitclient/CliGitAPISshExecutableTest.java
new file mode 100644
index 0000000000..4476d18a28
--- /dev/null
+++ b/src/test/java/org/jenkinsci/plugins/gitclient/CliGitAPISshExecutableTest.java
@@ -0,0 +1,125 @@
+package org.jenkinsci.plugins.gitclient;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.jvnet.hudson.test.Issue;
+
+/**
+ * Tests the order in which {@link CliGitAPIImpl#getSSHExecutable()} searches for ssh.
+ * The ssh of the git installation must be preferred over an ssh found on the system PATH.
+ *
+ * @author Akash Manna
+ */
+class CliGitAPISshExecutableTest {
+
+ @TempDir
+ private File tempDir;
+
+ private CliGitAPIImpl gitClientWithEnv(String gitExe, Map environmentVariables) {
+ return new CliGitAPIImpl(gitExe, tempDir, null, null) {
+ @Override
+ String getEnvVar(String envVar) {
+ return environmentVariables.get(envVar);
+ }
+ };
+ }
+
+ /* Paths are joined with a backslash, so on Unix the file name contains backslashes */
+ private File createExecutable(File executable) throws IOException {
+ File parent = executable.getParentFile();
+ if (parent != null) {
+ parent.mkdirs();
+ }
+ assertTrue(executable.createNewFile(), "Could not create " + executable);
+ return executable;
+ }
+
+ private File pathDirectoryWithSsh() throws IOException {
+ File pathDir = new File(tempDir, "path-dir");
+ pathDir.mkdirs();
+ createExecutable(new File(pathDir, "ssh.exe"));
+ return pathDir;
+ }
+
+ @Test
+ @Issue("JENKINS-1715")
+ void sshOfGitInstallationPreferredOverSshOnPath() throws Exception {
+ File programFiles = new File(tempDir, "Program Files");
+ File gitSsh = createExecutable(new File(programFiles + "\\Git\\bin\\ssh.exe"));
+
+ Map env = new HashMap<>();
+ env.put("ProgramFiles", programFiles.getAbsolutePath());
+ env.put("PATH", pathDirectoryWithSsh().getAbsolutePath());
+
+ assertEquals(gitSsh, gitClientWithEnv("git", env).getSSHExecutable());
+ }
+
+ @Test
+ @Issue("JENKINS-1715")
+ void sshOfGitInstallationInUsrBinPreferredOverSshOnPath() throws Exception {
+ File programFiles = new File(tempDir, "Program Files");
+ File gitSsh = createExecutable(new File(programFiles + "\\Git\\usr\\bin\\ssh.exe"));
+
+ Map env = new HashMap<>();
+ env.put("ProgramFiles", programFiles.getAbsolutePath());
+ env.put("PATH", pathDirectoryWithSsh().getAbsolutePath());
+
+ assertEquals(gitSsh, gitClientWithEnv("git", env).getSSHExecutable());
+ }
+
+ @Test
+ @Issue("JENKINS-1715")
+ void sshBesideGitExecutablePreferredOverSshOnPath() throws Exception {
+ File gitExe = new File(tempDir, "MinGit\\cmd\\git.exe");
+ File gitSsh = createExecutable(new File(gitExe.getParent() + "\\ssh.exe"));
+
+ Map env = new HashMap<>();
+ env.put("PATH", pathDirectoryWithSsh().getAbsolutePath());
+
+ assertEquals(gitSsh, gitClientWithEnv(gitExe.getAbsolutePath(), env).getSSHExecutable());
+ }
+
+ @Test
+ @Issue("JENKINS-72450")
+ void sshOnPathUsedWhenGitInstallationHasNoSsh() throws Exception {
+ File pathDir = pathDirectoryWithSsh();
+
+ Map env = new HashMap<>();
+ env.put("PATH", pathDir.getAbsolutePath());
+
+ assertEquals(
+ new File(pathDir, "ssh.exe"),
+ gitClientWithEnv(new File(tempDir, "no-ssh-here\\git.exe").getAbsolutePath(), env)
+ .getSSHExecutable());
+ }
+
+ @Test
+ @Issue("JENKINS-72450")
+ void gitSshEnvironmentVariablePreferredOverSshOnPath() throws Exception {
+ File gitSshVariable = createExecutable(new File(tempDir, "custom-ssh.exe"));
+
+ Map env = new HashMap<>();
+ env.put("GIT_SSH", gitSshVariable.getAbsolutePath());
+ env.put("PATH", pathDirectoryWithSsh().getAbsolutePath());
+
+ assertEquals(gitSshVariable, gitClientWithEnv("git", env).getSSHExecutable());
+ }
+
+ @Test
+ void sshNotFoundReportsUnsupportedGitInstallation() {
+ Map env = new HashMap<>();
+ env.put("PATH", new File(tempDir, "empty-dir").getAbsolutePath());
+
+ CliGitAPIImpl git = gitClientWithEnv(new File(tempDir, "no-ssh-here\\git.exe").getAbsolutePath(), env);
+ RuntimeException e = assertThrows(RuntimeException.class, git::getSSHExecutable);
+ assertTrue(e.getMessage().startsWith("ssh executable not found"), e.getMessage());
+ }
+}