diff options
author | Andrey Loskutov <loskutov@gmx.de> | 2022-05-27 01:02:15 -0400 |
---|---|---|
committer | Gerrit Code Review @ Eclipse.org <gerrit@eclipse.org> | 2022-05-27 01:02:15 -0400 |
commit | d2489ffa7080e8353b34f22484e5c6b371747f5a (patch) | |
tree | 53006175cc6925ce9894563c3599c615e07a3edd /org.eclipse.jgit | |
parent | 5828ca160233550bc8f6d05ec9819c46fbffe5f3 (diff) | |
parent | eaf4d500b886a7e776f50bf53497fe463e714b25 (diff) | |
download | jgit-d2489ffa7080e8353b34f22484e5c6b371747f5a.tar.gz jgit-d2489ffa7080e8353b34f22484e5c6b371747f5a.zip |
Merge "Add mergetool merge feature (execute external tool)"
Diffstat (limited to 'org.eclipse.jgit')
4 files changed, 178 insertions, 65 deletions
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandExecutor.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandExecutor.java index 0dde9b5f39..ad79fe8fc6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandExecutor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandExecutor.java @@ -72,10 +72,18 @@ public class CommandExecutor { } ExecutionResult result = fs.execute(pb, null); int rc = result.getRc(); - if ((rc != 0) && (checkExitCode - || isCommandExecutionError(rc))) { - throw new ToolException( - new String(result.getStderr().toByteArray()), result); + 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()), + result, execError); + } } return result; } finally { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/FileElement.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/FileElement.java index cdc8f015f6..1ae87aaa62 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/FileElement.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/FileElement.java @@ -11,6 +11,7 @@ package org.eclipse.jgit.internal.diffmergetool; import java.io.File; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; @@ -80,35 +81,27 @@ public class FileElement { } /** - * @param workingDir the working directory used if file cannot be found (e.g. /dev/null) + * Returns a temporary file with in passed working directory and fills it + * with stream if valid. + * + * @param directory + * the working directory where the temporary file is created + * @param midName + * name added in the middle of generated temporary file name * @return the object stream * @throws IOException */ - public File getFile(File workingDir) throws IOException { + public File getFile(File directory, String midName) throws IOException { if (tempFile != null) { return tempFile; } - File file = new File(path); - String name = file.getName(); - if (path.equals(DiffEntry.DEV_NULL)) { - file = new File(workingDir, "nul"); //$NON-NLS-1$ - } - else if (stream != null) { - tempFile = File.createTempFile(".__", "__" + name); //$NON-NLS-1$ //$NON-NLS-2$ - try (OutputStream outStream = new FileOutputStream(tempFile)) { - int read = 0; - byte[] bytes = new byte[8 * 1024]; - while ((read = stream.read(bytes)) != -1) { - outStream.write(bytes, 0, read); - } - } finally { - // stream can only be consumed once --> close it - stream.close(); - stream = null; - } - return tempFile; - } - return file; + String[] fileNameAndExtension = splitBaseFileNameAndExtension( + new File(path)); + tempFile = File.createTempFile( + fileNameAndExtension[0] + "_" + midName + "_", //$NON-NLS-1$ //$NON-NLS-2$ + fileNameAndExtension[1], directory); + copyFromStream(); + return tempFile; } /** @@ -130,19 +123,7 @@ public class FileElement { // TODO: avoid long random file name (number generated by // createTempFile) tempFile = File.createTempFile(".__", "__" + name); //$NON-NLS-1$ //$NON-NLS-2$ - if (stream != null) { - try (OutputStream outStream = new FileOutputStream(tempFile)) { - int read = 0; - byte[] bytes = new byte[8 * 1024]; - while ((read = stream.read(bytes)) != -1) { - outStream.write(bytes, 0, read); - } - } finally { - // stream can only be consumed once --> close it - stream.close(); - stream = null; - } - } + copyFromStream(); return tempFile; } return file; @@ -157,4 +138,34 @@ public class FileElement { tempFile = null; } + private void copyFromStream() throws IOException, FileNotFoundException { + if (stream != null) { + try (OutputStream outStream = new FileOutputStream(tempFile)) { + int read = 0; + byte[] bytes = new byte[8 * 1024]; + while ((read = stream.read(bytes)) != -1) { + outStream.write(bytes, 0, read); + } + } finally { + // stream can only be consumed once --> close it + stream.close(); + stream = null; + } + } + } + + private static String[] splitBaseFileNameAndExtension(File file) { + String[] result = new String[2]; + result[0] = file.getName(); + result[1] = ""; //$NON-NLS-1$ + if (!result[0].startsWith(".")) { //$NON-NLS-1$ + int idx = result[0].lastIndexOf("."); //$NON-NLS-1$ + if (idx != -1) { + result[1] = result[0].substring(idx, result[0].length()); + result[0] = result[0].substring(0, idx); + } + } + return result; + } + } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeTools.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeTools.java index cefefb8e75..c4c2ceccff 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeTools.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeTools.java @@ -10,6 +10,11 @@ package org.eclipse.jgit.internal.diffmergetool; import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.Map; import java.util.Set; import java.util.TreeMap; @@ -48,7 +53,7 @@ public class MergeTools { * @param remoteFile * the remote file element * @param baseFile - * the base file element + * the base file element (can be null) * @param mergedFilePath * the path of 'merged' file * @param toolName @@ -65,33 +70,77 @@ public class MergeTools { String toolName, BooleanTriState prompt, BooleanTriState gui) throws ToolException { ExternalMergeTool tool = guessTool(toolName, gui); + FileElement backup = null; + File tempDir = null; + ExecutionResult result = null; try { File workingDir = repo.getWorkTree(); - String localFilePath = localFile.getFile().getPath(); - String remoteFilePath = remoteFile.getFile().getPath(); - String baseFilePath = baseFile.getFile().getPath(); - String command = tool.getCommand(); - command = command.replace("$LOCAL", localFilePath); //$NON-NLS-1$ - command = command.replace("$REMOTE", remoteFilePath); //$NON-NLS-1$ - command = command.replace("$MERGED", mergedFilePath); //$NON-NLS-1$ - command = command.replace("$BASE", baseFilePath); //$NON-NLS-1$ - Map<String, String> env = new TreeMap<>(); - env.put(Constants.GIT_DIR_KEY, - repo.getDirectory().getAbsolutePath()); - env.put("LOCAL", localFilePath); //$NON-NLS-1$ - env.put("REMOTE", remoteFilePath); //$NON-NLS-1$ - env.put("MERGED", mergedFilePath); //$NON-NLS-1$ - env.put("BASE", baseFilePath); //$NON-NLS-1$ + // crate temp-directory or use working directory + tempDir = config.isWriteToTemp() + ? Files.createTempDirectory("jgit-mergetool-").toFile() //$NON-NLS-1$ + : workingDir; + // create additional backup file (copy worktree file) + backup = createBackupFile(mergedFilePath, tempDir); + // get local, remote and base file paths + String localFilePath = localFile.getFile(tempDir, "LOCAL") //$NON-NLS-1$ + .getPath(); + String remoteFilePath = remoteFile.getFile(tempDir, "REMOTE") //$NON-NLS-1$ + .getPath(); + String baseFilePath = ""; //$NON-NLS-1$ + if (baseFile != null) { + baseFilePath = baseFile.getFile(tempDir, "BASE").getPath(); //$NON-NLS-1$ + } + // prepare the command (replace the file paths) boolean trust = tool.getTrustExitCode() == BooleanTriState.TRUE; + String command = prepareCommand(mergedFilePath, localFilePath, + remoteFilePath, baseFilePath, + tool.getCommand(baseFile != null)); + // prepare the environment + Map<String, String> env = prepareEnvironment(repo, mergedFilePath, + localFilePath, remoteFilePath, baseFilePath); CommandExecutor cmdExec = new CommandExecutor(repo.getFS(), trust); - return cmdExec.run(command, workingDir, env); + result = cmdExec.run(command, workingDir, env); + // keep backup as .orig file + if (backup != null) { + keepBackupFile(mergedFilePath, backup); + } + return result; } catch (Exception e) { throw new ToolException(e); } finally { - localFile.cleanTemporaries(); - remoteFile.cleanTemporaries(); - baseFile.cleanTemporaries(); + // always delete backup file (ignore that it was may be already + // moved to keep-backup file) + if (backup != null) { + backup.cleanTemporaries(); + } + // if the tool returns an error and keepTemporaries is set to true, + // then these temporary files will be preserved + if (!((result == null) && config.isKeepTemporaries())) { + // delete the files + localFile.cleanTemporaries(); + remoteFile.cleanTemporaries(); + if (baseFile != null) { + baseFile.cleanTemporaries(); + } + // delete temporary directory if needed + if (config.isWriteToTemp() && (tempDir != null) + && tempDir.exists()) { + tempDir.delete(); + } + } + } + } + + private static FileElement createBackupFile(String mergedFilePath, + File tempDir) throws IOException { + FileElement backup = null; + Path path = Paths.get(tempDir.getPath(), mergedFilePath); + if (Files.exists(path)) { + backup = new FileElement(mergedFilePath, "NOID", null); //$NON-NLS-1$ + Files.copy(path, backup.getFile(tempDir, "BACKUP").toPath(), //$NON-NLS-1$ + StandardCopyOption.REPLACE_EXISTING); } + return backup; } /** @@ -159,6 +208,38 @@ public class MergeTools { return tool; } + private String prepareCommand(String mergedFilePath, String localFilePath, + String remoteFilePath, String baseFilePath, String command) { + command = command.replace("$LOCAL", localFilePath); //$NON-NLS-1$ + command = command.replace("$REMOTE", remoteFilePath); //$NON-NLS-1$ + command = command.replace("$MERGED", mergedFilePath); //$NON-NLS-1$ + command = command.replace("$BASE", baseFilePath); //$NON-NLS-1$ + return command; + } + + private Map<String, String> prepareEnvironment(Repository repo, + String mergedFilePath, String localFilePath, String remoteFilePath, + String baseFilePath) { + Map<String, String> env = new TreeMap<>(); + env.put(Constants.GIT_DIR_KEY, repo.getDirectory().getAbsolutePath()); + env.put("LOCAL", localFilePath); //$NON-NLS-1$ + env.put("REMOTE", remoteFilePath); //$NON-NLS-1$ + env.put("MERGED", mergedFilePath); //$NON-NLS-1$ + env.put("BASE", baseFilePath); //$NON-NLS-1$ + return env; + } + + private void keepBackupFile(String mergedFilePath, FileElement backup) + throws IOException { + if (config.isKeepBackup()) { + Path backupPath = backup.getFile().toPath(); + Files.move(backupPath, + backupPath.resolveSibling( + Paths.get(mergedFilePath).getFileName() + ".orig"), //$NON-NLS-1$ + StandardCopyOption.REPLACE_EXISTING); + } + } + private Map<String, ExternalMergeTool> setupPredefinedTools() { Map<String, ExternalMergeTool> tools = new TreeMap<>(); for (CommandLineMergeTool tool : CommandLineMergeTool.values()) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ToolException.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ToolException.java index 7862cf5967..1ae0780ac8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ToolException.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ToolException.java @@ -26,6 +26,8 @@ public class ToolException extends Exception { private final ExecutionResult result; + private final boolean commandExecutionError; + /** * the serial version UID */ @@ -35,8 +37,7 @@ public class ToolException extends Exception { * */ public ToolException() { - super(); - result = null; + this(null, null, false); } /** @@ -44,8 +45,7 @@ public class ToolException extends Exception { * the exception message */ public ToolException(String message) { - super(message); - result = null; + this(message, null, false); } /** @@ -53,10 +53,14 @@ public class ToolException extends Exception { * the exception message * @param result * the execution result + * @param commandExecutionError + * is command execution error happened ? */ - public ToolException(String message, ExecutionResult result) { + public ToolException(String message, ExecutionResult result, + boolean commandExecutionError) { super(message); this.result = result; + this.commandExecutionError = commandExecutionError; } /** @@ -68,6 +72,7 @@ public class ToolException extends Exception { public ToolException(String message, Throwable cause) { super(message, cause); result = null; + commandExecutionError = false; } /** @@ -77,6 +82,7 @@ public class ToolException extends Exception { public ToolException(Throwable cause) { super(cause); result = null; + commandExecutionError = false; } /** @@ -94,6 +100,13 @@ public class ToolException extends Exception { } /** + * @return true if command execution error appears, false otherwise + */ + public boolean isCommandExecutionError() { + return commandExecutionError; + } + + /** * @return the result Stderr */ public String getResultStderr() { |