From 6daec80727c887c856d5eddc81ed476519603d31 Mon Sep 17 00:00:00 2001 From: Kirill Likhodedov Date: Fri, 28 Dec 2012 19:19:46 +0400 Subject: [PATCH] IDEA-98232 Git executable auto-detector should prefer values from the PATH Test: - add tests on PATH; - let specify different drive letters, use drive letters in expected and actual paths to make them more natural looking. --- .../config/GitExecutableDetector.java | 45 +++++- .../GitExecutableDetectorWindowsTest.groovy | 148 ++++++++++++++---- 2 files changed, 157 insertions(+), 36 deletions(-) diff --git a/plugins/git4idea/src/git4idea/config/GitExecutableDetector.java b/plugins/git4idea/src/git4idea/config/GitExecutableDetector.java index 8767d8234966..8a556d6826eb 100644 --- a/plugins/git4idea/src/git4idea/config/GitExecutableDetector.java +++ b/plugins/git4idea/src/git4idea/config/GitExecutableDetector.java @@ -22,6 +22,7 @@ import com.intellij.execution.process.ProcessOutput; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.CharsetToolkit; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -53,6 +54,7 @@ public class GitExecutableDetector { private static final String GIT_EXE = "git.exe"; public static final String DEFAULT_WIN_GIT = GIT_EXE; + public static final String PATH_ENV = "PATH"; @NotNull public String detect() { @@ -75,7 +77,12 @@ public class GitExecutableDetector { @NotNull private String detectForWindows() { - String exec = checkMsys(); + String exec = checkInPath(); + if (exec != null) { + return exec; + } + + exec = checkProgramFiles(); if (exec != null) { return exec; } @@ -88,8 +95,37 @@ public class GitExecutableDetector { return checkSoleExecutable(); } + /** + * Looks into the %PATH% and checks Git directories mentioned there. + * @return Git executable to be used or null if nothing interesting was found in the PATH. + */ @Nullable - private static String checkMsys() { + private String checkInPath() { + String PATH = getPath(); + if (PATH == null) { + return null; + } + List pathEntries = StringUtil.split(PATH, ";"); + for (String pathEntry : pathEntries) { + if (looksLikeGit(pathEntry)) { + return checkBinDir(new File(pathEntry)); + } + } + return null; + } + + private static boolean looksLikeGit(@NotNull String path) { + List dirs = FileUtil.splitPath(path); + for (String dir : dirs) { + if (dir.toLowerCase().startsWith("git")) { + return true; + } + } + return false; + } + + @Nullable + private static String checkProgramFiles() { final String[] PROGRAM_FILES = { "Program Files", "Program Files (x86)" }; // collecting all potential msys distributives @@ -190,6 +226,11 @@ public class GitExecutableDetector { } } + @Nullable + protected String getPath() { + return System.getenv(PATH_ENV); + } + // Compare strategy: greater is better (if v1 > v2, then v1 is a better candidate for the Git executable) private static class VersionDirsComparator implements Comparator { diff --git a/plugins/git4idea/tests/git4idea/config/GitExecutableDetectorWindowsTest.groovy b/plugins/git4idea/tests/git4idea/config/GitExecutableDetectorWindowsTest.groovy index a8a565fb578e..1e109ba48351 100644 --- a/plugins/git4idea/tests/git4idea/config/GitExecutableDetectorWindowsTest.groovy +++ b/plugins/git4idea/tests/git4idea/config/GitExecutableDetectorWindowsTest.groovy @@ -32,106 +32,166 @@ import static junit.framework.Assert.assertEquals class GitExecutableDetectorWindowsTest { private File testRoot - private def canRun = [] + private def CAN_RUN = [] + private String PATH @Before void setUp() { GitTestUtil.setWindows(true); testRoot = FileUtil.createTempDirectory("", "") - setWindowsRoot(testRoot); + setWindowsRoot(new File(testRoot, "C_")); + PATH = System.getenv("PATH") } @Test void "Simple case"() { - fs "/Program Files/Git/bin/git.exe" + fs "C:/Program Files/Git/bin/git.exe" assertExecutable "C:/Program Files/Git/bin/git.exe" } @Test void "Prefer default Git without version to versioned ones"() { - fs "/Program Files/Git/bin/git.exe", "/Program Files/Git 1.8/bin/git.exe", "/Program Files/Git_1.7.1/bin/git.exe" + fs "C:/Program Files/Git/bin/git.exe", + "C:/Program Files/Git 1.8/bin/git.exe", + "C:/Program Files/Git_1.7.1/bin/git.exe" assertExecutable "C:/Program Files/Git/bin/git.exe" } @Test void "Prefer the latest version"() { - fs "/Program Files (x86)/Git 1.8/cmd/git.exe", - "/Program Files/Git_1.7.1/bin/git.exe", - "/Program Files/Git_1.7.5/cmd/git.cmd", - "/Program Files (x86)/Git_1.7.0.2/bin/git.exe" + fs "C:/Program Files (x86)/Git 1.8/cmd/git.exe", + "C:/Program Files/Git_1.7.1/bin/git.exe", + "C:/Program Files/Git_1.7.5/cmd/git.cmd", + "C:/Program Files (x86)/Git_1.7.0.2/bin/git.exe" assertExecutable "C:/Program Files (x86)/Git 1.8/cmd/git.exe" } @Test void "Prefer Program Files over x86"() { - fs "/Program Files (x86)/Git 1.8/bin/git.exe", "/Program Files/Git 1.8/bin/git.exe" + fs "C:/Program Files (x86)/Git 1.8/bin/git.exe", + "C:/Program Files/Git 1.8/bin/git.exe" assertExecutable "C:/Program Files/Git 1.8/bin/git.exe" } @Test void "Prefer git.cmd over git.exe"() { - fs "/Program Files (x86)/Git 1.7.4/bin/git.exe", "/Program Files/Git 1.7.4/cmd/git.cmd" + fs "C:/Program Files (x86)/Git 1.7.4/bin/git.exe", + "C:/Program Files/Git 1.7.4/cmd/git.cmd" assertExecutable "C:/Program Files/Git 1.7.4/cmd/git.cmd" } @Test void "Prefer cmd over bin in newer versions of Git"() { - fs "/Program Files (x86)/Git 1.8/bin/git.exe", "/Program Files/Git 1.8/cmd/git.exe" + fs "C:/Program Files (x86)/Git 1.8/bin/git.exe", + "C:/Program Files/Git 1.8/cmd/git.exe" assertExecutable "C:/Program Files/Git 1.8/cmd/git.exe" } @Test void "1.8.0 Prefer cmd/git.cmd over cmd/git.exe and bin/git.exe"() { - fs "/Program Files (x86)/Git_1.8/bin/git.exe", - "/Program Files (x86)/Git_1.8/cmd/git.cmd", - "/Program Files (x86)/Git_1.8/cmd/git.exe" + fs "C:/Program Files (x86)/Git_1.8/bin/git.exe", + "C:/Program Files (x86)/Git_1.8/cmd/git.cmd", + "C:/Program Files (x86)/Git_1.8/cmd/git.exe" assertExecutable "C:/Program Files (x86)/Git_1.8/cmd/git.cmd" } @Test void "Prefer msys over cygwin"() { - fs "/Program Files (x86)/Git_1.8.0.2/cmd/git.exe", - "/Program Files (x86)/Git_1.8.0.2/bin/git.exe", - "/cygwin/bin/git.exe" + fs "C:/Program Files (x86)/Git_1.8.0.2/cmd/git.exe", + "C:/Program Files (x86)/Git_1.8.0.2/bin/git.exe", + "C:/cygwin/bin/git.exe" assertExecutable "C:/Program Files (x86)/Git_1.8.0.2/cmd/git.exe" } @Test void "Only cygwin"() { - fs "/cygwin/bin/git.exe" + fs "C:/cygwin/bin/git.exe" assertExecutable "C:/cygwin/bin/git.exe" } @Test void "Many different versions, real case"() { - fs "/Program Files (x86)/Git_1.7.0.2/bin/git.exe", - "/Program Files (x86)/Git_1.7.0.2/cmd/git.cmd", - "/Program Files (x86)/Git_1.7.8/bin/git.exe", - "/Program Files (x86)/Git_1.7.8/cmd/git.cmd", - "/Program Files (x86)/Git_1.8/bin/git.exe", - "/Program Files (x86)/Git_1.8/cmd/git.cmd", - "/Program Files (x86)/Git_1.8/cmd/git.exe", - "/Program Files (x86)/Git_1.8.0.2/cmd/git.exe", - "/Program Files (x86)/Git_1.8.0.2/bin/git.exe", - "/cygwin/bin/git.exe" + fs "C:/Program Files (x86)/Git_1.7.0.2/bin/git.exe", + "C:/Program Files (x86)/Git_1.7.0.2/cmd/git.cmd", + "C:/Program Files (x86)/Git_1.7.8/bin/git.exe", + "C:/Program Files (x86)/Git_1.7.8/cmd/git.cmd", + "C:/Program Files (x86)/Git_1.8/bin/git.exe", + "C:/Program Files (x86)/Git_1.8/cmd/git.cmd", + "C:/Program Files (x86)/Git_1.8/cmd/git.exe", + "C:/Program Files (x86)/Git_1.8.0.2/cmd/git.exe", + "C:/Program Files (x86)/Git_1.8.0.2/bin/git.exe", + "C:/cygwin/bin/git.exe" assertExecutable "C:/Program Files (x86)/Git_1.8.0.2/cmd/git.exe" } @Test void "Program not found, try git.exe"() { - canRun = [ "git.exe" ] + CAN_RUN = [ "git.exe" ] assertExecutable "git.exe" } @Test void "For both git.exe and git.cmd prefer git.cmd"() { - canRun = [ "git.exe", "git.cmd" ] + CAN_RUN = [ "git.exe", "git.cmd" ] assertExecutable "git.cmd" } + @Test + void "Find Git in PATH"() { + PATH = "D:/Program Files (x86)/Git_distr/cmd"; + fs "D:/Program Files (x86)/Git_distr/cmd/git.cmd" + assertExecutable "D:/Program Files (x86)/Git_distr/cmd/git.cmd" + } + + @Test + void "Find Git in PATH not on the first place"() { + PATH = "C:/Ruby193/bin;C:/Users/John.Doe/Documents/Git_1.8.0.2/bin"; + fs "C:/Users/John.Doe/Documents/Git_1.8.0.2/bin/git.exe" + assertExecutable "C:/Users/John.Doe/Documents/Git_1.8.0.2/bin/git.exe" + } + + @Test + void "No Git in PATH, then look in Program Files"() { + PATH = "%SystemRoot%/system32;%SystemRoot%;%SystemRoot%/System32/Wbem;%SYSTEMROOT%/System32/WindowsPowerShell/v1.0/;" + + "C:/Program Files/Intel/DMIX;C:/Program Files/Mercurial/;C:/Program Files/TortoiseHg/;" + fs "C:/Program Files/Git/bin/git.exe" + assertExecutable "C:/Program Files/Git/bin/git.exe" + } + + @Test + void "Prefer PATH to Program Files"() { + PATH = "C:/Ruby193/bin;C:/Users/John.Doe/Documents/Git_1.8.0.2/bin"; + fs "C:/Program Files/Git/bin/git.exe", + "C:/Users/John.Doe/Documents/Git_1.8.0.2/bin/git.exe" + assertExecutable "C:/Users/John.Doe/Documents/Git_1.8.0.2/bin/git.exe" + } + + @Test + void "Prefer the first entry from the PATH"() { + PATH = "C:/Ruby193/bin;D:/Git/cmd;C:/Users/John.Doe/Documents/Git_1.8.0.2/bin;"; + fs "D:/Git/cmd/git.cmd", + "C:/Users/John.Doe/Documents/Git_1.8.0.2/bin/git.exe" + assertExecutable "D:/Git/cmd/git.cmd" + } + + @Test + void "Don't use Git from PATH if it doesn't exist on disk"() { + PATH = "C:/Ruby193/bin;D:/Git/cmd;"; + fs "C:/Program Files/Git/bin/git.exe" + assertExecutable "C:/Program Files/Git/bin/git.exe" + } + def assertExecutable(String expected) { - expected = FileUtil.toSystemDependentName(expected).replace("C:", testRoot.path) - assertEquals "Incorrect executable detected", expected, detect() + // we want to specify unix-like paths in "expected", because they are easier to write :) + def detected = detect() + detected = convertBack(detected) + assertEquals "Incorrect executable detected", expected, detected + } + + String convertBack(String detected) { + detected = detected.replace(testRoot.path + "/", "").replace('\\', '/') + detected = returnDiskColor(detected) + return detected } def fs(String... paths) { @@ -141,16 +201,36 @@ class GitExecutableDetectorWindowsTest { } def mkPath(String path) { - def file = new File(testRoot.getPath() + FileUtil.toSystemDependentName(path)) + path = FileUtil.toSystemDependentName(replaceDiskColon(path)) + def file = new File(testRoot.getPath() + File.separator + path) file.getParentFile().mkdirs() file.createNewFile() } + static String replaceDiskColon(String path) { + if (path[1] == ':') { + return path[0] + "_" + path.substring(2) + } + return path + } + + private static String returnDiskColor(String path) { + if (path[1] == '_') { + return path[0] + ":" + path.substring(2) + } + return path + } + private String detect() { new GitExecutableDetector() { @Override protected boolean runs(@NotNull String exec) { - return canRun.contains(exec); + return CAN_RUN.contains(exec); + } + + @Override + protected String getPath() { + return PATH.replaceAll('(\\w):', testRoot.path + File.separator + "\$1_"); } }.detect(); }