CommandExecutor.java
- /*
- * Copyright (C) 2018-2021, Andre Bossert <andre.bossert@siemens.com>
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Distribution License v. 1.0 which is available at
- * https://www.eclipse.org/org/documents/edl-v10.php.
- *
- * SPDX-License-Identifier: BSD-3-Clause
- */
- package org.eclipse.jgit.internal.diffmergetool;
- import java.io.File;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.OutputStream;
- import java.nio.file.Files;
- import java.nio.file.Path;
- import java.nio.file.Paths;
- import java.util.Arrays;
- import java.util.Map;
- import org.eclipse.jgit.errors.NoWorkTreeException;
- import org.eclipse.jgit.util.FS;
- import org.eclipse.jgit.util.FS.ExecutionResult;
- import org.eclipse.jgit.util.FS_POSIX;
- import org.eclipse.jgit.util.FS_Win32;
- import org.eclipse.jgit.util.FS_Win32_Cygwin;
- import org.eclipse.jgit.util.StringUtils;
- import org.eclipse.jgit.util.SystemReader;
- /**
- * Runs a command with help of FS.
- */
- public class CommandExecutor {
- private FS fs;
- private boolean checkExitCode;
- private File commandFile;
- private boolean useMsys2;
- /**
- * @param fs
- * the file system
- * @param checkExitCode
- * should the exit code be checked for errors ?
- */
- public CommandExecutor(FS fs, boolean checkExitCode) {
- this.fs = fs;
- this.checkExitCode = checkExitCode;
- }
- /**
- * @param command
- * the command string
- * @param workingDir
- * the working directory
- * @param env
- * the environment
- * @return the execution result
- * @throws ToolException
- * @throws InterruptedException
- * @throws IOException
- */
- public ExecutionResult run(String command, File workingDir,
- Map<String, String> env)
- throws ToolException, IOException, InterruptedException {
- String[] commandArray = createCommandArray(command);
- try {
- ProcessBuilder pb = fs.runInShell(commandArray[0],
- Arrays.copyOfRange(commandArray, 1, commandArray.length));
- pb.directory(workingDir);
- Map<String, String> envp = pb.environment();
- if (env != null) {
- envp.putAll(env);
- }
- ExecutionResult result = fs.execute(pb, null);
- int rc = result.getRc();
- if (rc != 0) {
- boolean execError = isCommandExecutionError(rc);
- if (checkExitCode || execError) {
- throw new ToolException(
- "JGit: tool execution return code: " + rc + "\n" //$NON-NLS-1$ //$NON-NLS-2$
- + "checkExitCode: " + checkExitCode + "\n" //$NON-NLS-1$ //$NON-NLS-2$
- + "execError: " + execError + "\n" //$NON-NLS-1$ //$NON-NLS-2$
- + "stderr: \n" //$NON-NLS-1$
- + new String(
- result.getStderr().toByteArray(),
- SystemReader.getInstance()
- .getDefaultCharset()),
- result, execError);
- }
- }
- return result;
- } finally {
- deleteCommandArray();
- }
- }
- /**
- * @param path
- * the executable path
- * @param workingDir
- * the working directory
- * @param env
- * the environment
- * @return the execution result
- * @throws ToolException
- * @throws InterruptedException
- * @throws IOException
- */
- public boolean checkExecutable(String path, File workingDir,
- Map<String, String> env)
- throws ToolException, IOException, InterruptedException {
- checkUseMsys2(path);
- String command = null;
- if (fs instanceof FS_Win32 && !useMsys2) {
- Path p = Paths.get(path);
- // Win32 (and not cygwin or MSYS2) where accepts only command / exe
- // name as parameter
- // so check if exists and executable in this case
- if (p.isAbsolute() && Files.isExecutable(p)) {
- return true;
- }
- // try where command for all other cases
- command = "where " + ExternalToolUtils.quotePath(path); //$NON-NLS-1$
- } else {
- command = "which " + ExternalToolUtils.quotePath(path); //$NON-NLS-1$
- }
- boolean available = true;
- try {
- ExecutionResult rc = run(command, workingDir, env);
- if (rc.getRc() != 0) {
- available = false;
- }
- } catch (IOException | InterruptedException | NoWorkTreeException
- | ToolException e) {
- // no op: is true to not hide possible tools from user
- }
- return available;
- }
- private void deleteCommandArray() {
- deleteCommandFile();
- }
- private String[] createCommandArray(String command)
- throws ToolException, IOException {
- String[] commandArray = null;
- checkUseMsys2(command);
- createCommandFile(command);
- if (fs instanceof FS_POSIX) {
- commandArray = new String[1];
- commandArray[0] = commandFile.getCanonicalPath();
- } else if (fs instanceof FS_Win32) {
- if (useMsys2) {
- commandArray = new String[3];
- commandArray[0] = "bash.exe"; //$NON-NLS-1$
- commandArray[1] = "-c"; //$NON-NLS-1$
- commandArray[2] = commandFile.getCanonicalPath().replace("\\", //$NON-NLS-1$
- "/"); //$NON-NLS-1$
- } else {
- commandArray = new String[1];
- commandArray[0] = commandFile.getCanonicalPath();
- }
- } else if (fs instanceof FS_Win32_Cygwin) {
- commandArray = new String[1];
- commandArray[0] = commandFile.getCanonicalPath().replace("\\", "/"); //$NON-NLS-1$ //$NON-NLS-2$
- } else {
- throw new ToolException(
- "JGit: file system not supported: " + fs.toString()); //$NON-NLS-1$
- }
- return commandArray;
- }
- private void checkUseMsys2(String command) {
- useMsys2 = false;
- String useMsys2Str = System.getProperty("jgit.usemsys2bash"); //$NON-NLS-1$
- if (!StringUtils.isEmptyOrNull(useMsys2Str)) {
- if (useMsys2Str.equalsIgnoreCase("auto")) { //$NON-NLS-1$
- useMsys2 = command.contains(".sh"); //$NON-NLS-1$
- } else {
- useMsys2 = Boolean.parseBoolean(useMsys2Str);
- }
- }
- }
- private void createCommandFile(String command)
- throws ToolException, IOException {
- String fileExtension = null;
- if (useMsys2 || fs instanceof FS_POSIX
- || fs instanceof FS_Win32_Cygwin) {
- fileExtension = ".sh"; //$NON-NLS-1$
- } else if (fs instanceof FS_Win32) {
- fileExtension = ".cmd"; //$NON-NLS-1$
- command = "@echo off" + System.lineSeparator() + command //$NON-NLS-1$
- + System.lineSeparator() + "exit /B %ERRORLEVEL%"; //$NON-NLS-1$
- } else {
- throw new ToolException(
- "JGit: file system not supported: " + fs.toString()); //$NON-NLS-1$
- }
- commandFile = File.createTempFile(".__", //$NON-NLS-1$
- "__jgit_tool" + fileExtension); //$NON-NLS-1$
- try (OutputStream outStream = new FileOutputStream(commandFile)) {
- byte[] strToBytes = command
- .getBytes(SystemReader.getInstance().getDefaultCharset());
- outStream.write(strToBytes);
- outStream.close();
- }
- commandFile.setExecutable(true);
- }
- private void deleteCommandFile() {
- if (commandFile != null && commandFile.exists()) {
- commandFile.delete();
- }
- }
- private boolean isCommandExecutionError(int rc) {
- if (useMsys2 || fs instanceof FS_POSIX
- || fs instanceof FS_Win32_Cygwin) {
- // 126: permission for executing command denied
- // 127: command not found
- if ((rc == 126) || (rc == 127)) {
- return true;
- }
- }
- else if (fs instanceof FS_Win32) {
- // 9009, 0x2331: Program is not recognized as an internal or
- // external command, operable program or batch file. Indicates that
- // command, application name or path has been misspelled when
- // configuring the Action.
- if (rc == 9009) {
- return true;
- }
- }
- return false;
- }
- }