aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndre Bossert <andre.bossert@siemens.com>2020-01-19 20:50:14 +0100
committerAndrey Loskutov <loskutov@gmx.de>2022-05-25 13:23:50 +0200
commitb63c2f39a16f1607cc65e82c0271c8c29a6038f1 (patch)
treebbb9e4155b28c72fb2bb986cfbe4c376c2f5cfce
parent0c749d33ba59f864e18400141b2510398a8dd77c (diff)
downloadjgit-b63c2f39a16f1607cc65e82c0271c8c29a6038f1.tar.gz
jgit-b63c2f39a16f1607cc65e82c0271c8c29a6038f1.zip
Add difftool compare feature (execute external tool)
see: http://git-scm.com/docs/git-difftool * add CommandExecutor that handles tool execution with help of "jgit.FS" * it handles tool execution with temporary created "command file" --> for for all "command interpreters" and parameters with spaces etc. * using of external bash.exe at Windows (MinGW) if shell-script is used as difftool command. It can be enabled with parameter "jgit.usemsys2bash=auto" that checks if command contains ".sh" or enabled / disabled with "jgit.usemsys2bash=true|false" * added special handling for empty files (e.g. deleted, added etc.) that are named "/dev/null" * added creation and deletion of temporary files needed for compare * added own Exception class for reporting to pgm / command line / EGit * added prompt option handling before executing difftool * reworked trustExitCode option for specific difftool and override for all difftools from config and command line * tested with command line options "--[no]-trust-exit-code", "--tool=<toolname>", "--[no]-gui", --[no]-prompt * ContentSource * added close() methods to close / cleanup used resources (like ObjectReader TreeWalk etc.) * added isWorkingTreeSource() methods to check if file can be used from working tree instead of copy from "ObjectLoader / ObjectReader" to temporary file (fixes "difftool <commit> <commit>") Bug: 356832 Change-Id: I5462fb6dbe4ecfd9da7c74117fce4070bbfd4d7a Signed-off-by: Andre Bossert <andre.bossert@siemens.com> Signed-off-by: Simeon Andreev <simeon.danailov.andreev@gmail.com>
-rw-r--r--org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DiffToolTest.java63
-rw-r--r--org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties2
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTool.java100
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalDiffToolTest.java149
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalToolTestCase.java13
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java77
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandExecutor.java182
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffToolConfig.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java109
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/FileElement.java160
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedDiffTool.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ToolException.java120
12 files changed, 894 insertions, 97 deletions
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DiffToolTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DiffToolTest.java
index e7bf48417d..e2ff189276 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DiffToolTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DiffToolTest.java
@@ -9,7 +9,13 @@
*/
package org.eclipse.jgit.pgm;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DIFFTOOL_SECTION;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DIFF_SECTION;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_CMD;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PROMPT;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_TOOL;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.Arrays;
@@ -19,6 +25,7 @@ import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.internal.diffmergetool.CommandLineDiffTool;
import org.eclipse.jgit.lib.CLIRepositoryTestCase;
+import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.pgm.opt.CmdLineParser;
import org.eclipse.jgit.pgm.opt.SubcommandHandler;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -60,6 +67,7 @@ public class DiffToolTest extends CLIRepositoryTestCase {
return result.outLines().toArray(new String[0]);
}
+ private static final String TOOL_NAME = "some_tool";
private Git git;
@Override
@@ -68,6 +76,15 @@ public class DiffToolTest extends CLIRepositoryTestCase {
super.setUp();
git = new Git(db);
git.commit().setMessage("initial commit").call();
+ configureEchoTool(TOOL_NAME);
+ }
+
+ @Test(expected = Die.class)
+ public void testNotDefinedTool() throws Exception {
+ createUnstagedChanges();
+
+ runAndCaptureUsingInitRaw("difftool", "--tool", "undefined");
+ fail("Expected exception when trying to run undefined tool");
}
@Test
@@ -85,7 +102,7 @@ public class DiffToolTest extends CLIRepositoryTestCase {
assertArrayOfLinesEquals("Incorrect output for option: " + option,
expectedOutput,
runAndCaptureUsingInitRaw("difftool", option,
- "some_tool"));
+ TOOL_NAME));
}
}
@@ -100,7 +117,7 @@ public class DiffToolTest extends CLIRepositoryTestCase {
for (String option : options) {
assertArrayOfLinesEquals("Incorrect output for option: " + option,
expectedOutput, runAndCaptureUsingInitRaw("difftool",
- "--trust-exit-code", option, "some_tool"));
+ "--trust-exit-code", option, TOOL_NAME));
}
}
@@ -116,7 +133,7 @@ public class DiffToolTest extends CLIRepositoryTestCase {
assertArrayOfLinesEquals("Incorrect output for option: " + option,
expectedOutput, runAndCaptureUsingInitRaw("difftool",
"--no-gui", "--no-prompt", "--no-trust-exit-code",
- option, "some_tool"));
+ option, TOOL_NAME));
}
}
@@ -131,7 +148,7 @@ public class DiffToolTest extends CLIRepositoryTestCase {
for (String option : options) {
assertArrayOfLinesEquals("Incorrect output for option: " + option,
expectedOutput, runAndCaptureUsingInitRaw("difftool",
- option, "--tool", "some_tool"));
+ option, "--tool", TOOL_NAME));
}
}
@@ -144,8 +161,11 @@ public class DiffToolTest extends CLIRepositoryTestCase {
String toolName = defaultTool.name();
expectedOutput.add(toolName);
}
+ String customToolHelpLine = TOOL_NAME + "." + CONFIG_KEY_CMD + " "
+ + getEchoCommand();
+ expectedOutput.add("user-defined:");
+ expectedOutput.add(customToolHelpLine);
String[] userDefinedToolsHelp = {
- "user-defined:",
"The following tools are valid, but not currently available:",
"Some of the tools listed above only work in a windowed",
"environment. If run in a terminal-only session, they will fail.",
@@ -157,6 +177,25 @@ public class DiffToolTest extends CLIRepositoryTestCase {
expectedOutput.toArray(new String[0]), runAndCaptureUsingInitRaw("difftool", option));
}
+ private void configureEchoTool(String toolName) {
+ StoredConfig config = db.getConfig();
+ // the default diff tool is configured without a subsection
+ String subsection = null;
+ config.setString(CONFIG_DIFF_SECTION, subsection, CONFIG_KEY_TOOL,
+ toolName);
+
+ String command = getEchoCommand();
+
+ config.setString(CONFIG_DIFFTOOL_SECTION, toolName, CONFIG_KEY_CMD,
+ command);
+ /*
+ * prevent prompts as we are running in tests and there is no user to
+ * interact with on the command line
+ */
+ config.setString(CONFIG_DIFFTOOL_SECTION, toolName, CONFIG_KEY_PROMPT,
+ String.valueOf(false));
+ }
+
private RevCommit createUnstagedChanges() throws Exception {
writeTrashFile("a", "Hello world a");
writeTrashFile("b", "Hello world b");
@@ -188,11 +227,7 @@ public class DiffToolTest extends CLIRepositoryTestCase {
for (int i = 0; i < changes.size(); ++i) {
DiffEntry change = changes.get(i);
String newPath = change.getNewPath();
- String oldPath = change.getOldPath();
- String newIdName = change.getNewId().name();
- String oldIdName = change.getOldId().name();
- String expectedLine = "M\t" + newPath + " (" + newIdName + ")"
- + "\t" + oldPath + " (" + oldIdName + ")";
+ String expectedLine = newPath;
expectedToolOutput[i] = expectedLine;
}
return expectedToolOutput;
@@ -202,4 +237,12 @@ public class DiffToolTest extends CLIRepositoryTestCase {
String[] expected, String[] actual) {
assertEquals(failMessage, toString(expected), toString(actual));
}
+
+ private static String getEchoCommand() {
+ /*
+ * use 'MERGED' placeholder, as both 'LOCAL' and 'REMOTE' will be
+ * replaced with full paths to a temporary file during some of the tests
+ */
+ return "(echo \"$MERGED\")";
+ }
}
diff --git a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
index fda0bf6ff4..3653b9d8fc 100644
--- a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
+++ b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
@@ -60,7 +60,7 @@ deletedBranch=Deleted branch {0}
deletedRemoteBranch=Deleted remote branch {0}
diffToolHelpSetToFollowing='git difftool --tool=<tool>' may be set to one of the following:\n{0}\n\tuser-defined:\n{1}\nThe following tools are valid, but not currently available:\n{2}\nSome of the tools listed above only work in a windowed\nenvironment. If run in a terminal-only session, they will fail.
diffToolLaunch=Viewing ({0}/{1}): '{2}'\nLaunch '{3}' [Y/n]?
-diffToolDied=external diff died, stopping at {0}
+diffToolDied=external diff died, stopping at path ''{0}'' due to exception: {1}
doesNotExist={0} does not exist
dontOverwriteLocalChanges=error: Your local changes to the following file would be overwritten by merge:
everythingUpToDate=Everything up-to-date
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTool.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTool.java
index 128881779b..2f74177452 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTool.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTool.java
@@ -21,8 +21,10 @@ import java.text.MessageFormat;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
-
+import org.eclipse.jgit.diff.ContentSource;
+import org.eclipse.jgit.diff.ContentSource.Pair;
import org.eclipse.jgit.diff.DiffEntry;
+import org.eclipse.jgit.diff.DiffEntry.Side;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.errors.AmbiguousObjectException;
@@ -30,8 +32,11 @@ import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.RevisionSyntaxException;
import org.eclipse.jgit.internal.diffmergetool.DiffTools;
import org.eclipse.jgit.internal.diffmergetool.ExternalDiffTool;
+import org.eclipse.jgit.internal.diffmergetool.FileElement;
+import org.eclipse.jgit.internal.diffmergetool.ToolException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.ObjectStream;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.TextProgressMonitor;
import org.eclipse.jgit.lib.internal.BooleanTriState;
@@ -40,8 +45,10 @@ import org.eclipse.jgit.pgm.opt.PathTreeFilterHandler;
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.FileTreeIterator;
+import org.eclipse.jgit.treewalk.WorkingTreeIterator;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.eclipse.jgit.util.StringUtils;
+import org.eclipse.jgit.util.FS.ExecutionResult;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
@@ -145,40 +152,54 @@ class DiffTool extends TextBuiltin {
private void compare(List<DiffEntry> files, boolean showPrompt,
String toolNamePrompt) throws IOException {
- for (int fileIndex = 0; fileIndex < files.size(); fileIndex++) {
- DiffEntry ent = files.get(fileIndex);
- String mergedFilePath = ent.getNewPath();
- if (mergedFilePath.equals(DiffEntry.DEV_NULL)) {
- mergedFilePath = ent.getOldPath();
- }
- // check if user wants to launch compare
- boolean launchCompare = true;
- if (showPrompt) {
- launchCompare = isLaunchCompare(fileIndex + 1, files.size(),
- mergedFilePath, toolNamePrompt);
- }
- if (launchCompare) {
- switch (ent.getChangeType()) {
- case MODIFY:
- outw.println("M\t" + ent.getNewPath() //$NON-NLS-1$
- + " (" + ent.getNewId().name() + ")" //$NON-NLS-1$ //$NON-NLS-2$
- + "\t" + ent.getOldPath() //$NON-NLS-1$
- + " (" + ent.getOldId().name() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
- int ret = diffTools.compare(ent.getNewPath(),
- ent.getOldPath(), ent.getNewId().name(),
- ent.getOldId().name(), toolName, prompt, gui,
- trustExitCode);
- if (ret != 0) {
+ ContentSource.Pair sourcePair = new ContentSource.Pair(source(oldTree),
+ source(newTree));
+ try {
+ for (int fileIndex = 0; fileIndex < files.size(); fileIndex++) {
+ DiffEntry ent = files.get(fileIndex);
+ String mergedFilePath = ent.getNewPath();
+ if (mergedFilePath.equals(DiffEntry.DEV_NULL)) {
+ mergedFilePath = ent.getOldPath();
+ }
+ FileElement local = new FileElement(ent.getOldPath(),
+ ent.getOldId().name(),
+ getObjectStream(sourcePair, Side.OLD, ent));
+ FileElement remote = new FileElement(ent.getNewPath(),
+ ent.getNewId().name(),
+ getObjectStream(sourcePair, Side.NEW, ent));
+ // check if user wants to launch compare
+ boolean launchCompare = true;
+ if (showPrompt) {
+ launchCompare = isLaunchCompare(fileIndex + 1, files.size(),
+ mergedFilePath, toolNamePrompt);
+ }
+ if (launchCompare) {
+ try {
+ // TODO: check how to return the exit-code of
+ // the
+ // tool
+ // to
+ // jgit / java runtime ?
+ // int rc =...
+ ExecutionResult result = diffTools.compare(db, local,
+ remote, mergedFilePath,
+ toolName, prompt, gui, trustExitCode);
+ outw.println(new String(result.getStdout().toByteArray()));
+ errw.println(
+ new String(result.getStderr().toByteArray()));
+ } catch (ToolException e) {
+ outw.println(e.getResultStdout());
+ outw.flush();
+ errw.println(e.getMessage());
throw die(MessageFormat.format(
- CLIText.get().diffToolDied, mergedFilePath));
+ CLIText.get().diffToolDied, mergedFilePath, e));
}
- break;
- default:
+ } else {
break;
}
- } else {
- break;
}
+ } finally {
+ sourcePair.close();
}
}
@@ -254,4 +275,23 @@ class DiffTool extends TextBuiltin {
return files;
}
+ private ObjectStream getObjectStream(Pair pair, Side side, DiffEntry ent) {
+ ObjectStream stream = null;
+ if (!pair.isWorkingTreeSource(side)) {
+ try {
+ stream = pair.open(side, ent).openStream();
+ } catch (Exception e) {
+ stream = null;
+ }
+ }
+ return stream;
+ }
+
+ private ContentSource source(AbstractTreeIterator iterator) {
+ if (iterator instanceof WorkingTreeIterator) {
+ return ContentSource.create((WorkingTreeIterator) iterator);
+ }
+ return ContentSource.create(db.newObjectReader());
+ }
+
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalDiffToolTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalDiffToolTest.java
index c9ebec7638..ebc67c81cb 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalDiffToolTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalDiffToolTest.java
@@ -10,13 +10,17 @@
package org.eclipse.jgit.internal.diffmergetool;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DIFFTOOL_SECTION;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DIFF_SECTION;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_CMD;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_GUITOOL;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PATH;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PROMPT;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_TOOL;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_TRUST_EXIT_CODE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import java.util.Collections;
import java.util.LinkedHashSet;
@@ -25,6 +29,7 @@ import java.util.Set;
import org.eclipse.jgit.lib.internal.BooleanTriState;
import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS.ExecutionResult;
import org.junit.Test;
/**
@@ -32,6 +37,54 @@ import org.junit.Test;
*/
public class ExternalDiffToolTest extends ExternalToolTestCase {
+ @Test(expected = ToolException.class)
+ public void testUserToolWithError() throws Exception {
+ String toolName = "customTool";
+
+ int errorReturnCode = 1;
+ String command = "exit " + errorReturnCode;
+
+ FileBasedConfig config = db.getConfig();
+ config.setString(CONFIG_DIFFTOOL_SECTION, toolName, CONFIG_KEY_CMD,
+ command);
+
+ DiffTools manager = new DiffTools(db);
+
+ BooleanTriState prompt = BooleanTriState.UNSET;
+ BooleanTriState gui = BooleanTriState.UNSET;
+ BooleanTriState trustExitCode = BooleanTriState.TRUE;
+
+ manager.compare(db, local, remote, merged.getPath(), toolName, prompt,
+ gui, trustExitCode);
+
+ fail("Expected exception to be thrown due to external tool exiting with error code: "
+ + errorReturnCode);
+ }
+
+ @Test(expected = ToolException.class)
+ public void testUserToolWithCommandNotFoundError() throws Exception {
+ String toolName = "customTool";
+
+ int errorReturnCode = 127; // command not found
+ String command = "exit " + errorReturnCode;
+
+ FileBasedConfig config = db.getConfig();
+ config.setString(CONFIG_DIFFTOOL_SECTION, toolName, CONFIG_KEY_CMD,
+ command);
+
+ DiffTools manager = new DiffTools(db);
+
+ BooleanTriState prompt = BooleanTriState.UNSET;
+ BooleanTriState gui = BooleanTriState.UNSET;
+ BooleanTriState trustExitCode = BooleanTriState.FALSE;
+
+ manager.compare(db, local, remote, merged.getPath(), toolName, prompt,
+ gui, trustExitCode);
+
+ fail("Expected exception to be thrown due to external tool exiting with error code: "
+ + errorReturnCode);
+ }
+
@Test
public void testToolNames() {
DiffTools manager = new DiffTools(db);
@@ -86,11 +139,11 @@ public class ExternalDiffToolTest extends ExternalToolTestCase {
config.setString(CONFIG_DIFFTOOL_SECTION, customToolname,
CONFIG_KEY_PATH, "/usr/bin/echo");
config.setString(CONFIG_DIFFTOOL_SECTION, customToolname,
- CONFIG_KEY_PROMPT, "--no-prompt");
+ CONFIG_KEY_PROMPT, String.valueOf(false));
config.setString(CONFIG_DIFFTOOL_SECTION, customToolname,
- CONFIG_KEY_GUITOOL, "--no-gui");
+ CONFIG_KEY_GUITOOL, String.valueOf(false));
config.setString(CONFIG_DIFFTOOL_SECTION, customToolname,
- CONFIG_KEY_TRUST_EXIT_CODE, "--no-trust-exit-code");
+ CONFIG_KEY_TRUST_EXIT_CODE, String.valueOf(false));
DiffTools manager = new DiffTools(db);
Set<String> actualToolNames = manager.getUserDefinedTools().keySet();
Set<String> expectedToolNames = new LinkedHashSet<>();
@@ -109,38 +162,50 @@ public class ExternalDiffToolTest extends ExternalToolTestCase {
}
@Test
- public void testCompare() {
- DiffTools manager = new DiffTools(db);
+ public void testCompare() throws ToolException {
+ String toolName = "customTool";
+
+ FileBasedConfig config = db.getConfig();
+ // the default diff tool is configured without a subsection
+ String subsection = null;
+ config.setString(CONFIG_DIFF_SECTION, subsection, CONFIG_KEY_TOOL,
+ toolName);
+
+ String command = getEchoCommand();
+
+ config.setString(CONFIG_DIFFTOOL_SECTION, toolName, CONFIG_KEY_CMD,
+ command);
- String newPath = "";
- String oldPath = "";
- String newId = "";
- String oldId = "";
- String toolName = "";
BooleanTriState prompt = BooleanTriState.UNSET;
BooleanTriState gui = BooleanTriState.UNSET;
BooleanTriState trustExitCode = BooleanTriState.UNSET;
+ DiffTools manager = new DiffTools(db);
+
int expectedCompareResult = 0;
- int compareResult = manager.compare(newPath, oldPath, newId, oldId,
- toolName, prompt, gui, trustExitCode);
+ ExecutionResult compareResult = manager.compare(db, local, remote,
+ merged.getPath(), toolName, prompt, gui, trustExitCode);
assertEquals("Incorrect compare result for external diff tool",
- expectedCompareResult, compareResult);
+ expectedCompareResult, compareResult.getRc());
}
@Test
public void testDefaultTool() throws Exception {
+ String toolName = "customTool";
+ String guiToolName = "customGuiTool";
+
FileBasedConfig config = db.getConfig();
// the default diff tool is configured without a subsection
String subsection = null;
- config.setString("diff", subsection, "tool", "customTool");
+ config.setString(CONFIG_DIFF_SECTION, subsection, CONFIG_KEY_TOOL,
+ toolName);
DiffTools manager = new DiffTools(db);
BooleanTriState gui = BooleanTriState.UNSET;
String defaultToolName = manager.getDefaultToolName(gui);
assertEquals(
"Expected configured difftool to be the default external diff tool",
- "my_default_toolname", defaultToolName);
+ toolName, defaultToolName);
gui = BooleanTriState.TRUE;
String defaultGuiToolName = manager.getDefaultToolName(gui);
@@ -148,11 +213,63 @@ public class ExternalDiffToolTest extends ExternalToolTestCase {
"Expected configured difftool to be the default external diff tool",
"my_gui_tool", defaultGuiToolName);
- config.setString("diff", subsection, "guitool", "customGuiTool");
+ config.setString(CONFIG_DIFF_SECTION, subsection, CONFIG_KEY_GUITOOL,
+ guiToolName);
manager = new DiffTools(db);
defaultGuiToolName = manager.getDefaultToolName(gui);
assertEquals(
"Expected configured difftool to be the default external diff guitool",
"my_gui_tool", defaultGuiToolName);
}
+
+ @Test
+ public void testOverridePreDefinedToolPath() {
+ String newToolPath = "/tmp/path/";
+
+ CommandLineDiffTool[] defaultTools = CommandLineDiffTool.values();
+ assertTrue("Expected to find pre-defined external diff tools",
+ defaultTools.length > 0);
+
+ CommandLineDiffTool overridenTool = defaultTools[0];
+ String overridenToolName = overridenTool.name();
+ String overridenToolPath = newToolPath + overridenToolName;
+ FileBasedConfig config = db.getConfig();
+ config.setString(CONFIG_DIFFTOOL_SECTION, overridenToolName,
+ CONFIG_KEY_PATH, overridenToolPath);
+
+ DiffTools manager = new DiffTools(db);
+ Map<String, ExternalDiffTool> availableTools = manager
+ .getAvailableTools();
+ ExternalDiffTool externalDiffTool = availableTools
+ .get(overridenToolName);
+ String actualDiffToolPath = externalDiffTool.getPath();
+ assertEquals(
+ "Expected pre-defined external diff tool to have overriden path",
+ overridenToolPath, actualDiffToolPath);
+ String expectedDiffToolCommand = overridenToolPath + " "
+ + overridenTool.getParameters();
+ String actualDiffToolCommand = externalDiffTool.getCommand();
+ assertEquals(
+ "Expected pre-defined external diff tool to have overriden command",
+ expectedDiffToolCommand, actualDiffToolCommand);
+ }
+
+ @Test(expected = ToolException.class)
+ public void testUndefinedTool() throws Exception {
+ DiffTools manager = new DiffTools(db);
+
+ String toolName = "undefined";
+ BooleanTriState prompt = BooleanTriState.UNSET;
+ BooleanTriState gui = BooleanTriState.UNSET;
+ BooleanTriState trustExitCode = BooleanTriState.UNSET;
+
+ manager.compare(db, local, remote, merged.getPath(), toolName, prompt,
+ gui, trustExitCode);
+ fail("Expected exception to be thrown due to not defined external diff tool");
+ }
+
+ private String getEchoCommand() {
+ return "(echo \"$LOCAL\" \"$REMOTE\") > "
+ + commandResult.getAbsolutePath();
+ }
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalToolTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalToolTestCase.java
index 0cc12978a8..6757eb4635 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalToolTestCase.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalToolTestCase.java
@@ -36,6 +36,14 @@ public abstract class ExternalToolTestCase extends RepositoryTestCase {
protected File commandResult;
+ protected FileElement local;
+
+ protected FileElement remote;
+
+ protected FileElement merged;
+
+ protected FileElement base;
+
@Before
@Override
public void setUp() throws Exception {
@@ -51,6 +59,11 @@ public abstract class ExternalToolTestCase extends RepositoryTestCase {
baseFile.deleteOnExit();
commandResult = writeTrashFile("commandResult.txt", "");
commandResult.deleteOnExit();
+
+ local = new FileElement(localFile.getAbsolutePath(), "LOCAL");
+ remote = new FileElement(remoteFile.getAbsolutePath(), "REMOTE");
+ merged = new FileElement(mergedFile.getAbsolutePath(), "MERGED");
+ base = new FileElement(baseFile.getAbsolutePath(), "BASE");
}
@After
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java
index 1a41df3d0a..64ff19c9c3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010, 2020 Google Inc. and others
+ * Copyright (C) 2010, 2021 Google Inc. and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -91,6 +91,29 @@ public abstract class ContentSource {
public abstract ObjectLoader open(String path, ObjectId id)
throws IOException;
+ /**
+ * Closes the used resources like ObjectReader, TreeWalk etc. Default
+ * implementation does nothing.
+ *
+ * @since 6.2
+ */
+ public void close() {
+ // Do nothing
+ }
+
+ /**
+ * Checks if the source is from "working tree", so it can be accessed as a
+ * file directly.
+ *
+ * @since 6.2
+ *
+ * @return true if working tree source and false otherwise (loader must be
+ * used)
+ */
+ public boolean isWorkingTreeSource() {
+ return false;
+ }
+
private static class ObjectReaderSource extends ContentSource {
private final ObjectReader reader;
@@ -111,6 +134,16 @@ public abstract class ContentSource {
public ObjectLoader open(String path, ObjectId id) throws IOException {
return reader.open(id, Constants.OBJ_BLOB);
}
+
+ @Override
+ public void close() {
+ reader.close();
+ }
+
+ @Override
+ public boolean isWorkingTreeSource() {
+ return false;
+ }
}
private static class WorkingTreeSource extends ContentSource {
@@ -194,6 +227,16 @@ public abstract class ContentSource {
throw new FileNotFoundException(path);
}
}
+
+ @Override
+ public void close() {
+ tw.close();
+ }
+
+ @Override
+ public boolean isWorkingTreeSource() {
+ return true;
+ }
}
/** A pair of sources to access the old and new sides of a DiffEntry. */
@@ -261,5 +304,37 @@ public abstract class ContentSource {
throw new IllegalArgumentException();
}
}
+
+ /**
+ * Closes used resources.
+ *
+ * @since 6.2
+ */
+ public void close() {
+ oldSource.close();
+ newSource.close();
+ }
+
+ /**
+ * Checks if source (side) is a "working tree".
+ *
+ * @since 6.2
+ *
+ * @param side
+ * which side of the entry to read (OLD or NEW).
+ * @return is the source a "working tree"
+ *
+ */
+ public boolean isWorkingTreeSource(DiffEntry.Side side) {
+ switch (side) {
+ case OLD:
+ return oldSource.isWorkingTreeSource();
+ case NEW:
+ return newSource.isWorkingTreeSource();
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
}
}
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
new file mode 100644
index 0000000000..0dde9b5f39
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandExecutor.java
@@ -0,0 +1,182 @@
+/*
+ * 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.util.Arrays;
+import java.util.Map;
+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;
+
+/**
+ * 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) && (checkExitCode
+ || isCommandExecutionError(rc))) {
+ throw new ToolException(
+ new String(result.getStderr().toByteArray()), result);
+ }
+ return result;
+ } finally {
+ deleteCommandArray();
+ }
+ }
+
+ 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 (useMsys2Str != null && !useMsys2Str.isEmpty()) {
+ 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();
+ 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;
+ }
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffToolConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffToolConfig.java
index 551f634f2d..c8b04f90f2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffToolConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffToolConfig.java
@@ -49,9 +49,10 @@ public class DiffToolConfig {
toolName = rc.getString(CONFIG_DIFF_SECTION, null, CONFIG_KEY_TOOL);
guiToolName = rc.getString(CONFIG_DIFF_SECTION, null,
CONFIG_KEY_GUITOOL);
- prompt = rc.getBoolean(CONFIG_DIFFTOOL_SECTION, CONFIG_KEY_PROMPT,
+ prompt = rc.getBoolean(CONFIG_DIFFTOOL_SECTION, toolName,
+ CONFIG_KEY_PROMPT,
true);
- String trustStr = rc.getString(CONFIG_DIFFTOOL_SECTION, null,
+ String trustStr = rc.getString(CONFIG_DIFFTOOL_SECTION, toolName,
CONFIG_KEY_TRUST_EXIT_CODE);
if (trustStr != null) {
trustExitCode = Boolean.parseBoolean(trustStr)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java
index 39729a4eec..b15cbdc34a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java
@@ -12,11 +12,16 @@ package org.eclipse.jgit.internal.diffmergetool;
import java.util.TreeMap;
import java.util.Collections;
+import java.io.File;
+import java.io.IOException;
import java.util.Map;
import java.util.Set;
+import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.internal.BooleanTriState;
+import org.eclipse.jgit.util.FS.ExecutionResult;
+import org.eclipse.jgit.util.StringUtils;
/**
* Manages diff tools.
@@ -25,9 +30,9 @@ public class DiffTools {
private final DiffToolConfig config;
- private Map<String, ExternalDiffTool> predefinedTools;
+ private final Map<String, ExternalDiffTool> predefinedTools;
- private Map<String, ExternalDiffTool> userDefinedTools;
+ private final Map<String, ExternalDiffTool> userDefinedTools;
/**
* Creates the external diff-tools manager for given repository.
@@ -37,21 +42,22 @@ public class DiffTools {
*/
public DiffTools(Repository repo) {
config = repo.getConfig().get(DiffToolConfig.KEY);
- setupPredefinedTools();
- setupUserDefinedTools();
+ predefinedTools = setupPredefinedTools();
+ userDefinedTools = setupUserDefinedTools(config, predefinedTools);
}
/**
* Compare two versions of a file.
*
- * @param newPath
- * the new file path
- * @param oldPath
- * the old file path
- * @param newId
- * the new object ID
- * @param oldId
- * the old object ID
+ * @param repo
+ * the repository
+ * @param localFile
+ * the local file element
+ * @param remoteFile
+ * the remote file element
+ * @param mergedFilePath
+ * the path of 'merged' file, it equals local or remote path for
+ * difftool
* @param toolName
* the selected tool name (can be null)
* @param prompt
@@ -61,11 +67,39 @@ public class DiffTools {
* @param trustExitCode
* the "trust exit code" option
* @return the return code from executed tool
+ * @throws ToolException
*/
- public int compare(String newPath, String oldPath, String newId,
- String oldId, String toolName, BooleanTriState prompt,
- BooleanTriState gui, BooleanTriState trustExitCode) {
- return 0;
+ public ExecutionResult compare(Repository repo, FileElement localFile,
+ FileElement remoteFile, String mergedFilePath, String toolName,
+ BooleanTriState prompt, BooleanTriState gui,
+ BooleanTriState trustExitCode) throws ToolException {
+ ExternalDiffTool tool = guessTool(toolName, gui);
+ try {
+ File workingDir = repo.getWorkTree();
+ String localFilePath = localFile.getFile().getPath();
+ String remoteFilePath = remoteFile.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$
+ 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$
+ boolean trust = config.isTrustExitCode();
+ if (trustExitCode != BooleanTriState.UNSET) {
+ trust = trustExitCode == BooleanTriState.TRUE;
+ }
+ CommandExecutor cmdExec = new CommandExecutor(repo.getFS(), trust);
+ return cmdExec.run(command, workingDir, env);
+ } catch (IOException | InterruptedException e) {
+ throw new ToolException(e);
+ } finally {
+ localFile.cleanTemporaries();
+ remoteFile.cleanTemporaries();
+ }
}
/**
@@ -103,41 +137,64 @@ public class DiffTools {
*/
public String getDefaultToolName(BooleanTriState gui) {
return gui != BooleanTriState.UNSET ? "my_gui_tool" //$NON-NLS-1$
- : "my_default_toolname"; //$NON-NLS-1$
+ : config.getDefaultToolName();
}
/**
* @return is interactive (config prompt enabled) ?
*/
public boolean isInteractive() {
- return false;
+ return config.isPrompt();
+ }
+
+ private ExternalDiffTool guessTool(String toolName, BooleanTriState gui)
+ throws ToolException {
+ if (StringUtils.isEmptyOrNull(toolName)) {
+ toolName = getDefaultToolName(gui);
+ }
+ ExternalDiffTool tool = getTool(toolName);
+ if (tool == null) {
+ throw new ToolException("Unknown diff tool " + toolName); //$NON-NLS-1$
+ }
+ return tool;
+ }
+
+ private ExternalDiffTool getTool(final String name) {
+ ExternalDiffTool tool = userDefinedTools.get(name);
+ if (tool == null) {
+ tool = predefinedTools.get(name);
+ }
+ return tool;
}
- private void setupPredefinedTools() {
- predefinedTools = new TreeMap<>();
+ private static Map<String, ExternalDiffTool> setupPredefinedTools() {
+ Map<String, ExternalDiffTool> tools = new TreeMap<>();
for (CommandLineDiffTool tool : CommandLineDiffTool.values()) {
- predefinedTools.put(tool.name(), new PreDefinedDiffTool(tool));
+ tools.put(tool.name(), new PreDefinedDiffTool(tool));
}
+ return tools;
}
- private void setupUserDefinedTools() {
- userDefinedTools = new TreeMap<>();
- Map<String, ExternalDiffTool> userTools = config.getTools();
+ private static Map<String, ExternalDiffTool> setupUserDefinedTools(
+ DiffToolConfig cfg, Map<String, ExternalDiffTool> predefTools) {
+ Map<String, ExternalDiffTool> tools = new TreeMap<>();
+ Map<String, ExternalDiffTool> userTools = cfg.getTools();
for (String name : userTools.keySet()) {
ExternalDiffTool userTool = userTools.get(name);
// if difftool.<name>.cmd is defined we have user defined tool
if (userTool.getCommand() != null) {
- userDefinedTools.put(name, userTool);
+ tools.put(name, userTool);
} else if (userTool.getPath() != null) {
// if difftool.<name>.path is defined we just overload the path
// of predefined tool
- PreDefinedDiffTool predefTool = (PreDefinedDiffTool) predefinedTools
+ PreDefinedDiffTool predefTool = (PreDefinedDiffTool) predefTools
.get(name);
if (predefTool != null) {
predefTool.setPath(userTool.getPath());
}
}
}
+ return tools;
}
}
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
new file mode 100644
index 0000000000..cdc8f015f6
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/FileElement.java
@@ -0,0 +1,160 @@
+/*
+ * 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 org.eclipse.jgit.diff.DiffEntry;
+import org.eclipse.jgit.lib.ObjectStream;
+
+/**
+ * The element used as left or right file for compare.
+ *
+ */
+public class FileElement {
+
+ private final String path;
+
+ private final String id;
+
+ private ObjectStream stream;
+
+ private File tempFile;
+
+ /**
+ * @param path
+ * the file path
+ * @param id
+ * the file id
+ */
+ public FileElement(final String path, final String id) {
+ this(path, id, null);
+ }
+
+ /**
+ * @param path
+ * the file path
+ * @param id
+ * the file id
+ * @param stream
+ * the object stream to load instead of file
+ */
+ public FileElement(final String path, final String id,
+ ObjectStream stream) {
+ this.path = path;
+ this.id = id;
+ this.stream = stream;
+ }
+
+ /**
+ * @return the file path
+ */
+ public String getPath() {
+ return path;
+ }
+
+ /**
+ * @return the file id
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * @param stream
+ * the object stream
+ */
+ public void setStream(ObjectStream stream) {
+ this.stream = stream;
+ }
+
+ /**
+ * @param workingDir the working directory used if file cannot be found (e.g. /dev/null)
+ * @return the object stream
+ * @throws IOException
+ */
+ public File getFile(File workingDir) 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;
+ }
+
+ /**
+ * Returns a real file from work tree or a temporary file with content if
+ * stream is valid or if path is "/dev/null"
+ *
+ * @return the object stream
+ * @throws IOException
+ */
+ public File getFile() throws IOException {
+ if (tempFile != null) {
+ return tempFile;
+ }
+ File file = new File(path);
+ String name = file.getName();
+ // if we have a stream or file is missing ("/dev/null") then create
+ // temporary file
+ if ((stream != null) || path.equals(DiffEntry.DEV_NULL)) {
+ // 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;
+ }
+ }
+ return tempFile;
+ }
+ return file;
+ }
+
+ /**
+ * Deletes and invalidates temporary file if necessary.
+ */
+ public void cleanTemporaries() {
+ if (tempFile != null && tempFile.exists())
+ tempFile.delete();
+ tempFile = null;
+ }
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedDiffTool.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedDiffTool.java
index 1c69fb4911..092cb605be 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedDiffTool.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedDiffTool.java
@@ -46,17 +46,6 @@ public class PreDefinedDiffTool extends UserDefinedDiffTool {
*/
@Override
public void setPath(String path) {
- // handling of spaces in path
- if (path.contains(" ")) { //$NON-NLS-1$
- // add quotes before if needed
- if (!path.startsWith("\"")) { //$NON-NLS-1$
- path = "\"" + path; //$NON-NLS-1$
- }
- // add quotes after if needed
- if (!path.endsWith("\"")) { //$NON-NLS-1$
- path = path + "\""; //$NON-NLS-1$
- }
- }
super.setPath(path);
}
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
new file mode 100644
index 0000000000..7862cf5967
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ToolException.java
@@ -0,0 +1,120 @@
+/*
+ * 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 org.eclipse.jgit.util.FS.ExecutionResult;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Tool exception for differentiation.
+ *
+ */
+public class ToolException extends Exception {
+
+ private final static Logger LOG = LoggerFactory
+ .getLogger(ToolException.class);
+
+ private final ExecutionResult result;
+
+ /**
+ * the serial version UID
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ *
+ */
+ public ToolException() {
+ super();
+ result = null;
+ }
+
+ /**
+ * @param message
+ * the exception message
+ */
+ public ToolException(String message) {
+ super(message);
+ result = null;
+ }
+
+ /**
+ * @param message
+ * the exception message
+ * @param result
+ * the execution result
+ */
+ public ToolException(String message, ExecutionResult result) {
+ super(message);
+ this.result = result;
+ }
+
+ /**
+ * @param message
+ * the exception message
+ * @param cause
+ * the cause for throw
+ */
+ public ToolException(String message, Throwable cause) {
+ super(message, cause);
+ result = null;
+ }
+
+ /**
+ * @param cause
+ * the cause for throw
+ */
+ public ToolException(Throwable cause) {
+ super(cause);
+ result = null;
+ }
+
+ /**
+ * @return true if result is valid, false else
+ */
+ public boolean isResult() {
+ return result != null;
+ }
+
+ /**
+ * @return the execution result
+ */
+ public ExecutionResult getResult() {
+ return result;
+ }
+
+ /**
+ * @return the result Stderr
+ */
+ public String getResultStderr() {
+ try {
+ return new String(result.getStderr().toByteArray());
+ } catch (Exception e) {
+ LOG.warn(e.getMessage());
+ }
+ return ""; //$NON-NLS-1$
+ }
+
+ /**
+ * @return the result Stdout
+ */
+ public String getResultStdout() {
+ try {
+ return new String(result.getStdout().toByteArray());
+ } catch (Exception e) {
+ LOG.warn(e.getMessage());
+ }
+ return ""; //$NON-NLS-1$
+ }
+
+}