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.
This commit is contained in:
Kirill Likhodedov
2012-12-28 19:20:47 +04:00
parent 1f0ede6098
commit 6daec80727
2 changed files with 157 additions and 36 deletions
@@ -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<String> 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<String> 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<File> {
@@ -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();
}