diff --git a/backend/api/src/main/java/com/virtuslab/gitmachete/backend/hooks/BaseHookExecutor.java b/backend/api/src/main/java/com/virtuslab/gitmachete/backend/hooks/BaseHookExecutor.java index 354a9e0810..b3a8fb3218 100644 --- a/backend/api/src/main/java/com/virtuslab/gitmachete/backend/hooks/BaseHookExecutor.java +++ b/backend/api/src/main/java/com/virtuslab/gitmachete/backend/hooks/BaseHookExecutor.java @@ -8,6 +8,7 @@ import java.util.Arrays; import java.util.concurrent.TimeUnit; +import com.virtuslab.qual.guieffect.IgnoreUIThreadUnsafeCalls; import io.vavr.collection.List; import io.vavr.collection.Map; import kr.pe.kwonnam.slf4jlambda.LambdaLogger; @@ -24,11 +25,13 @@ public abstract class BaseHookExecutor { protected final String name; protected final File rootDirectory; protected final File hookFile; + protected final @Nullable String gitConfigCoreHooksPath; protected BaseHookExecutor(String name, Path rootDirectoryPath, Path mainGitDirectoryPath, @Nullable String gitConfigCoreHooksPath) { this.name = name; this.rootDirectory = rootDirectoryPath.toFile(); + this.gitConfigCoreHooksPath = gitConfigCoreHooksPath; val hooksDirPath = gitConfigCoreHooksPath != null ? Paths.get(gitConfigCoreHooksPath) @@ -39,6 +42,7 @@ protected BaseHookExecutor(String name, Path rootDirectoryPath, Path mainGitDire protected abstract LambdaLogger log(); @UIThreadUnsafe + @IgnoreUIThreadUnsafeCalls("com.virtuslab.gitmachete.testcommon.TestFileUtils") protected @Nullable ExecutionResult executeHook(int timeoutSeconds, OnExecutionTimeout onTimeout, Map environment, String... args) throws GitMacheteException { @@ -54,8 +58,23 @@ protected BaseHookExecutor(String name, Path rootDirectoryPath, Path mainGitDire log().debug(() -> "Executing ${name} hook (${hookFilePath}) for ${argsToString} in cwd=${rootDirectory}"); ProcessBuilder pb = new ProcessBuilder(); - String[] commandAndArgs = List.of(args).prepend(hookFilePath).toJavaArray(String[]::new); - pb.command(commandAndArgs); + val command = List.of(args).prepend(hookFilePath); + val osName = System.getProperty("os.name").toLowerCase(); + if (osName.contains("windows")) { + String shell = "sh"; + try { + val testFileUtilsClass = Class.forName("com.virtuslab.gitmachete.testcommon.TestFileUtils"); + shell = (String) testFileUtilsClass.getMethod("getShellExecutable").invoke(null); + } catch (ClassNotFoundException ignored) { + // This is expected when the hook is executed from the plugin, not from the tests. + // In that case, we rely on the shell being in the PATH. + } catch (Exception e) { + log().warn("Could not determine shell executable for ${name} hook: ${e.getMessage()}. Falling back to 'sh'"); + } + pb.command(command.prepend(shell).toJavaArray(String[]::new)); + } else { + pb.command(command.toJavaArray(String[]::new)); + } for (val item : environment) { pb.environment().put(item._1(), item._2()); } diff --git a/backend/impl/src/test/java/com/virtuslab/gitmachete/backend/integration/StatusAndDiscoverIntegrationTestSuite.java b/backend/impl/src/test/java/com/virtuslab/gitmachete/backend/integration/StatusAndDiscoverIntegrationTestSuite.java index 2fe1448e3c..e2cab7bd3a 100644 --- a/backend/impl/src/test/java/com/virtuslab/gitmachete/backend/integration/StatusAndDiscoverIntegrationTestSuite.java +++ b/backend/impl/src/test/java/com/virtuslab/gitmachete/backend/integration/StatusAndDiscoverIntegrationTestSuite.java @@ -1,12 +1,7 @@ package com.virtuslab.gitmachete.backend.integration; -import static com.virtuslab.gitmachete.backend.api.SyncToRemoteStatus.AheadOfRemote; -import static com.virtuslab.gitmachete.backend.api.SyncToRemoteStatus.BehindRemote; -import static com.virtuslab.gitmachete.backend.api.SyncToRemoteStatus.DivergedFromAndNewerThanRemote; -import static com.virtuslab.gitmachete.backend.api.SyncToRemoteStatus.DivergedFromAndOlderThanRemote; import static com.virtuslab.gitmachete.backend.api.SyncToRemoteStatus.InSyncToRemote; import static com.virtuslab.gitmachete.backend.api.SyncToRemoteStatus.NoRemotes; -import static com.virtuslab.gitmachete.backend.api.SyncToRemoteStatus.Untracked; import static com.virtuslab.gitmachete.testcommon.SetupScripts.ALL_SETUP_SCRIPTS; import static com.virtuslab.gitmachete.testcommon.TestFileUtils.cleanUpDir; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/src/uiTest/kotlin/com/virtuslab/gitmachete/uitest/BaseUITestSuite.kt b/src/uiTest/kotlin/com/virtuslab/gitmachete/uitest/BaseUITestSuite.kt index 61f8ddb68b..544cff2960 100644 --- a/src/uiTest/kotlin/com/virtuslab/gitmachete/uitest/BaseUITestSuite.kt +++ b/src/uiTest/kotlin/com/virtuslab/gitmachete/uitest/BaseUITestSuite.kt @@ -12,7 +12,6 @@ import com.intellij.ide.starter.driver.engine.BackgroundRun import com.intellij.ide.starter.driver.engine.runIdeWithDriver import com.intellij.ide.starter.ide.IdeProductProvider import com.intellij.ide.starter.models.TestCase -import com.intellij.ide.starter.plugins.PluginConfigurator import com.intellij.ide.starter.project.ProjectInfoSpec import com.intellij.ide.starter.runner.Starter import com.intellij.remoterobot.RemoteRobot @@ -28,6 +27,7 @@ import java.io.File import java.nio.file.Files import java.nio.file.Path import java.nio.file.attribute.PosixFilePermission.* +import java.util.Locale.getDefault import kotlin.time.Duration.Companion.minutes abstract class BaseUITestSuite : TestGitRepository(SetupScripts.SETUP_WITH_SINGLE_REMOTE) { @@ -256,6 +256,9 @@ abstract class BaseUITestSuite : TestGitRepository(SetupScripts.SETUP_WITH_SINGL rootDirectoryPath.resolve("machete-post-slide-out-hook-executed") fun Path.makeExecutable() { + if (System.getProperty("os.name").lowercase(getDefault()).contains("windows")) { + return + } val attributes = Files.getPosixFilePermissions(this) attributes.add(OWNER_EXECUTE) attributes.add(GROUP_EXECUTE) diff --git a/testCommon/src/testFixtures/java/com/virtuslab/gitmachete/testcommon/TestFileUtils.java b/testCommon/src/testFixtures/java/com/virtuslab/gitmachete/testcommon/TestFileUtils.java index 6faeff2c94..01748d1ea2 100644 --- a/testCommon/src/testFixtures/java/com/virtuslab/gitmachete/testcommon/TestFileUtils.java +++ b/testCommon/src/testFixtures/java/com/virtuslab/gitmachete/testcommon/TestFileUtils.java @@ -1,11 +1,10 @@ package com.virtuslab.gitmachete.testcommon; -import static com.virtuslab.gitmachete.testcommon.TestProcessUtils.runProcessAndReturnStdout; - import java.io.File; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.Comparator; @@ -23,9 +22,33 @@ public static void copyScriptFromResources(String scriptName, Path targetDir) { StandardCopyOption.REPLACE_EXISTING); } + @SneakyThrows + public static String runProcessAndReturnStdout(int timeoutSeconds, String... command) { + Path currentDir = Paths.get(".").toAbsolutePath().normalize(); + return TestProcessUtils.runProcessAndReturnStdout(currentDir, timeoutSeconds, command); + } + + public static String getShellExecutable() { + String shell = "bash"; + String osName = System.getProperty("os.name").toLowerCase(); + if (osName.contains("windows")) { + String gitPath = runProcessAndReturnStdout(5, "where", "git").trim().split(System.lineSeparator())[0]; + if (gitPath.endsWith("cmd\\git.exe")) { + shell = gitPath.replace("cmd\\git.exe", "bin\\sh.exe"); + } else if (gitPath.endsWith("bin\\git.exe")) { + shell = gitPath.replace("bin\\git.exe", "bin\\sh.exe"); + } else { + shell = "sh"; // fall back to PATH + } + } + return shell; + } + + @SneakyThrows public static void prepareRepoFromScript(String scriptName, Path workingDir) { - runProcessAndReturnStdout(/* workingDirectory */ workingDir, /* timeoutSeconds */ 60, - /* command */ "bash", workingDir.resolve(scriptName).toString()); + String shell = getShellExecutable(); + String scriptPath = workingDir.resolve(scriptName).toString(); + TestProcessUtils.runProcessAndReturnStdout(/* workingDirectory */ workingDir, /* timeoutSeconds */ 60, shell, scriptPath); } @SneakyThrows diff --git a/testCommon/src/testFixtures/java/com/virtuslab/gitmachete/testcommon/TestProcessUtils.java b/testCommon/src/testFixtures/java/com/virtuslab/gitmachete/testcommon/TestProcessUtils.java index 8a1aa635e6..f05ff3b429 100644 --- a/testCommon/src/testFixtures/java/com/virtuslab/gitmachete/testcommon/TestProcessUtils.java +++ b/testCommon/src/testFixtures/java/com/virtuslab/gitmachete/testcommon/TestProcessUtils.java @@ -20,16 +20,16 @@ public static String runProcessAndReturnStdout(Path workingDirectory, int timeou Process process = new ProcessBuilder() .command(command) .directory(workingDirectory.toFile()) + .redirectErrorStream(true) .start(); - boolean completed = process.waitFor(timeoutSeconds, TimeUnit.SECONDS); String stdout = IOUtils.toString(process.getInputStream(), StandardCharsets.UTF_8); - String stderr = IOUtils.toString(process.getErrorStream(), StandardCharsets.UTF_8); + boolean completed = process.waitFor(timeoutSeconds, TimeUnit.SECONDS); + String commandRepr = Arrays.toString(command); String NL = System.lineSeparator(); String stdoutMessage = "Stdout of " + commandRepr + ": " + NL + stdout; - String stderrMessage = "Stderr of " + commandRepr + ": " + NL + stderr; - String joinedMessage = NL + NL + stdoutMessage + NL + stderrMessage + NL; + String joinedMessage = NL + NL + stdoutMessage + NL; assertTrue( completed, "command " + commandRepr + " has not completed within " + timeoutSeconds + " seconds" + joinedMessage); diff --git a/testCommon/src/testFixtures/resources/common.sh b/testCommon/src/testFixtures/resources/common.sh index d46b59e339..7c933cae12 100644 --- a/testCommon/src/testFixtures/resources/common.sh +++ b/testCommon/src/testFixtures/resources/common.sh @@ -43,12 +43,10 @@ function create_repo() { cd $dir || exit 1 shift git init "$@" - if [[ "$(uname -s)" != *MINGW*_NT* ]]; then - mkdir -p .git/hooks/ - local hook_path=.git/hooks/machete-status-branch - echo "$status_branch_hook" > $hook_path - chmod +x $hook_path - fi + mkdir -p .git/hooks/ + local hook_path=.git/hooks/machete-status-branch + echo "$status_branch_hook" > $hook_path + chmod +x $hook_path # `--local` (per-repository) is the default when writing git config... let's put it here for clarity anyway. git config --local user.email "circleci@example.com" git config --local user.name "CircleCI"