Merge branch 'master' into stable-6.2
* master: Adapt diff- and merge tool code for PGM and EGit usage Teach JGit to handle external diff/merge tools defined in .gitattributes Change-Id: I3aefc14160caaac859bd3548460dd755ebe42fc5
This commit is contained in:
commit
2ef2a3562e
|
@ -16,12 +16,17 @@
|
||||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_TOOL;
|
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_TOOL;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.eclipse.jgit.internal.diffmergetool.CommandLineDiffTool;
|
import org.eclipse.jgit.internal.diffmergetool.DiffTools;
|
||||||
|
import org.eclipse.jgit.internal.diffmergetool.ExternalDiffTool;
|
||||||
import org.eclipse.jgit.lib.StoredConfig;
|
import org.eclipse.jgit.lib.StoredConfig;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
@ -40,6 +45,59 @@ public void setUp() throws Exception {
|
||||||
configureEchoTool(TOOL_NAME);
|
configureEchoTool(TOOL_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(expected = Die.class)
|
||||||
|
public void testUndefinedTool() throws Exception {
|
||||||
|
String toolName = "undefined";
|
||||||
|
String[] conflictingFilenames = createUnstagedChanges();
|
||||||
|
|
||||||
|
List<String> expectedErrors = new ArrayList<>();
|
||||||
|
for (String changedFilename : conflictingFilenames) {
|
||||||
|
expectedErrors.add("External diff tool is not defined: " + toolName);
|
||||||
|
expectedErrors.add("compare of " + changedFilename + " failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
runAndCaptureUsingInitRaw(expectedErrors, DIFF_TOOL, "--no-prompt",
|
||||||
|
"--tool", toolName);
|
||||||
|
fail("Expected exception to be thrown due to undefined external tool");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = Die.class)
|
||||||
|
public void testUserToolWithCommandNotFoundError() throws Exception {
|
||||||
|
String toolName = "customTool";
|
||||||
|
|
||||||
|
int errorReturnCode = 127; // command not found
|
||||||
|
String command = "exit " + errorReturnCode;
|
||||||
|
|
||||||
|
StoredConfig config = db.getConfig();
|
||||||
|
config.setString(CONFIG_DIFFTOOL_SECTION, toolName, CONFIG_KEY_CMD,
|
||||||
|
command);
|
||||||
|
|
||||||
|
createMergeConflict();
|
||||||
|
runAndCaptureUsingInitRaw(DIFF_TOOL, "--no-prompt", "--tool", toolName);
|
||||||
|
|
||||||
|
fail("Expected exception to be thrown due to external tool exiting with error code: "
|
||||||
|
+ errorReturnCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = Die.class)
|
||||||
|
public void testEmptyToolName() throws Exception {
|
||||||
|
String emptyToolName = "";
|
||||||
|
|
||||||
|
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,
|
||||||
|
emptyToolName);
|
||||||
|
|
||||||
|
createUnstagedChanges();
|
||||||
|
|
||||||
|
String araxisErrorLine = "compare: unrecognized option `-wait' @ error/compare.c/CompareImageCommand/1123.";
|
||||||
|
String[] expectedErrorOutput = { araxisErrorLine, araxisErrorLine, };
|
||||||
|
runAndCaptureUsingInitRaw(Arrays.asList(expectedErrorOutput), DIFF_TOOL,
|
||||||
|
"--no-prompt");
|
||||||
|
fail("Expected exception to be thrown due to external tool exiting with an error");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testToolWithPrompt() throws Exception {
|
public void testToolWithPrompt() throws Exception {
|
||||||
String[] inputLines = {
|
String[] inputLines = {
|
||||||
|
@ -136,12 +194,12 @@ expectedOutput, runAndCaptureUsingInitRaw(DIFF_TOOL,
|
||||||
@Test
|
@Test
|
||||||
public void testToolCached() throws Exception {
|
public void testToolCached() throws Exception {
|
||||||
String[] conflictingFilenames = createStagedChanges();
|
String[] conflictingFilenames = createStagedChanges();
|
||||||
String[] expectedOutput = getExpectedToolOutputNoPrompt(conflictingFilenames);
|
Pattern[] expectedOutput = getExpectedCachedToolOutputNoPrompt(conflictingFilenames);
|
||||||
|
|
||||||
String[] options = { "--cached", "--staged", };
|
String[] options = { "--cached", "--staged", };
|
||||||
|
|
||||||
for (String option : options) {
|
for (String option : options) {
|
||||||
assertArrayOfLinesEquals("Incorrect output for option: " + option,
|
assertArrayOfMatchingLines("Incorrect output for option: " + option,
|
||||||
expectedOutput, runAndCaptureUsingInitRaw(DIFF_TOOL,
|
expectedOutput, runAndCaptureUsingInitRaw(DIFF_TOOL,
|
||||||
option, "--tool", TOOL_NAME));
|
option, "--tool", TOOL_NAME));
|
||||||
}
|
}
|
||||||
|
@ -149,20 +207,38 @@ expectedOutput, runAndCaptureUsingInitRaw(DIFF_TOOL,
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testToolHelp() throws Exception {
|
public void testToolHelp() throws Exception {
|
||||||
CommandLineDiffTool[] defaultTools = CommandLineDiffTool.values();
|
|
||||||
List<String> expectedOutput = new ArrayList<>();
|
List<String> expectedOutput = new ArrayList<>();
|
||||||
|
|
||||||
|
DiffTools diffTools = new DiffTools(db);
|
||||||
|
Map<String, ExternalDiffTool> predefinedTools = diffTools
|
||||||
|
.getPredefinedTools(true);
|
||||||
|
List<ExternalDiffTool> availableTools = new ArrayList<>();
|
||||||
|
List<ExternalDiffTool> notAvailableTools = new ArrayList<>();
|
||||||
|
for (ExternalDiffTool tool : predefinedTools.values()) {
|
||||||
|
if (tool.isAvailable()) {
|
||||||
|
availableTools.add(tool);
|
||||||
|
} else {
|
||||||
|
notAvailableTools.add(tool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
expectedOutput.add(
|
expectedOutput.add(
|
||||||
"'git difftool --tool=<tool>' may be set to one of the following:");
|
"'git difftool --tool=<tool>' may be set to one of the following:");
|
||||||
for (CommandLineDiffTool defaultTool : defaultTools) {
|
for (ExternalDiffTool tool : availableTools) {
|
||||||
String toolName = defaultTool.name();
|
String toolName = tool.getName();
|
||||||
expectedOutput.add(toolName);
|
expectedOutput.add(toolName);
|
||||||
}
|
}
|
||||||
String customToolHelpLine = TOOL_NAME + "." + CONFIG_KEY_CMD + " "
|
String customToolHelpLine = TOOL_NAME + "." + CONFIG_KEY_CMD + " "
|
||||||
+ getEchoCommand();
|
+ getEchoCommand();
|
||||||
expectedOutput.add("user-defined:");
|
expectedOutput.add("user-defined:");
|
||||||
expectedOutput.add(customToolHelpLine);
|
expectedOutput.add(customToolHelpLine);
|
||||||
|
expectedOutput.add(
|
||||||
|
"The following tools are valid, but not currently available:");
|
||||||
|
for (ExternalDiffTool tool : notAvailableTools) {
|
||||||
|
String toolName = tool.getName();
|
||||||
|
expectedOutput.add(toolName);
|
||||||
|
}
|
||||||
String[] userDefinedToolsHelp = {
|
String[] userDefinedToolsHelp = {
|
||||||
"The following tools are valid, but not currently available:",
|
|
||||||
"Some of the tools listed above only work in a windowed",
|
"Some of the tools listed above only work in a windowed",
|
||||||
"environment. If run in a terminal-only session, they will fail.",
|
"environment. If run in a terminal-only session, they will fail.",
|
||||||
};
|
};
|
||||||
|
@ -193,43 +269,76 @@ private void configureEchoTool(String toolName) {
|
||||||
String.valueOf(false));
|
String.valueOf(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String[] getExpectedToolOutputNoPrompt(String[] conflictingFilenames) {
|
private String[] getExpectedToolOutputNoPrompt(String[] conflictingFilenames) {
|
||||||
String[] expectedToolOutput = new String[conflictingFilenames.length];
|
String[] expectedToolOutput = new String[conflictingFilenames.length];
|
||||||
for (int i = 0; i < conflictingFilenames.length; ++i) {
|
for (int i = 0; i < conflictingFilenames.length; ++i) {
|
||||||
String newPath = conflictingFilenames[i];
|
String newPath = conflictingFilenames[i];
|
||||||
String expectedLine = newPath;
|
Path fullPath = getFullPath(newPath);
|
||||||
expectedToolOutput[i] = expectedLine;
|
expectedToolOutput[i] = fullPath.toString();
|
||||||
}
|
}
|
||||||
return expectedToolOutput;
|
return expectedToolOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String[] getExpectedCompareOutput(String[] conflictingFilenames) {
|
private Pattern[] getExpectedCachedToolOutputNoPrompt(String[] conflictingFilenames) {
|
||||||
|
String tmpDir = System.getProperty("java.io.tmpdir");
|
||||||
|
if (tmpDir.endsWith(File.separator)) {
|
||||||
|
tmpDir = tmpDir.substring(0, tmpDir.length() - 1);
|
||||||
|
}
|
||||||
|
Pattern emptyPattern = Pattern.compile("");
|
||||||
|
List<Pattern> expectedToolOutput = new ArrayList<>();
|
||||||
|
for (int i = 0; i < conflictingFilenames.length; ++i) {
|
||||||
|
String changedFilename = conflictingFilenames[i];
|
||||||
|
Path fullPath = getFullPath(changedFilename);
|
||||||
|
String filename = fullPath.getFileName().toString();
|
||||||
|
String regexp = tmpDir + File.separatorChar + filename
|
||||||
|
+ "_REMOTE_.*";
|
||||||
|
Pattern pattern = Pattern.compile(regexp);
|
||||||
|
expectedToolOutput.add(pattern);
|
||||||
|
expectedToolOutput.add(emptyPattern);
|
||||||
|
}
|
||||||
|
expectedToolOutput.add(emptyPattern);
|
||||||
|
return expectedToolOutput.toArray(new Pattern[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String[] getExpectedCompareOutput(String[] conflictingFilenames) {
|
||||||
List<String> expected = new ArrayList<>();
|
List<String> expected = new ArrayList<>();
|
||||||
int n = conflictingFilenames.length;
|
int n = conflictingFilenames.length;
|
||||||
for (int i = 0; i < n; ++i) {
|
for (int i = 0; i < n; ++i) {
|
||||||
String newPath = conflictingFilenames[i];
|
String changedFilename = conflictingFilenames[i];
|
||||||
expected.add(
|
expected.add(
|
||||||
"Viewing (" + (i + 1) + "/" + n + "): '" + newPath + "'");
|
"Viewing (" + (i + 1) + "/" + n + "): '" + changedFilename
|
||||||
|
+ "'");
|
||||||
expected.add("Launch '" + TOOL_NAME + "' [Y/n]?");
|
expected.add("Launch '" + TOOL_NAME + "' [Y/n]?");
|
||||||
expected.add(newPath);
|
Path fullPath = getFullPath(changedFilename);
|
||||||
|
expected.add(fullPath.toString());
|
||||||
}
|
}
|
||||||
return expected.toArray(new String[0]);
|
return expected.toArray(new String[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String[] getExpectedAbortOutput(String[] conflictingFilenames,
|
private String[] getExpectedAbortOutput(String[] conflictingFilenames,
|
||||||
int abortIndex) {
|
int abortIndex) {
|
||||||
List<String> expected = new ArrayList<>();
|
List<String> expected = new ArrayList<>();
|
||||||
int n = conflictingFilenames.length;
|
int n = conflictingFilenames.length;
|
||||||
for (int i = 0; i < n; ++i) {
|
for (int i = 0; i < n; ++i) {
|
||||||
String newPath = conflictingFilenames[i];
|
String changedFilename = conflictingFilenames[i];
|
||||||
expected.add(
|
expected.add(
|
||||||
"Viewing (" + (i + 1) + "/" + n + "): '" + newPath + "'");
|
"Viewing (" + (i + 1) + "/" + n + "): '" + changedFilename
|
||||||
|
+ "'");
|
||||||
expected.add("Launch '" + TOOL_NAME + "' [Y/n]?");
|
expected.add("Launch '" + TOOL_NAME + "' [Y/n]?");
|
||||||
if (i == abortIndex) {
|
if (i == abortIndex) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
expected.add(newPath);
|
Path fullPath = getFullPath(changedFilename);
|
||||||
|
expected.add(fullPath.toString());
|
||||||
}
|
}
|
||||||
return expected.toArray(new String[0]);
|
return expected.toArray(new String[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String getEchoCommand() {
|
||||||
|
/*
|
||||||
|
* use 'REMOTE' placeholder, as it will be replaced by a file path
|
||||||
|
* within the repository.
|
||||||
|
*/
|
||||||
|
return "(echo \"$REMOTE\")";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,13 +14,17 @@
|
||||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_TOOL;
|
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_TOOL;
|
||||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_MERGETOOL_SECTION;
|
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_MERGETOOL_SECTION;
|
||||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_MERGE_SECTION;
|
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_MERGE_SECTION;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.eclipse.jgit.internal.diffmergetool.CommandLineMergeTool;
|
import org.eclipse.jgit.internal.diffmergetool.ExternalMergeTool;
|
||||||
|
import org.eclipse.jgit.internal.diffmergetool.MergeTools;
|
||||||
import org.eclipse.jgit.lib.StoredConfig;
|
import org.eclipse.jgit.lib.StoredConfig;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
@ -39,6 +43,58 @@ public void setUp() throws Exception {
|
||||||
configureEchoTool(TOOL_NAME);
|
configureEchoTool(TOOL_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUndefinedTool() throws Exception {
|
||||||
|
String toolName = "undefined";
|
||||||
|
String[] conflictingFilenames = createMergeConflict();
|
||||||
|
|
||||||
|
List<String> expectedErrors = new ArrayList<>();
|
||||||
|
for (String conflictingFilename : conflictingFilenames) {
|
||||||
|
expectedErrors.add("External merge tool is not defined: " + toolName);
|
||||||
|
expectedErrors.add("merge of " + conflictingFilename + " failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
runAndCaptureUsingInitRaw(expectedErrors, MERGE_TOOL,
|
||||||
|
"--no-prompt", "--tool", toolName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = Die.class)
|
||||||
|
public void testUserToolWithCommandNotFoundError() throws Exception {
|
||||||
|
String toolName = "customTool";
|
||||||
|
|
||||||
|
int errorReturnCode = 127; // command not found
|
||||||
|
String command = "exit " + errorReturnCode;
|
||||||
|
|
||||||
|
StoredConfig config = db.getConfig();
|
||||||
|
config.setString(CONFIG_MERGETOOL_SECTION, toolName, CONFIG_KEY_CMD,
|
||||||
|
command);
|
||||||
|
|
||||||
|
createMergeConflict();
|
||||||
|
runAndCaptureUsingInitRaw(MERGE_TOOL, "--no-prompt", "--tool",
|
||||||
|
toolName);
|
||||||
|
|
||||||
|
fail("Expected exception to be thrown due to external tool exiting with error code: "
|
||||||
|
+ errorReturnCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEmptyToolName() throws Exception {
|
||||||
|
String emptyToolName = "";
|
||||||
|
|
||||||
|
StoredConfig config = db.getConfig();
|
||||||
|
// the default merge tool is configured without a subsection
|
||||||
|
String subsection = null;
|
||||||
|
config.setString(CONFIG_MERGE_SECTION, subsection, CONFIG_KEY_TOOL,
|
||||||
|
emptyToolName);
|
||||||
|
|
||||||
|
createMergeConflict();
|
||||||
|
|
||||||
|
String araxisErrorLine = "compare: unrecognized option `-wait' @ error/compare.c/CompareImageCommand/1123.";
|
||||||
|
String[] expectedErrorOutput = { araxisErrorLine, araxisErrorLine, };
|
||||||
|
runAndCaptureUsingInitRaw(Arrays.asList(expectedErrorOutput),
|
||||||
|
MERGE_TOOL, "--no-prompt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAbortMerge() throws Exception {
|
public void testAbortMerge() throws Exception {
|
||||||
String[] inputLines = {
|
String[] inputLines = {
|
||||||
|
@ -157,20 +213,38 @@ expectedOutput, runAndCaptureUsingInitRaw(MERGE_TOOL,
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testToolHelp() throws Exception {
|
public void testToolHelp() throws Exception {
|
||||||
CommandLineMergeTool[] defaultTools = CommandLineMergeTool.values();
|
|
||||||
List<String> expectedOutput = new ArrayList<>();
|
List<String> expectedOutput = new ArrayList<>();
|
||||||
|
|
||||||
|
MergeTools diffTools = new MergeTools(db);
|
||||||
|
Map<String, ExternalMergeTool> predefinedTools = diffTools
|
||||||
|
.getPredefinedTools(true);
|
||||||
|
List<ExternalMergeTool> availableTools = new ArrayList<>();
|
||||||
|
List<ExternalMergeTool> notAvailableTools = new ArrayList<>();
|
||||||
|
for (ExternalMergeTool tool : predefinedTools.values()) {
|
||||||
|
if (tool.isAvailable()) {
|
||||||
|
availableTools.add(tool);
|
||||||
|
} else {
|
||||||
|
notAvailableTools.add(tool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
expectedOutput.add(
|
expectedOutput.add(
|
||||||
"'git mergetool --tool=<tool>' may be set to one of the following:");
|
"'git mergetool --tool=<tool>' may be set to one of the following:");
|
||||||
for (CommandLineMergeTool defaultTool : defaultTools) {
|
for (ExternalMergeTool tool : availableTools) {
|
||||||
String toolName = defaultTool.name();
|
String toolName = tool.getName();
|
||||||
expectedOutput.add(toolName);
|
expectedOutput.add(toolName);
|
||||||
}
|
}
|
||||||
String customToolHelpLine = TOOL_NAME + "." + CONFIG_KEY_CMD + " "
|
String customToolHelpLine = TOOL_NAME + "." + CONFIG_KEY_CMD + " "
|
||||||
+ getEchoCommand();
|
+ getEchoCommand();
|
||||||
expectedOutput.add("user-defined:");
|
expectedOutput.add("user-defined:");
|
||||||
expectedOutput.add(customToolHelpLine);
|
expectedOutput.add(customToolHelpLine);
|
||||||
|
expectedOutput.add(
|
||||||
|
"The following tools are valid, but not currently available:");
|
||||||
|
for (ExternalMergeTool tool : notAvailableTools) {
|
||||||
|
String toolName = tool.getName();
|
||||||
|
expectedOutput.add(toolName);
|
||||||
|
}
|
||||||
String[] userDefinedToolsHelp = {
|
String[] userDefinedToolsHelp = {
|
||||||
"The following tools are valid, but not currently available:",
|
|
||||||
"Some of the tools listed above only work in a windowed",
|
"Some of the tools listed above only work in a windowed",
|
||||||
"environment. If run in a terminal-only session, they will fail.", };
|
"environment. If run in a terminal-only session, they will fail.", };
|
||||||
expectedOutput.addAll(Arrays.asList(userDefinedToolsHelp));
|
expectedOutput.addAll(Arrays.asList(userDefinedToolsHelp));
|
||||||
|
@ -200,7 +274,7 @@ private void configureEchoTool(String toolName) {
|
||||||
String.valueOf(false));
|
String.valueOf(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String[] getExpectedMergeConflictOutputNoPrompt(
|
private String[] getExpectedMergeConflictOutputNoPrompt(
|
||||||
String[] conflictFilenames) {
|
String[] conflictFilenames) {
|
||||||
List<String> expected = new ArrayList<>();
|
List<String> expected = new ArrayList<>();
|
||||||
expected.add("Merging:");
|
expected.add("Merging:");
|
||||||
|
@ -212,7 +286,8 @@ private static String[] getExpectedMergeConflictOutputNoPrompt(
|
||||||
+ "':");
|
+ "':");
|
||||||
expected.add("{local}: modified file");
|
expected.add("{local}: modified file");
|
||||||
expected.add("{remote}: modified file");
|
expected.add("{remote}: modified file");
|
||||||
expected.add(conflictFilename);
|
Path filePath = getFullPath(conflictFilename);
|
||||||
|
expected.add(filePath.toString());
|
||||||
expected.add(conflictFilename + " seems unchanged.");
|
expected.add(conflictFilename + " seems unchanged.");
|
||||||
}
|
}
|
||||||
return expected.toArray(new String[0]);
|
return expected.toArray(new String[0]);
|
||||||
|
@ -237,7 +312,7 @@ private static String[] getExpectedAbortLaunchOutput(
|
||||||
return expected.toArray(new String[0]);
|
return expected.toArray(new String[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String[] getExpectedAbortMergeOutput(
|
private String[] getExpectedAbortMergeOutput(
|
||||||
String[] conflictFilenames, int abortIndex) {
|
String[] conflictFilenames, int abortIndex) {
|
||||||
List<String> expected = new ArrayList<>();
|
List<String> expected = new ArrayList<>();
|
||||||
expected.add("Merging:");
|
expected.add("Merging:");
|
||||||
|
@ -254,8 +329,9 @@ private static String[] getExpectedAbortMergeOutput(
|
||||||
"Normal merge conflict for '" + conflictFilename + "':");
|
"Normal merge conflict for '" + conflictFilename + "':");
|
||||||
expected.add("{local}: modified file");
|
expected.add("{local}: modified file");
|
||||||
expected.add("{remote}: modified file");
|
expected.add("{remote}: modified file");
|
||||||
|
Path fullPath = getFullPath(conflictFilename);
|
||||||
expected.add("Hit return to start merge resolution tool ("
|
expected.add("Hit return to start merge resolution tool ("
|
||||||
+ TOOL_NAME + "): " + conflictFilename);
|
+ TOOL_NAME + "): " + fullPath);
|
||||||
expected.add(conflictFilename + " seems unchanged.");
|
expected.add(conflictFilename + " seems unchanged.");
|
||||||
expected.add("Was the merge successful [y/n]?");
|
expected.add("Was the merge successful [y/n]?");
|
||||||
if (i < conflictFilenames.length - 1) {
|
if (i < conflictFilenames.length - 1) {
|
||||||
|
@ -266,7 +342,7 @@ private static String[] getExpectedAbortMergeOutput(
|
||||||
return expected.toArray(new String[0]);
|
return expected.toArray(new String[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String[] getExpectedMergeConflictOutput(
|
private String[] getExpectedMergeConflictOutput(
|
||||||
String[] conflictFilenames) {
|
String[] conflictFilenames) {
|
||||||
List<String> expected = new ArrayList<>();
|
List<String> expected = new ArrayList<>();
|
||||||
expected.add("Merging:");
|
expected.add("Merging:");
|
||||||
|
@ -279,8 +355,9 @@ private static String[] getExpectedMergeConflictOutput(
|
||||||
+ "':");
|
+ "':");
|
||||||
expected.add("{local}: modified file");
|
expected.add("{local}: modified file");
|
||||||
expected.add("{remote}: modified file");
|
expected.add("{remote}: modified file");
|
||||||
|
Path filePath = getFullPath(conflictFilename);
|
||||||
expected.add("Hit return to start merge resolution tool ("
|
expected.add("Hit return to start merge resolution tool ("
|
||||||
+ TOOL_NAME + "): " + conflictFilename);
|
+ TOOL_NAME + "): " + filePath);
|
||||||
expected.add(conflictFilename + " seems unchanged.");
|
expected.add(conflictFilename + " seems unchanged.");
|
||||||
expected.add("Was the merge successful [y/n]?");
|
expected.add("Was the merge successful [y/n]?");
|
||||||
if (i < conflictFilenames.length - 1) {
|
if (i < conflictFilenames.length - 1) {
|
||||||
|
@ -307,4 +384,12 @@ private static String[] getExpectedDeletedConflictOutput(
|
||||||
}
|
}
|
||||||
return expected.toArray(new String[0]);
|
return expected.toArray(new String[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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\")";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,13 +10,18 @@
|
||||||
package org.eclipse.jgit.pgm;
|
package org.eclipse.jgit.pgm;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.eclipse.jgit.api.Git;
|
import org.eclipse.jgit.api.Git;
|
||||||
|
@ -29,6 +34,7 @@
|
||||||
import org.eclipse.jgit.treewalk.TreeWalk;
|
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.kohsuke.args4j.Argument;
|
import org.kohsuke.args4j.Argument;
|
||||||
|
import org.kohsuke.args4j.CmdLineException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base test case for the {@code difftool} and {@code mergetool} commands.
|
* Base test case for the {@code difftool} and {@code mergetool} commands.
|
||||||
|
@ -64,8 +70,23 @@ protected String[] runAndCaptureUsingInitRaw(String... args)
|
||||||
return runAndCaptureUsingInitRaw(inputStream, args);
|
return runAndCaptureUsingInitRaw(inputStream, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected String[] runAndCaptureUsingInitRaw(
|
||||||
|
List<String> expectedErrorOutput, String... args) throws Exception {
|
||||||
|
InputStream inputStream = null; // no input stream
|
||||||
|
return runAndCaptureUsingInitRaw(inputStream, expectedErrorOutput,
|
||||||
|
args);
|
||||||
|
}
|
||||||
|
|
||||||
protected String[] runAndCaptureUsingInitRaw(InputStream inputStream,
|
protected String[] runAndCaptureUsingInitRaw(InputStream inputStream,
|
||||||
String... args) throws Exception {
|
String... args) throws Exception {
|
||||||
|
List<String> expectedErrorOutput = Collections.emptyList();
|
||||||
|
return runAndCaptureUsingInitRaw(inputStream, expectedErrorOutput,
|
||||||
|
args);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String[] runAndCaptureUsingInitRaw(InputStream inputStream,
|
||||||
|
List<String> expectedErrorOutput, String... args)
|
||||||
|
throws CmdLineException, Exception, IOException {
|
||||||
CLIGitCommand.Result result = new CLIGitCommand.Result();
|
CLIGitCommand.Result result = new CLIGitCommand.Result();
|
||||||
|
|
||||||
GitCliJGitWrapperParser bean = new GitCliJGitWrapperParser();
|
GitCliJGitWrapperParser bean = new GitCliJGitWrapperParser();
|
||||||
|
@ -86,7 +107,7 @@ protected String[] runAndCaptureUsingInitRaw(InputStream inputStream,
|
||||||
.filter(l -> !l.isBlank()) // we care only about error messages
|
.filter(l -> !l.isBlank()) // we care only about error messages
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
assertEquals("Expected no standard error output from tool",
|
assertEquals("Expected no standard error output from tool",
|
||||||
Collections.EMPTY_LIST.toString(), errLines.toString());
|
expectedErrorOutput.toString(), errLines.toString());
|
||||||
|
|
||||||
return result.outLines().toArray(new String[0]);
|
return result.outLines().toArray(new String[0]);
|
||||||
}
|
}
|
||||||
|
@ -177,6 +198,13 @@ protected List<DiffEntry> getRepositoryChanges(RevCommit commit)
|
||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Path getFullPath(String repositoryFilename) {
|
||||||
|
Path dotGitPath = db.getDirectory().toPath();
|
||||||
|
Path repositoryRoot = dotGitPath.getParent();
|
||||||
|
Path repositoryFilePath = repositoryRoot.resolve(repositoryFilename);
|
||||||
|
return repositoryFilePath;
|
||||||
|
}
|
||||||
|
|
||||||
protected static InputStream createInputStream(String[] inputLines) {
|
protected static InputStream createInputStream(String[] inputLines) {
|
||||||
return createInputStream(Arrays.asList(inputLines));
|
return createInputStream(Arrays.asList(inputLines));
|
||||||
}
|
}
|
||||||
|
@ -192,11 +220,24 @@ protected static void assertArrayOfLinesEquals(String failMessage,
|
||||||
assertEquals(failMessage, toString(expected), toString(actual));
|
assertEquals(failMessage, toString(expected), toString(actual));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static String getEchoCommand() {
|
protected static void assertArrayOfMatchingLines(String failMessage,
|
||||||
/*
|
Pattern[] expected, String[] actual) {
|
||||||
* use 'MERGED' placeholder, as both 'LOCAL' and 'REMOTE' will be
|
assertEquals(failMessage + System.lineSeparator()
|
||||||
* replaced with full paths to a temporary file during some of the tests
|
+ "Expected and actual lines count don't match. Expected: "
|
||||||
*/
|
+ Arrays.asList(expected) + ", actual: "
|
||||||
return "(echo \"$MERGED\")";
|
+ Arrays.asList(actual), expected.length, actual.length);
|
||||||
|
int n = expected.length;
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
Pattern expectedPattern = expected[i];
|
||||||
|
String actualLine = actual[i];
|
||||||
|
Matcher matcher = expectedPattern.matcher(actualLine);
|
||||||
|
boolean matches = matcher.matches();
|
||||||
|
assertTrue(failMessage + System.lineSeparator() + "Line " + i + " '"
|
||||||
|
+ actualLine + "' doesn't match expected pattern: "
|
||||||
|
+ expectedPattern + System.lineSeparator() + "Expected: "
|
||||||
|
+ Arrays.asList(expected) + ", actual: "
|
||||||
|
+ Arrays.asList(actual),
|
||||||
|
matches);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,8 @@ 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.
|
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]?
|
diffToolLaunch=Viewing ({0}/{1}): ''{2}''\nLaunch ''{3}'' [Y/n]?
|
||||||
diffToolDied=external diff died, stopping at path ''{0}'' due to exception: {1}
|
diffToolDied=external diff died, stopping at path ''{0}'' due to exception: {1}
|
||||||
|
diffToolPromptToolName=This message is displayed because 'diff.tool' is not configured.\nSee 'git difftool --tool-help' or 'git help config' for more details.\n'git difftool' will now attempt to use one of the following tools:\n{0}\n
|
||||||
|
diffToolUnknownToolName=Unknown diff tool '{0}'
|
||||||
doesNotExist={0} does not exist
|
doesNotExist={0} does not exist
|
||||||
dontOverwriteLocalChanges=error: Your local changes to the following file would be overwritten by merge:
|
dontOverwriteLocalChanges=error: Your local changes to the following file would be overwritten by merge:
|
||||||
everythingUpToDate=Everything up-to-date
|
everythingUpToDate=Everything up-to-date
|
||||||
|
@ -107,6 +109,8 @@ mergeToolDeletedConflictByThem= {local}: modified file\n {remote}: deleted
|
||||||
mergeToolContinueUnresolvedPaths=\nContinue merging other unresolved paths [y/n]?
|
mergeToolContinueUnresolvedPaths=\nContinue merging other unresolved paths [y/n]?
|
||||||
mergeToolWasMergeSuccessfull=Was the merge successful [y/n]?
|
mergeToolWasMergeSuccessfull=Was the merge successful [y/n]?
|
||||||
mergeToolDeletedMergeDecision=Use (m)odified or (d)eleted file, or (a)bort?
|
mergeToolDeletedMergeDecision=Use (m)odified or (d)eleted file, or (a)bort?
|
||||||
|
mergeToolPromptToolName=This message is displayed because 'merge.tool' is not configured.\nSee 'git mergetool --tool-help' or 'git help config' for more details.\n'git mergetool' will now attempt to use one of the following tools:\n{0}\n
|
||||||
|
mergeToolUnknownToolName=Unknown merge tool '{0}'
|
||||||
mergeFailed=Automatic merge failed; fix conflicts and then commit the result
|
mergeFailed=Automatic merge failed; fix conflicts and then commit the result
|
||||||
mergeCheckoutFailed=Please, commit your changes or stash them before you can merge.
|
mergeCheckoutFailed=Please, commit your changes or stash them before you can merge.
|
||||||
mergeMadeBy=Merge made by the ''{0}'' strategy.
|
mergeMadeBy=Merge made by the ''{0}'' strategy.
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2018-2022, Andre Bossert <andre.bossert@siemens.com>
|
* Copyright (C) 2018-2022, Andre Bossert <andre.bossert@siemens.com>
|
||||||
|
* Copyright (C) 2019, Tim Neumann <tim.neumann@advantest.com>
|
||||||
*
|
*
|
||||||
* This program and the accompanying materials are made available under the
|
* This program and the accompanying materials are made available under the
|
||||||
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
||||||
|
@ -22,30 +23,33 @@
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.eclipse.jgit.diff.ContentSource;
|
import org.eclipse.jgit.diff.ContentSource;
|
||||||
import org.eclipse.jgit.diff.ContentSource.Pair;
|
import org.eclipse.jgit.diff.ContentSource.Pair;
|
||||||
import org.eclipse.jgit.diff.DiffEntry;
|
import org.eclipse.jgit.diff.DiffEntry;
|
||||||
import org.eclipse.jgit.diff.DiffEntry.Side;
|
import org.eclipse.jgit.diff.DiffEntry.Side;
|
||||||
import org.eclipse.jgit.internal.diffmergetool.ToolException;
|
|
||||||
import org.eclipse.jgit.internal.diffmergetool.DiffTools;
|
|
||||||
import org.eclipse.jgit.internal.diffmergetool.FileElement;
|
|
||||||
import org.eclipse.jgit.internal.diffmergetool.ExternalDiffTool;
|
|
||||||
import org.eclipse.jgit.diff.DiffFormatter;
|
import org.eclipse.jgit.diff.DiffFormatter;
|
||||||
import org.eclipse.jgit.dircache.DirCacheCheckout;
|
import org.eclipse.jgit.dircache.DirCacheCheckout;
|
||||||
import org.eclipse.jgit.dircache.DirCacheIterator;
|
|
||||||
import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
|
import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
|
||||||
|
import org.eclipse.jgit.dircache.DirCacheIterator;
|
||||||
import org.eclipse.jgit.errors.AmbiguousObjectException;
|
import org.eclipse.jgit.errors.AmbiguousObjectException;
|
||||||
import org.eclipse.jgit.errors.CorruptObjectException;
|
import org.eclipse.jgit.errors.CorruptObjectException;
|
||||||
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
|
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
|
||||||
import org.eclipse.jgit.errors.NoWorkTreeException;
|
import org.eclipse.jgit.errors.NoWorkTreeException;
|
||||||
import org.eclipse.jgit.errors.RevisionSyntaxException;
|
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.PromptContinueHandler;
|
||||||
|
import org.eclipse.jgit.internal.diffmergetool.ToolException;
|
||||||
import org.eclipse.jgit.lib.Constants;
|
import org.eclipse.jgit.lib.Constants;
|
||||||
|
import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.lib.ObjectReader;
|
import org.eclipse.jgit.lib.ObjectReader;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.lib.TextProgressMonitor;
|
import org.eclipse.jgit.lib.TextProgressMonitor;
|
||||||
import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
|
|
||||||
import org.eclipse.jgit.lib.internal.BooleanTriState;
|
import org.eclipse.jgit.lib.internal.BooleanTriState;
|
||||||
import org.eclipse.jgit.pgm.internal.CLIText;
|
import org.eclipse.jgit.pgm.internal.CLIText;
|
||||||
import org.eclipse.jgit.pgm.opt.PathTreeFilterHandler;
|
import org.eclipse.jgit.pgm.opt.PathTreeFilterHandler;
|
||||||
|
@ -58,7 +62,6 @@
|
||||||
import org.eclipse.jgit.treewalk.WorkingTreeOptions;
|
import org.eclipse.jgit.treewalk.WorkingTreeOptions;
|
||||||
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
|
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
|
||||||
import org.eclipse.jgit.treewalk.filter.TreeFilter;
|
import org.eclipse.jgit.treewalk.filter.TreeFilter;
|
||||||
import org.eclipse.jgit.util.StringUtils;
|
|
||||||
import org.eclipse.jgit.util.FS.ExecutionResult;
|
import org.eclipse.jgit.util.FS.ExecutionResult;
|
||||||
import org.kohsuke.args4j.Argument;
|
import org.kohsuke.args4j.Argument;
|
||||||
import org.kohsuke.args4j.Option;
|
import org.kohsuke.args4j.Option;
|
||||||
|
@ -75,9 +78,13 @@ class DiffTool extends TextBuiltin {
|
||||||
@Argument(index = 1, metaVar = "metaVar_treeish")
|
@Argument(index = 1, metaVar = "metaVar_treeish")
|
||||||
private AbstractTreeIterator newTree;
|
private AbstractTreeIterator newTree;
|
||||||
|
|
||||||
|
private Optional<String> toolName = Optional.empty();
|
||||||
|
|
||||||
@Option(name = "--tool", aliases = {
|
@Option(name = "--tool", aliases = {
|
||||||
"-t" }, metaVar = "metaVar_tool", usage = "usage_ToolForDiff")
|
"-t" }, metaVar = "metaVar_tool", usage = "usage_ToolForDiff")
|
||||||
private String toolName;
|
void setToolName(String name) {
|
||||||
|
toolName = Optional.of(name);
|
||||||
|
}
|
||||||
|
|
||||||
@Option(name = "--cached", aliases = { "--staged" }, usage = "usage_cached")
|
@Option(name = "--cached", aliases = { "--staged" }, usage = "usage_cached")
|
||||||
private boolean cached;
|
private boolean cached;
|
||||||
|
@ -97,16 +104,16 @@ void noPrompt(@SuppressWarnings("unused") boolean on) {
|
||||||
@Option(name = "--tool-help", usage = "usage_toolHelp")
|
@Option(name = "--tool-help", usage = "usage_toolHelp")
|
||||||
private boolean toolHelp;
|
private boolean toolHelp;
|
||||||
|
|
||||||
private BooleanTriState gui = BooleanTriState.UNSET;
|
private boolean gui = false;
|
||||||
|
|
||||||
@Option(name = "--gui", aliases = { "-g" }, usage = "usage_DiffGuiTool")
|
@Option(name = "--gui", aliases = { "-g" }, usage = "usage_DiffGuiTool")
|
||||||
void setGui(@SuppressWarnings("unused") boolean on) {
|
void setGui(@SuppressWarnings("unused") boolean on) {
|
||||||
gui = BooleanTriState.TRUE;
|
gui = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Option(name = "--no-gui", usage = "usage_noGui")
|
@Option(name = "--no-gui", usage = "usage_noGui")
|
||||||
void noGui(@SuppressWarnings("unused") boolean on) {
|
void noGui(@SuppressWarnings("unused") boolean on) {
|
||||||
gui = BooleanTriState.FALSE;
|
gui = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private BooleanTriState trustExitCode = BooleanTriState.UNSET;
|
private BooleanTriState trustExitCode = BooleanTriState.UNSET;
|
||||||
|
@ -140,23 +147,12 @@ protected void run() {
|
||||||
if (toolHelp) {
|
if (toolHelp) {
|
||||||
showToolHelp();
|
showToolHelp();
|
||||||
} else {
|
} else {
|
||||||
boolean showPrompt = diffTools.isInteractive();
|
|
||||||
if (prompt != BooleanTriState.UNSET) {
|
|
||||||
showPrompt = prompt == BooleanTriState.TRUE;
|
|
||||||
}
|
|
||||||
String toolNamePrompt = toolName;
|
|
||||||
if (showPrompt) {
|
|
||||||
if (StringUtils.isEmptyOrNull(toolNamePrompt)) {
|
|
||||||
toolNamePrompt = diffTools.getDefaultToolName(gui);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// get the changed files
|
// get the changed files
|
||||||
List<DiffEntry> files = getFiles();
|
List<DiffEntry> files = getFiles();
|
||||||
if (files.size() > 0) {
|
if (files.size() > 0) {
|
||||||
compare(files, showPrompt, toolNamePrompt);
|
compare(files);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
outw.flush();
|
|
||||||
} catch (RevisionSyntaxException | IOException e) {
|
} catch (RevisionSyntaxException | IOException e) {
|
||||||
throw die(e.getMessage(), e);
|
throw die(e.getMessage(), e);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -164,51 +160,103 @@ protected void run() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void compare(List<DiffEntry> files, boolean showPrompt,
|
private void informUserNoTool(List<String> tools) {
|
||||||
String toolNamePrompt) throws IOException {
|
try {
|
||||||
|
StringBuilder toolNames = new StringBuilder();
|
||||||
|
for (String name : tools) {
|
||||||
|
toolNames.append(name + " "); //$NON-NLS-1$
|
||||||
|
}
|
||||||
|
outw.println(MessageFormat.format(
|
||||||
|
CLIText.get().diffToolPromptToolName, toolNames));
|
||||||
|
outw.flush();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException("Cannot output text", e); //$NON-NLS-1$
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CountingPromptContinueHandler
|
||||||
|
implements PromptContinueHandler {
|
||||||
|
private final int fileIndex;
|
||||||
|
|
||||||
|
private final int fileCount;
|
||||||
|
|
||||||
|
private final String fileName;
|
||||||
|
|
||||||
|
public CountingPromptContinueHandler(int fileIndex, int fileCount,
|
||||||
|
String fileName) {
|
||||||
|
this.fileIndex = fileIndex;
|
||||||
|
this.fileCount = fileCount;
|
||||||
|
this.fileName = fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("boxing")
|
||||||
|
@Override
|
||||||
|
public boolean prompt(String toolToLaunchName) {
|
||||||
|
try {
|
||||||
|
boolean launchCompare = true;
|
||||||
|
outw.println(MessageFormat.format(CLIText.get().diffToolLaunch,
|
||||||
|
fileIndex, fileCount, fileName, toolToLaunchName)
|
||||||
|
+ " "); //$NON-NLS-1$
|
||||||
|
outw.flush();
|
||||||
|
BufferedReader br = inputReader;
|
||||||
|
String line = null;
|
||||||
|
if ((line = br.readLine()) != null) {
|
||||||
|
if (!line.equalsIgnoreCase("Y")) { //$NON-NLS-1$
|
||||||
|
launchCompare = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return launchCompare;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException("Cannot output text", e); //$NON-NLS-1$
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void compare(List<DiffEntry> files) throws IOException {
|
||||||
ContentSource.Pair sourcePair = new ContentSource.Pair(source(oldTree),
|
ContentSource.Pair sourcePair = new ContentSource.Pair(source(oldTree),
|
||||||
source(newTree));
|
source(newTree));
|
||||||
try {
|
try {
|
||||||
for (int fileIndex = 0; fileIndex < files.size(); fileIndex++) {
|
for (int fileIndex = 0; fileIndex < files.size(); fileIndex++) {
|
||||||
DiffEntry ent = files.get(fileIndex);
|
DiffEntry ent = files.get(fileIndex);
|
||||||
String mergedFilePath = ent.getNewPath();
|
|
||||||
if (mergedFilePath.equals(DiffEntry.DEV_NULL)) {
|
String filePath = ent.getNewPath();
|
||||||
mergedFilePath = ent.getOldPath();
|
if (filePath.equals(DiffEntry.DEV_NULL)) {
|
||||||
|
filePath = ent.getOldPath();
|
||||||
}
|
}
|
||||||
// check if user wants to launch compare
|
|
||||||
boolean launchCompare = true;
|
try {
|
||||||
if (showPrompt) {
|
FileElement local = createFileElement(
|
||||||
launchCompare = isLaunchCompare(fileIndex + 1, files.size(),
|
FileElement.Type.LOCAL, sourcePair, Side.OLD, ent);
|
||||||
mergedFilePath, toolNamePrompt);
|
FileElement remote = createFileElement(
|
||||||
}
|
FileElement.Type.REMOTE, sourcePair, Side.NEW, ent);
|
||||||
if (launchCompare) {
|
|
||||||
try {
|
PromptContinueHandler promptContinueHandler = new CountingPromptContinueHandler(
|
||||||
FileElement local = createFileElement(
|
fileIndex + 1, files.size(), filePath);
|
||||||
FileElement.Type.LOCAL, sourcePair, Side.OLD,
|
|
||||||
ent);
|
Optional<ExecutionResult> optionalResult = diffTools
|
||||||
FileElement remote = createFileElement(
|
.compare(local, remote, toolName, prompt, gui,
|
||||||
FileElement.Type.REMOTE, sourcePair, Side.NEW,
|
trustExitCode, promptContinueHandler,
|
||||||
ent);
|
this::informUserNoTool);
|
||||||
FileElement merged = new FileElement(mergedFilePath,
|
|
||||||
FileElement.Type.MERGED);
|
if (optionalResult.isPresent()) {
|
||||||
|
ExecutionResult result = optionalResult.get();
|
||||||
// TODO: check how to return the exit-code of the tool
|
// TODO: check how to return the exit-code of the tool
|
||||||
// to jgit / java runtime ?
|
// to jgit / java runtime ?
|
||||||
// int rc =...
|
// int rc =...
|
||||||
ExecutionResult result = diffTools.compare(local,
|
outw.println(
|
||||||
remote, merged, toolName, prompt, gui,
|
new String(result.getStdout().toByteArray()));
|
||||||
trustExitCode);
|
outw.flush();
|
||||||
outw.println(new String(result.getStdout().toByteArray()));
|
|
||||||
errw.println(
|
errw.println(
|
||||||
new String(result.getStderr().toByteArray()));
|
new String(result.getStderr().toByteArray()));
|
||||||
} catch (ToolException e) {
|
errw.flush();
|
||||||
outw.println(e.getResultStdout());
|
|
||||||
outw.flush();
|
|
||||||
errw.println(e.getMessage());
|
|
||||||
throw die(MessageFormat.format(
|
|
||||||
CLIText.get().diffToolDied, mergedFilePath), e);
|
|
||||||
}
|
}
|
||||||
} else {
|
} catch (ToolException e) {
|
||||||
break;
|
outw.println(e.getResultStdout());
|
||||||
|
outw.flush();
|
||||||
|
errw.println(e.getMessage());
|
||||||
|
errw.flush();
|
||||||
|
throw die(MessageFormat.format(
|
||||||
|
CLIText.get().diffToolDied, filePath, e), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -216,32 +264,17 @@ private void compare(List<DiffEntry> files, boolean showPrompt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("boxing")
|
|
||||||
private boolean isLaunchCompare(int fileIndex, int fileCount,
|
|
||||||
String fileName, String toolNamePrompt) throws IOException {
|
|
||||||
boolean launchCompare = true;
|
|
||||||
outw.println(MessageFormat.format(CLIText.get().diffToolLaunch,
|
|
||||||
fileIndex, fileCount, fileName, toolNamePrompt) + " "); //$NON-NLS-1$
|
|
||||||
outw.flush();
|
|
||||||
BufferedReader br = inputReader;
|
|
||||||
String line = null;
|
|
||||||
if ((line = br.readLine()) != null) {
|
|
||||||
if (!line.equalsIgnoreCase("Y")) { //$NON-NLS-1$
|
|
||||||
launchCompare = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return launchCompare;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showToolHelp() throws IOException {
|
private void showToolHelp() throws IOException {
|
||||||
|
Map<String, ExternalDiffTool> predefTools = diffTools
|
||||||
|
.getPredefinedTools(true);
|
||||||
StringBuilder availableToolNames = new StringBuilder();
|
StringBuilder availableToolNames = new StringBuilder();
|
||||||
for (String name : diffTools.getAvailableTools().keySet()) {
|
|
||||||
availableToolNames.append(MessageFormat.format("\t\t{0}\n", name)); //$NON-NLS-1$
|
|
||||||
}
|
|
||||||
StringBuilder notAvailableToolNames = new StringBuilder();
|
StringBuilder notAvailableToolNames = new StringBuilder();
|
||||||
for (String name : diffTools.getNotAvailableTools().keySet()) {
|
for (String name : predefTools.keySet()) {
|
||||||
notAvailableToolNames
|
if (predefTools.get(name).isAvailable()) {
|
||||||
.append(MessageFormat.format("\t\t{0}\n", name)); //$NON-NLS-1$
|
availableToolNames.append(MessageFormat.format("\t\t{0}\n", name)); //$NON-NLS-1$
|
||||||
|
} else {
|
||||||
|
notAvailableToolNames.append(MessageFormat.format("\t\t{0}\n", name)); //$NON-NLS-1$
|
||||||
|
}
|
||||||
}
|
}
|
||||||
StringBuilder userToolNames = new StringBuilder();
|
StringBuilder userToolNames = new StringBuilder();
|
||||||
Map<String, ExternalDiffTool> userTools = diffTools
|
Map<String, ExternalDiffTool> userTools = diffTools
|
||||||
|
@ -289,12 +322,12 @@ private List<DiffEntry> getFiles()
|
||||||
}
|
}
|
||||||
|
|
||||||
private FileElement createFileElement(FileElement.Type elementType,
|
private FileElement createFileElement(FileElement.Type elementType,
|
||||||
Pair pair, Side side, DiffEntry entry)
|
Pair pair, Side side, DiffEntry entry) throws NoWorkTreeException,
|
||||||
throws NoWorkTreeException, CorruptObjectException, IOException,
|
CorruptObjectException, IOException, ToolException {
|
||||||
ToolException {
|
|
||||||
String entryPath = side == Side.NEW ? entry.getNewPath()
|
String entryPath = side == Side.NEW ? entry.getNewPath()
|
||||||
: entry.getOldPath();
|
: entry.getOldPath();
|
||||||
FileElement fileElement = new FileElement(entryPath, elementType);
|
FileElement fileElement = new FileElement(entryPath, elementType,
|
||||||
|
db.getWorkTree());
|
||||||
if (!pair.isWorkingTreeSource(side) && !fileElement.isNullPath()) {
|
if (!pair.isWorkingTreeSource(side) && !fileElement.isNullPath()) {
|
||||||
try (RevWalk revWalk = new RevWalk(db);
|
try (RevWalk revWalk = new RevWalk(db);
|
||||||
TreeWalk treeWalk = new TreeWalk(db,
|
TreeWalk treeWalk = new TreeWalk(db,
|
||||||
|
@ -323,7 +356,8 @@ private FileElement createFileElement(FileElement.Type elementType,
|
||||||
fileElement.createTempFile(null)));
|
fileElement.createTempFile(null)));
|
||||||
} else {
|
} else {
|
||||||
throw new ToolException("Cannot find path '" + entryPath //$NON-NLS-1$
|
throw new ToolException("Cannot find path '" + entryPath //$NON-NLS-1$
|
||||||
+ "' in staging area!", null); //$NON-NLS-1$
|
+ "' in staging area!", //$NON-NLS-1$
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2018-2022, Andre Bossert <andre.bossert@siemens.com>
|
* Copyright (C) 2018-2022, Andre Bossert <andre.bossert@siemens.com>
|
||||||
|
* Copyright (C) 2019, Tim Neumann <tim.neumann@advantest.com>
|
||||||
*
|
*
|
||||||
* This program and the accompanying materials are made available under the
|
* This program and the accompanying materials are made available under the
|
||||||
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
||||||
|
@ -22,6 +23,7 @@
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
|
||||||
import org.eclipse.jgit.api.Git;
|
import org.eclipse.jgit.api.Git;
|
||||||
|
@ -29,29 +31,29 @@
|
||||||
import org.eclipse.jgit.api.StatusCommand;
|
import org.eclipse.jgit.api.StatusCommand;
|
||||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
import org.eclipse.jgit.diff.ContentSource;
|
import org.eclipse.jgit.diff.ContentSource;
|
||||||
import org.eclipse.jgit.internal.diffmergetool.FileElement.Type;
|
|
||||||
import org.eclipse.jgit.dircache.DirCache;
|
import org.eclipse.jgit.dircache.DirCache;
|
||||||
import org.eclipse.jgit.dircache.DirCacheCheckout;
|
import org.eclipse.jgit.dircache.DirCacheCheckout;
|
||||||
|
import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
|
||||||
import org.eclipse.jgit.dircache.DirCacheEntry;
|
import org.eclipse.jgit.dircache.DirCacheEntry;
|
||||||
import org.eclipse.jgit.dircache.DirCacheIterator;
|
import org.eclipse.jgit.dircache.DirCacheIterator;
|
||||||
import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
|
|
||||||
import org.eclipse.jgit.errors.NoWorkTreeException;
|
import org.eclipse.jgit.errors.NoWorkTreeException;
|
||||||
import org.eclipse.jgit.errors.RevisionSyntaxException;
|
import org.eclipse.jgit.errors.RevisionSyntaxException;
|
||||||
import org.eclipse.jgit.internal.diffmergetool.ExternalMergeTool;
|
import org.eclipse.jgit.internal.diffmergetool.ExternalMergeTool;
|
||||||
import org.eclipse.jgit.internal.diffmergetool.FileElement;
|
import org.eclipse.jgit.internal.diffmergetool.FileElement;
|
||||||
|
import org.eclipse.jgit.internal.diffmergetool.FileElement.Type;
|
||||||
import org.eclipse.jgit.internal.diffmergetool.MergeTools;
|
import org.eclipse.jgit.internal.diffmergetool.MergeTools;
|
||||||
import org.eclipse.jgit.internal.diffmergetool.ToolException;
|
import org.eclipse.jgit.internal.diffmergetool.ToolException;
|
||||||
|
import org.eclipse.jgit.lib.Constants;
|
||||||
|
import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
|
||||||
import org.eclipse.jgit.lib.IndexDiff.StageState;
|
import org.eclipse.jgit.lib.IndexDiff.StageState;
|
||||||
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.lib.internal.BooleanTriState;
|
||||||
|
import org.eclipse.jgit.pgm.internal.CLIText;
|
||||||
import org.eclipse.jgit.revwalk.RevWalk;
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
import org.eclipse.jgit.treewalk.TreeWalk;
|
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||||
import org.eclipse.jgit.treewalk.WorkingTreeOptions;
|
import org.eclipse.jgit.treewalk.WorkingTreeOptions;
|
||||||
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
|
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
|
||||||
import org.eclipse.jgit.lib.Constants;
|
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
|
||||||
import org.eclipse.jgit.lib.Repository;
|
|
||||||
import org.eclipse.jgit.lib.internal.BooleanTriState;
|
|
||||||
import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
|
|
||||||
import org.eclipse.jgit.pgm.internal.CLIText;
|
|
||||||
import org.eclipse.jgit.util.FS.ExecutionResult;
|
import org.eclipse.jgit.util.FS.ExecutionResult;
|
||||||
import org.kohsuke.args4j.Argument;
|
import org.kohsuke.args4j.Argument;
|
||||||
import org.kohsuke.args4j.Option;
|
import org.kohsuke.args4j.Option;
|
||||||
|
@ -61,9 +63,13 @@
|
||||||
class MergeTool extends TextBuiltin {
|
class MergeTool extends TextBuiltin {
|
||||||
private MergeTools mergeTools;
|
private MergeTools mergeTools;
|
||||||
|
|
||||||
|
private Optional<String> toolName = Optional.empty();
|
||||||
|
|
||||||
@Option(name = "--tool", aliases = {
|
@Option(name = "--tool", aliases = {
|
||||||
"-t" }, metaVar = "metaVar_tool", usage = "usage_ToolForMerge")
|
"-t" }, metaVar = "metaVar_tool", usage = "usage_ToolForMerge")
|
||||||
private String toolName;
|
void setToolName(String name) {
|
||||||
|
toolName = Optional.of(name);
|
||||||
|
}
|
||||||
|
|
||||||
private BooleanTriState prompt = BooleanTriState.UNSET;
|
private BooleanTriState prompt = BooleanTriState.UNSET;
|
||||||
|
|
||||||
|
@ -80,16 +86,16 @@ void noPrompt(@SuppressWarnings("unused") boolean on) {
|
||||||
@Option(name = "--tool-help", usage = "usage_toolHelp")
|
@Option(name = "--tool-help", usage = "usage_toolHelp")
|
||||||
private boolean toolHelp;
|
private boolean toolHelp;
|
||||||
|
|
||||||
private BooleanTriState gui = BooleanTriState.UNSET;
|
private boolean gui = false;
|
||||||
|
|
||||||
@Option(name = "--gui", aliases = { "-g" }, usage = "usage_MergeGuiTool")
|
@Option(name = "--gui", aliases = { "-g" }, usage = "usage_MergeGuiTool")
|
||||||
void setGui(@SuppressWarnings("unused") boolean on) {
|
void setGui(@SuppressWarnings("unused") boolean on) {
|
||||||
gui = BooleanTriState.TRUE;
|
gui = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Option(name = "--no-gui", usage = "usage_noGui")
|
@Option(name = "--no-gui", usage = "usage_noGui")
|
||||||
void noGui(@SuppressWarnings("unused") boolean on) {
|
void noGui(@SuppressWarnings("unused") boolean on) {
|
||||||
gui = BooleanTriState.FALSE;
|
gui = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Argument(required = false, index = 0, metaVar = "metaVar_paths")
|
@Argument(required = false, index = 0, metaVar = "metaVar_paths")
|
||||||
|
@ -115,20 +121,10 @@ protected void run() {
|
||||||
if (toolHelp) {
|
if (toolHelp) {
|
||||||
showToolHelp();
|
showToolHelp();
|
||||||
} else {
|
} else {
|
||||||
// get prompt
|
|
||||||
boolean showPrompt = mergeTools.isInteractive();
|
|
||||||
if (prompt != BooleanTriState.UNSET) {
|
|
||||||
showPrompt = prompt == BooleanTriState.TRUE;
|
|
||||||
}
|
|
||||||
// get passed or default tool name
|
|
||||||
String toolNameSelected = toolName;
|
|
||||||
if ((toolNameSelected == null) || toolNameSelected.isEmpty()) {
|
|
||||||
toolNameSelected = mergeTools.getDefaultToolName(gui);
|
|
||||||
}
|
|
||||||
// get the changed files
|
// get the changed files
|
||||||
Map<String, StageState> files = getFiles();
|
Map<String, StageState> files = getFiles();
|
||||||
if (files.size() > 0) {
|
if (files.size() > 0) {
|
||||||
merge(files, showPrompt, toolNameSelected);
|
merge(files);
|
||||||
} else {
|
} else {
|
||||||
outw.println(CLIText.get().mergeToolNoFiles);
|
outw.println(CLIText.get().mergeToolNoFiles);
|
||||||
}
|
}
|
||||||
|
@ -139,8 +135,21 @@ protected void run() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void merge(Map<String, StageState> files, boolean showPrompt,
|
private void informUserNoTool(List<String> tools) {
|
||||||
String toolNamePrompt) throws Exception {
|
try {
|
||||||
|
StringBuilder toolNames = new StringBuilder();
|
||||||
|
for (String name : tools) {
|
||||||
|
toolNames.append(name + " "); //$NON-NLS-1$
|
||||||
|
}
|
||||||
|
outw.println(MessageFormat
|
||||||
|
.format(CLIText.get().mergeToolPromptToolName, toolNames));
|
||||||
|
outw.flush();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException("Cannot output text", e); //$NON-NLS-1$
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void merge(Map<String, StageState> files) throws Exception {
|
||||||
// sort file names
|
// sort file names
|
||||||
List<String> mergedFilePaths = new ArrayList<>(files.keySet());
|
List<String> mergedFilePaths = new ArrayList<>(files.keySet());
|
||||||
Collections.sort(mergedFilePaths);
|
Collections.sort(mergedFilePaths);
|
||||||
|
@ -152,6 +161,10 @@ private void merge(Map<String, StageState> files, boolean showPrompt,
|
||||||
outw.println(MessageFormat.format(CLIText.get().mergeToolMerging,
|
outw.println(MessageFormat.format(CLIText.get().mergeToolMerging,
|
||||||
mergedFiles));
|
mergedFiles));
|
||||||
outw.flush();
|
outw.flush();
|
||||||
|
boolean showPrompt = mergeTools.isInteractive();
|
||||||
|
if (prompt != BooleanTriState.UNSET) {
|
||||||
|
showPrompt = prompt == BooleanTriState.TRUE;
|
||||||
|
}
|
||||||
// merge the files
|
// merge the files
|
||||||
MergeResult mergeResult = MergeResult.SUCCESSFUL;
|
MergeResult mergeResult = MergeResult.SUCCESSFUL;
|
||||||
for (String mergedFilePath : mergedFilePaths) {
|
for (String mergedFilePath : mergedFilePaths) {
|
||||||
|
@ -169,8 +182,7 @@ private void merge(Map<String, StageState> files, boolean showPrompt,
|
||||||
// get file stage state and merge
|
// get file stage state and merge
|
||||||
StageState fileState = files.get(mergedFilePath);
|
StageState fileState = files.get(mergedFilePath);
|
||||||
if (fileState == StageState.BOTH_MODIFIED) {
|
if (fileState == StageState.BOTH_MODIFIED) {
|
||||||
mergeResult = mergeModified(mergedFilePath, showPrompt,
|
mergeResult = mergeModified(mergedFilePath, showPrompt);
|
||||||
toolNamePrompt);
|
|
||||||
} else if ((fileState == StageState.DELETED_BY_US)
|
} else if ((fileState == StageState.DELETED_BY_US)
|
||||||
|| (fileState == StageState.DELETED_BY_THEM)) {
|
|| (fileState == StageState.DELETED_BY_THEM)) {
|
||||||
mergeResult = mergeDeleted(mergedFilePath,
|
mergeResult = mergeDeleted(mergedFilePath,
|
||||||
|
@ -184,19 +196,11 @@ private void merge(Map<String, StageState> files, boolean showPrompt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private MergeResult mergeModified(String mergedFilePath, boolean showPrompt,
|
private MergeResult mergeModified(String mergedFilePath, boolean showPrompt)
|
||||||
String toolNamePrompt) throws Exception {
|
throws Exception {
|
||||||
outw.println(MessageFormat.format(CLIText.get().mergeToolNormalConflict,
|
outw.println(MessageFormat.format(CLIText.get().mergeToolNormalConflict,
|
||||||
mergedFilePath));
|
mergedFilePath));
|
||||||
outw.flush();
|
outw.flush();
|
||||||
// check if user wants to launch merge resolution tool
|
|
||||||
boolean launch = true;
|
|
||||||
if (showPrompt) {
|
|
||||||
launch = isLaunch(toolNamePrompt);
|
|
||||||
}
|
|
||||||
if (!launch) {
|
|
||||||
return MergeResult.ABORTED; // abort
|
|
||||||
}
|
|
||||||
boolean isMergeSuccessful = true;
|
boolean isMergeSuccessful = true;
|
||||||
ContentSource baseSource = ContentSource.create(db.newObjectReader());
|
ContentSource baseSource = ContentSource.create(db.newObjectReader());
|
||||||
ContentSource localSource = ContentSource.create(db.newObjectReader());
|
ContentSource localSource = ContentSource.create(db.newObjectReader());
|
||||||
|
@ -210,8 +214,8 @@ private MergeResult mergeModified(String mergedFilePath, boolean showPrompt,
|
||||||
FileElement base = null;
|
FileElement base = null;
|
||||||
FileElement local = null;
|
FileElement local = null;
|
||||||
FileElement remote = null;
|
FileElement remote = null;
|
||||||
FileElement merged = new FileElement(mergedFilePath,
|
FileElement merged = new FileElement(mergedFilePath, Type.MERGED,
|
||||||
Type.MERGED);
|
db.getWorkTree());
|
||||||
DirCache cache = db.readDirCache();
|
DirCache cache = db.readDirCache();
|
||||||
try (RevWalk revWalk = new RevWalk(db);
|
try (RevWalk revWalk = new RevWalk(db);
|
||||||
TreeWalk treeWalk = new TreeWalk(db,
|
TreeWalk treeWalk = new TreeWalk(db,
|
||||||
|
@ -233,7 +237,8 @@ private MergeResult mergeModified(String mergedFilePath, boolean showPrompt,
|
||||||
.get(WorkingTreeOptions.KEY);
|
.get(WorkingTreeOptions.KEY);
|
||||||
CheckoutMetadata checkoutMetadata = new CheckoutMetadata(
|
CheckoutMetadata checkoutMetadata = new CheckoutMetadata(
|
||||||
eolStreamType, filterCommand);
|
eolStreamType, filterCommand);
|
||||||
DirCacheEntry entry = treeWalk.getTree(DirCacheIterator.class).getDirCacheEntry();
|
DirCacheEntry entry = treeWalk
|
||||||
|
.getTree(DirCacheIterator.class).getDirCacheEntry();
|
||||||
if (entry == null) {
|
if (entry == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -275,23 +280,27 @@ private MergeResult mergeModified(String mergedFilePath, boolean showPrompt,
|
||||||
// TODO: check how to return the exit-code of the
|
// TODO: check how to return the exit-code of the
|
||||||
// tool to jgit / java runtime ?
|
// tool to jgit / java runtime ?
|
||||||
// int rc =...
|
// int rc =...
|
||||||
ExecutionResult executionResult = mergeTools.merge(local,
|
Optional<ExecutionResult> optionalResult = mergeTools.merge(
|
||||||
remote, merged, base, tempDir, toolName, prompt, gui);
|
local, remote, merged, base, tempDir, toolName, prompt,
|
||||||
outw.println(
|
gui, this::promptForLaunch, this::informUserNoTool);
|
||||||
new String(executionResult.getStdout().toByteArray()));
|
if (optionalResult.isPresent()) {
|
||||||
outw.flush();
|
ExecutionResult result = optionalResult.get();
|
||||||
errw.println(
|
outw.println(new String(result.getStdout().toByteArray()));
|
||||||
new String(executionResult.getStderr().toByteArray()));
|
outw.flush();
|
||||||
errw.flush();
|
errw.println(new String(result.getStderr().toByteArray()));
|
||||||
|
errw.flush();
|
||||||
|
} else {
|
||||||
|
return MergeResult.ABORTED;
|
||||||
|
}
|
||||||
} catch (ToolException e) {
|
} catch (ToolException e) {
|
||||||
isMergeSuccessful = false;
|
isMergeSuccessful = false;
|
||||||
outw.println(e.getResultStdout());
|
outw.println(e.getResultStdout());
|
||||||
outw.flush();
|
outw.flush();
|
||||||
|
errw.println(e.getMessage());
|
||||||
errw.println(MessageFormat.format(
|
errw.println(MessageFormat.format(
|
||||||
CLIText.get().mergeToolMergeFailed, mergedFilePath));
|
CLIText.get().mergeToolMergeFailed, mergedFilePath));
|
||||||
errw.flush();
|
errw.flush();
|
||||||
if (e.isCommandExecutionError()) {
|
if (e.isCommandExecutionError()) {
|
||||||
errw.println(e.getMessage());
|
|
||||||
throw die(CLIText.get().mergeToolExecutionError, e);
|
throw die(CLIText.get().mergeToolExecutionError, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -380,19 +389,23 @@ private boolean isMergeSuccessful() throws IOException {
|
||||||
return hasUserAccepted(CLIText.get().mergeToolWasMergeSuccessfull);
|
return hasUserAccepted(CLIText.get().mergeToolWasMergeSuccessfull);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isLaunch(String toolNamePrompt) throws IOException {
|
private boolean promptForLaunch(String toolNamePrompt) {
|
||||||
boolean launch = true;
|
try {
|
||||||
outw.print(MessageFormat.format(CLIText.get().mergeToolLaunch,
|
boolean launch = true;
|
||||||
toolNamePrompt) + " "); //$NON-NLS-1$
|
outw.print(MessageFormat.format(CLIText.get().mergeToolLaunch,
|
||||||
outw.flush();
|
toolNamePrompt) + " "); //$NON-NLS-1$
|
||||||
BufferedReader br = inputReader;
|
outw.flush();
|
||||||
String line = null;
|
BufferedReader br = inputReader;
|
||||||
if ((line = br.readLine()) != null) {
|
String line = null;
|
||||||
if (!line.equalsIgnoreCase("y") && !line.equalsIgnoreCase("")) { //$NON-NLS-1$ //$NON-NLS-2$
|
if ((line = br.readLine()) != null) {
|
||||||
launch = false;
|
if (!line.equalsIgnoreCase("y") && !line.equalsIgnoreCase("")) { //$NON-NLS-1$ //$NON-NLS-2$
|
||||||
|
launch = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return launch;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException("Cannot output text", e); //$NON-NLS-1$
|
||||||
}
|
}
|
||||||
return launch;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getDeletedMergeDecision() throws IOException {
|
private int getDeletedMergeDecision() throws IOException {
|
||||||
|
@ -420,14 +433,16 @@ private int getDeletedMergeDecision() throws IOException {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showToolHelp() throws IOException {
|
private void showToolHelp() throws IOException {
|
||||||
|
Map<String, ExternalMergeTool> predefTools = mergeTools
|
||||||
|
.getPredefinedTools(true);
|
||||||
StringBuilder availableToolNames = new StringBuilder();
|
StringBuilder availableToolNames = new StringBuilder();
|
||||||
for (String name : mergeTools.getAvailableTools().keySet()) {
|
|
||||||
availableToolNames.append(MessageFormat.format("\t\t{0}\n", name)); //$NON-NLS-1$
|
|
||||||
}
|
|
||||||
StringBuilder notAvailableToolNames = new StringBuilder();
|
StringBuilder notAvailableToolNames = new StringBuilder();
|
||||||
for (String name : mergeTools.getNotAvailableTools().keySet()) {
|
for (String name : predefTools.keySet()) {
|
||||||
notAvailableToolNames
|
if (predefTools.get(name).isAvailable()) {
|
||||||
.append(MessageFormat.format("\t\t{0}\n", name)); //$NON-NLS-1$
|
availableToolNames.append(MessageFormat.format("\t\t{0}\n", name)); //$NON-NLS-1$
|
||||||
|
} else {
|
||||||
|
notAvailableToolNames.append(MessageFormat.format("\t\t{0}\n", name)); //$NON-NLS-1$
|
||||||
|
}
|
||||||
}
|
}
|
||||||
StringBuilder userToolNames = new StringBuilder();
|
StringBuilder userToolNames = new StringBuilder();
|
||||||
Map<String, ExternalMergeTool> userTools = mergeTools
|
Map<String, ExternalMergeTool> userTools = mergeTools
|
||||||
|
|
|
@ -139,6 +139,8 @@ public static String fatalError(String message) {
|
||||||
/***/ public String diffToolHelpSetToFollowing;
|
/***/ public String diffToolHelpSetToFollowing;
|
||||||
/***/ public String diffToolLaunch;
|
/***/ public String diffToolLaunch;
|
||||||
/***/ public String diffToolDied;
|
/***/ public String diffToolDied;
|
||||||
|
/***/ public String diffToolPromptToolName;
|
||||||
|
/***/ public String diffToolUnknownToolName;
|
||||||
/***/ public String doesNotExist;
|
/***/ public String doesNotExist;
|
||||||
/***/ public String dontOverwriteLocalChanges;
|
/***/ public String dontOverwriteLocalChanges;
|
||||||
/***/ public String everythingUpToDate;
|
/***/ public String everythingUpToDate;
|
||||||
|
@ -185,6 +187,8 @@ public static String fatalError(String message) {
|
||||||
/***/ public String mergeToolContinueUnresolvedPaths;
|
/***/ public String mergeToolContinueUnresolvedPaths;
|
||||||
/***/ public String mergeToolWasMergeSuccessfull;
|
/***/ public String mergeToolWasMergeSuccessfull;
|
||||||
/***/ public String mergeToolDeletedMergeDecision;
|
/***/ public String mergeToolDeletedMergeDecision;
|
||||||
|
/***/ public String mergeToolPromptToolName;
|
||||||
|
/***/ public String mergeToolUnknownToolName;
|
||||||
/***/ public String mergeFailed;
|
/***/ public String mergeFailed;
|
||||||
/***/ public String mergeCheckoutFailed;
|
/***/ public String mergeCheckoutFailed;
|
||||||
/***/ public String mergeMadeBy;
|
/***/ public String mergeMadeBy;
|
||||||
|
|
|
@ -18,15 +18,25 @@
|
||||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_TOOL;
|
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.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_TRUST_EXIT_CODE;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.junit.TestRepository;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.lib.internal.BooleanTriState;
|
import org.eclipse.jgit.lib.internal.BooleanTriState;
|
||||||
import org.eclipse.jgit.storage.file.FileBasedConfig;
|
import org.eclipse.jgit.storage.file.FileBasedConfig;
|
||||||
import org.eclipse.jgit.util.FS.ExecutionResult;
|
import org.eclipse.jgit.util.FS.ExecutionResult;
|
||||||
|
@ -48,14 +58,7 @@ public void testUserToolWithError() throws Exception {
|
||||||
config.setString(CONFIG_DIFFTOOL_SECTION, toolName, CONFIG_KEY_CMD,
|
config.setString(CONFIG_DIFFTOOL_SECTION, toolName, CONFIG_KEY_CMD,
|
||||||
command);
|
command);
|
||||||
|
|
||||||
DiffTools manager = new DiffTools(db);
|
invokeCompare(toolName);
|
||||||
|
|
||||||
BooleanTriState prompt = BooleanTriState.UNSET;
|
|
||||||
BooleanTriState gui = BooleanTriState.UNSET;
|
|
||||||
BooleanTriState trustExitCode = BooleanTriState.TRUE;
|
|
||||||
|
|
||||||
manager.compare(local, remote, merged, toolName, prompt, gui,
|
|
||||||
trustExitCode);
|
|
||||||
|
|
||||||
fail("Expected exception to be thrown due to external tool exiting with error code: "
|
fail("Expected exception to be thrown due to external tool exiting with error code: "
|
||||||
+ errorReturnCode);
|
+ errorReturnCode);
|
||||||
|
@ -72,33 +75,91 @@ public void testUserToolWithCommandNotFoundError() throws Exception {
|
||||||
config.setString(CONFIG_DIFFTOOL_SECTION, toolName, CONFIG_KEY_CMD,
|
config.setString(CONFIG_DIFFTOOL_SECTION, toolName, CONFIG_KEY_CMD,
|
||||||
command);
|
command);
|
||||||
|
|
||||||
DiffTools manager = new DiffTools(db);
|
invokeCompare(toolName);
|
||||||
|
|
||||||
BooleanTriState prompt = BooleanTriState.UNSET;
|
|
||||||
BooleanTriState gui = BooleanTriState.UNSET;
|
|
||||||
BooleanTriState trustExitCode = BooleanTriState.FALSE;
|
|
||||||
|
|
||||||
manager.compare(local, remote, merged, toolName, prompt, gui,
|
|
||||||
trustExitCode);
|
|
||||||
|
|
||||||
fail("Expected exception to be thrown due to external tool exiting with error code: "
|
fail("Expected exception to be thrown due to external tool exiting with error code: "
|
||||||
+ errorReturnCode);
|
+ errorReturnCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testToolNames() {
|
public void testUserDefinedTool() throws Exception {
|
||||||
|
String command = getEchoCommand();
|
||||||
|
|
||||||
|
FileBasedConfig config = db.getConfig();
|
||||||
|
String customToolName = "customTool";
|
||||||
|
config.setString(CONFIG_DIFFTOOL_SECTION, customToolName,
|
||||||
|
CONFIG_KEY_CMD, command);
|
||||||
|
|
||||||
DiffTools manager = new DiffTools(db);
|
DiffTools manager = new DiffTools(db);
|
||||||
Set<String> actualToolNames = manager.getToolNames();
|
|
||||||
Set<String> expectedToolNames = Collections.emptySet();
|
Map<String, ExternalDiffTool> tools = manager.getUserDefinedTools();
|
||||||
assertEquals("Incorrect set of external diff tool names",
|
ExternalDiffTool externalTool = tools.get(customToolName);
|
||||||
expectedToolNames, actualToolNames);
|
boolean trustExitCode = true;
|
||||||
|
manager.compare(local, remote, externalTool, trustExitCode);
|
||||||
|
|
||||||
|
assertEchoCommandHasCorrectOutput();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUserDefinedToolWithPrompt() throws Exception {
|
||||||
|
String command = getEchoCommand();
|
||||||
|
|
||||||
|
FileBasedConfig config = db.getConfig();
|
||||||
|
String customToolName = "customTool";
|
||||||
|
config.setString(CONFIG_DIFFTOOL_SECTION, customToolName,
|
||||||
|
CONFIG_KEY_CMD, command);
|
||||||
|
|
||||||
|
DiffTools manager = new DiffTools(db);
|
||||||
|
|
||||||
|
PromptHandler promptHandler = PromptHandler.acceptPrompt();
|
||||||
|
MissingToolHandler noToolHandler = new MissingToolHandler();
|
||||||
|
|
||||||
|
manager.compare(local, remote, Optional.of(customToolName),
|
||||||
|
BooleanTriState.TRUE, false, BooleanTriState.TRUE,
|
||||||
|
promptHandler, noToolHandler);
|
||||||
|
|
||||||
|
assertEchoCommandHasCorrectOutput();
|
||||||
|
|
||||||
|
List<String> actualToolPrompts = promptHandler.toolPrompts;
|
||||||
|
List<String> expectedToolPrompts = Arrays.asList("customTool");
|
||||||
|
assertEquals("Expected a user prompt for custom tool call",
|
||||||
|
expectedToolPrompts, actualToolPrompts);
|
||||||
|
|
||||||
|
assertEquals("Expected to no informing about missing tools",
|
||||||
|
Collections.EMPTY_LIST, noToolHandler.missingTools);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUserDefinedToolWithCancelledPrompt() throws Exception {
|
||||||
|
String command = getEchoCommand();
|
||||||
|
|
||||||
|
FileBasedConfig config = db.getConfig();
|
||||||
|
String customToolName = "customTool";
|
||||||
|
config.setString(CONFIG_DIFFTOOL_SECTION, customToolName,
|
||||||
|
CONFIG_KEY_CMD, command);
|
||||||
|
|
||||||
|
DiffTools manager = new DiffTools(db);
|
||||||
|
|
||||||
|
PromptHandler promptHandler = PromptHandler.cancelPrompt();
|
||||||
|
MissingToolHandler noToolHandler = new MissingToolHandler();
|
||||||
|
|
||||||
|
Optional<ExecutionResult> result = manager.compare(local, remote,
|
||||||
|
Optional.of(customToolName), BooleanTriState.TRUE, false,
|
||||||
|
BooleanTriState.TRUE, promptHandler, noToolHandler);
|
||||||
|
assertFalse("Expected no result if user cancels the operation",
|
||||||
|
result.isPresent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAllTools() {
|
public void testAllTools() {
|
||||||
|
FileBasedConfig config = db.getConfig();
|
||||||
|
String customToolName = "customTool";
|
||||||
|
config.setString(CONFIG_DIFFTOOL_SECTION, customToolName,
|
||||||
|
CONFIG_KEY_CMD, "echo");
|
||||||
|
|
||||||
DiffTools manager = new DiffTools(db);
|
DiffTools manager = new DiffTools(db);
|
||||||
Set<String> actualToolNames = manager.getAvailableTools().keySet();
|
Set<String> actualToolNames = manager.getAllToolNames();
|
||||||
Set<String> expectedToolNames = new LinkedHashSet<>();
|
Set<String> expectedToolNames = new LinkedHashSet<>();
|
||||||
|
expectedToolNames.add(customToolName);
|
||||||
CommandLineDiffTool[] defaultTools = CommandLineDiffTool.values();
|
CommandLineDiffTool[] defaultTools = CommandLineDiffTool.values();
|
||||||
for (CommandLineDiffTool defaultTool : defaultTools) {
|
for (CommandLineDiffTool defaultTool : defaultTools) {
|
||||||
String toolName = defaultTool.name();
|
String toolName = defaultTool.name();
|
||||||
|
@ -152,15 +213,6 @@ public void testUserDefinedTools() {
|
||||||
actualToolNames);
|
actualToolNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testNotAvailableTools() {
|
|
||||||
DiffTools manager = new DiffTools(db);
|
|
||||||
Set<String> actualToolNames = manager.getNotAvailableTools().keySet();
|
|
||||||
Set<String> expectedToolNames = Collections.emptySet();
|
|
||||||
assertEquals("Incorrect set of not available external diff tools",
|
|
||||||
expectedToolNames, actualToolNames);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCompare() throws ToolException {
|
public void testCompare() throws ToolException {
|
||||||
String toolName = "customTool";
|
String toolName = "customTool";
|
||||||
|
@ -175,18 +227,12 @@ public void testCompare() throws ToolException {
|
||||||
|
|
||||||
config.setString(CONFIG_DIFFTOOL_SECTION, toolName, CONFIG_KEY_CMD,
|
config.setString(CONFIG_DIFFTOOL_SECTION, toolName, CONFIG_KEY_CMD,
|
||||||
command);
|
command);
|
||||||
|
Optional<ExecutionResult> result = invokeCompare(toolName);
|
||||||
BooleanTriState prompt = BooleanTriState.UNSET;
|
assertTrue("Expected external diff tool result to be available",
|
||||||
BooleanTriState gui = BooleanTriState.UNSET;
|
result.isPresent());
|
||||||
BooleanTriState trustExitCode = BooleanTriState.UNSET;
|
|
||||||
|
|
||||||
DiffTools manager = new DiffTools(db);
|
|
||||||
|
|
||||||
int expectedCompareResult = 0;
|
int expectedCompareResult = 0;
|
||||||
ExecutionResult compareResult = manager.compare(local, remote, merged,
|
|
||||||
toolName, prompt, gui, trustExitCode);
|
|
||||||
assertEquals("Incorrect compare result for external diff tool",
|
assertEquals("Incorrect compare result for external diff tool",
|
||||||
expectedCompareResult, compareResult.getRc());
|
expectedCompareResult, result.get().getRc());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -201,17 +247,17 @@ public void testDefaultTool() throws Exception {
|
||||||
toolName);
|
toolName);
|
||||||
|
|
||||||
DiffTools manager = new DiffTools(db);
|
DiffTools manager = new DiffTools(db);
|
||||||
BooleanTriState gui = BooleanTriState.UNSET;
|
boolean gui = false;
|
||||||
String defaultToolName = manager.getDefaultToolName(gui);
|
String defaultToolName = manager.getDefaultToolName(gui);
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"Expected configured difftool to be the default external diff tool",
|
"Expected configured difftool to be the default external diff tool",
|
||||||
toolName, defaultToolName);
|
toolName, defaultToolName);
|
||||||
|
|
||||||
gui = BooleanTriState.TRUE;
|
gui = true;
|
||||||
String defaultGuiToolName = manager.getDefaultToolName(gui);
|
String defaultGuiToolName = manager.getDefaultToolName(gui);
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"Expected configured difftool to be the default external diff tool",
|
"Expected default gui difftool to be the default tool if no gui tool is set",
|
||||||
"my_gui_tool", defaultGuiToolName);
|
toolName, defaultGuiToolName);
|
||||||
|
|
||||||
config.setString(CONFIG_DIFF_SECTION, subsection, CONFIG_KEY_GUITOOL,
|
config.setString(CONFIG_DIFF_SECTION, subsection, CONFIG_KEY_GUITOOL,
|
||||||
guiToolName);
|
guiToolName);
|
||||||
|
@ -219,7 +265,7 @@ public void testDefaultTool() throws Exception {
|
||||||
defaultGuiToolName = manager.getDefaultToolName(gui);
|
defaultGuiToolName = manager.getDefaultToolName(gui);
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"Expected configured difftool to be the default external diff guitool",
|
"Expected configured difftool to be the default external diff guitool",
|
||||||
"my_gui_tool", defaultGuiToolName);
|
guiToolName, defaultGuiToolName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -239,7 +285,7 @@ public void testOverridePreDefinedToolPath() {
|
||||||
|
|
||||||
DiffTools manager = new DiffTools(db);
|
DiffTools manager = new DiffTools(db);
|
||||||
Map<String, ExternalDiffTool> availableTools = manager
|
Map<String, ExternalDiffTool> availableTools = manager
|
||||||
.getAvailableTools();
|
.getPredefinedTools(true);
|
||||||
ExternalDiffTool externalDiffTool = availableTools
|
ExternalDiffTool externalDiffTool = availableTools
|
||||||
.get(overridenToolName);
|
.get(overridenToolName);
|
||||||
String actualDiffToolPath = externalDiffTool.getPath();
|
String actualDiffToolPath = externalDiffTool.getPath();
|
||||||
|
@ -256,20 +302,152 @@ public void testOverridePreDefinedToolPath() {
|
||||||
|
|
||||||
@Test(expected = ToolException.class)
|
@Test(expected = ToolException.class)
|
||||||
public void testUndefinedTool() throws Exception {
|
public void testUndefinedTool() throws Exception {
|
||||||
|
String toolName = "undefined";
|
||||||
|
invokeCompare(toolName);
|
||||||
|
fail("Expected exception to be thrown due to not defined external diff tool");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDefaultToolExecutionWithPrompt() throws Exception {
|
||||||
|
FileBasedConfig config = db.getConfig();
|
||||||
|
// the default diff tool is configured without a subsection
|
||||||
|
String subsection = null;
|
||||||
|
config.setString("diff", subsection, "tool", "customTool");
|
||||||
|
|
||||||
|
String command = getEchoCommand();
|
||||||
|
|
||||||
|
config.setString("difftool", "customTool", "cmd", command);
|
||||||
|
|
||||||
DiffTools manager = new DiffTools(db);
|
DiffTools manager = new DiffTools(db);
|
||||||
|
|
||||||
String toolName = "undefined";
|
PromptHandler promptHandler = PromptHandler.acceptPrompt();
|
||||||
BooleanTriState prompt = BooleanTriState.UNSET;
|
MissingToolHandler noToolHandler = new MissingToolHandler();
|
||||||
BooleanTriState gui = BooleanTriState.UNSET;
|
|
||||||
BooleanTriState trustExitCode = BooleanTriState.UNSET;
|
|
||||||
|
|
||||||
manager.compare(local, remote, merged, toolName, prompt, gui,
|
manager.compare(local, remote, Optional.empty(), BooleanTriState.TRUE,
|
||||||
trustExitCode);
|
false, BooleanTriState.TRUE, promptHandler, noToolHandler);
|
||||||
fail("Expected exception to be thrown due to not defined external diff tool");
|
|
||||||
|
assertEchoCommandHasCorrectOutput();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoDefaultToolName() {
|
||||||
|
DiffTools manager = new DiffTools(db);
|
||||||
|
boolean gui = false;
|
||||||
|
String defaultToolName = manager.getDefaultToolName(gui);
|
||||||
|
assertNull("Expected no default tool when none is configured",
|
||||||
|
defaultToolName);
|
||||||
|
|
||||||
|
gui = true;
|
||||||
|
defaultToolName = manager.getDefaultToolName(gui);
|
||||||
|
assertNull("Expected no default tool when none is configured",
|
||||||
|
defaultToolName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExternalToolInGitAttributes() throws Exception {
|
||||||
|
String content = "attributes:\n*.txt difftool=customTool";
|
||||||
|
File gitattributes = writeTrashFile(".gitattributes", content);
|
||||||
|
gitattributes.deleteOnExit();
|
||||||
|
try (TestRepository<Repository> testRepository = new TestRepository<>(
|
||||||
|
db)) {
|
||||||
|
FileBasedConfig config = db.getConfig();
|
||||||
|
config.setString("difftool", "customTool", "cmd", "echo");
|
||||||
|
testRepository.git().add().addFilepattern(localFile.getName())
|
||||||
|
.call();
|
||||||
|
|
||||||
|
testRepository.git().add().addFilepattern(".gitattributes").call();
|
||||||
|
|
||||||
|
testRepository.branch("master").commit().message("first commit")
|
||||||
|
.create();
|
||||||
|
|
||||||
|
DiffTools manager = new DiffTools(db);
|
||||||
|
Optional<String> tool = manager
|
||||||
|
.getExternalToolFromAttributes(localFile.getName());
|
||||||
|
assertTrue("Failed to find user defined tool", tool.isPresent());
|
||||||
|
assertEquals("Failed to find user defined tool", "customTool",
|
||||||
|
tool.get());
|
||||||
|
} finally {
|
||||||
|
Files.delete(gitattributes.toPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNotExternalToolInGitAttributes() throws Exception {
|
||||||
|
String content = "";
|
||||||
|
File gitattributes = writeTrashFile(".gitattributes", content);
|
||||||
|
gitattributes.deleteOnExit();
|
||||||
|
try (TestRepository<Repository> testRepository = new TestRepository<>(
|
||||||
|
db)) {
|
||||||
|
FileBasedConfig config = db.getConfig();
|
||||||
|
config.setString("difftool", "customTool", "cmd", "echo");
|
||||||
|
testRepository.git().add().addFilepattern(localFile.getName())
|
||||||
|
.call();
|
||||||
|
|
||||||
|
testRepository.git().add().addFilepattern(".gitattributes").call();
|
||||||
|
|
||||||
|
testRepository.branch("master").commit().message("first commit")
|
||||||
|
.create();
|
||||||
|
|
||||||
|
DiffTools manager = new DiffTools(db);
|
||||||
|
Optional<String> tool = manager
|
||||||
|
.getExternalToolFromAttributes(localFile.getName());
|
||||||
|
assertFalse(
|
||||||
|
"Expected no external tool if no default tool is specified in .gitattributes",
|
||||||
|
tool.isPresent());
|
||||||
|
} finally {
|
||||||
|
Files.delete(gitattributes.toPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = ToolException.class)
|
||||||
|
public void testNullTool() throws Exception {
|
||||||
|
DiffTools manager = new DiffTools(db);
|
||||||
|
|
||||||
|
boolean trustExitCode = true;
|
||||||
|
ExternalDiffTool tool = null;
|
||||||
|
manager.compare(local, remote, tool, trustExitCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = ToolException.class)
|
||||||
|
public void testNullToolWithPrompt() throws Exception {
|
||||||
|
DiffTools manager = new DiffTools(db);
|
||||||
|
|
||||||
|
PromptHandler promptHandler = PromptHandler.cancelPrompt();
|
||||||
|
MissingToolHandler noToolHandler = new MissingToolHandler();
|
||||||
|
|
||||||
|
Optional<String> tool = null;
|
||||||
|
manager.compare(local, remote, tool, BooleanTriState.TRUE, false,
|
||||||
|
BooleanTriState.TRUE, promptHandler, noToolHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<ExecutionResult> invokeCompare(String toolName)
|
||||||
|
throws ToolException {
|
||||||
|
DiffTools manager = new DiffTools(db);
|
||||||
|
|
||||||
|
BooleanTriState prompt = BooleanTriState.UNSET;
|
||||||
|
boolean gui = false;
|
||||||
|
BooleanTriState trustExitCode = BooleanTriState.TRUE;
|
||||||
|
PromptHandler promptHandler = PromptHandler.acceptPrompt();
|
||||||
|
MissingToolHandler noToolHandler = new MissingToolHandler();
|
||||||
|
|
||||||
|
Optional<ExecutionResult> result = manager.compare(local, remote,
|
||||||
|
Optional.of(toolName), prompt, gui, trustExitCode,
|
||||||
|
promptHandler, noToolHandler);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getEchoCommand() {
|
private String getEchoCommand() {
|
||||||
return "(echo \"$LOCAL\" \"$REMOTE\") > "
|
return "(echo \"$LOCAL\" \"$REMOTE\") > "
|
||||||
+ commandResult.getAbsolutePath();
|
+ commandResult.getAbsolutePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void assertEchoCommandHasCorrectOutput() throws IOException {
|
||||||
|
List<String> actualLines = Files.readAllLines(commandResult.toPath());
|
||||||
|
String actualContent = String.join(System.lineSeparator(), actualLines);
|
||||||
|
actualLines = Arrays.asList(actualContent.split(" "));
|
||||||
|
List<String> expectedLines = Arrays.asList(localFile.getAbsolutePath(),
|
||||||
|
remoteFile.getAbsolutePath());
|
||||||
|
assertEquals("Dummy test tool called with unexpected arguments",
|
||||||
|
expectedLines, actualLines);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,22 +9,30 @@
|
||||||
*/
|
*/
|
||||||
package org.eclipse.jgit.internal.diffmergetool;
|
package org.eclipse.jgit.internal.diffmergetool;
|
||||||
|
|
||||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_MERGETOOL_SECTION;
|
|
||||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_MERGE_SECTION;
|
|
||||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_CMD;
|
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_GUITOOL;
|
||||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PATH;
|
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_PROMPT;
|
||||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_TOOL;
|
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.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_TRUST_EXIT_CODE;
|
||||||
|
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_MERGETOOL_SECTION;
|
||||||
|
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_MERGE_SECTION;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
import static org.junit.Assume.assumeTrue;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.eclipse.jgit.lib.internal.BooleanTriState;
|
import org.eclipse.jgit.lib.internal.BooleanTriState;
|
||||||
|
@ -50,12 +58,7 @@ public void testUserToolWithError() throws Exception {
|
||||||
config.setString(CONFIG_MERGETOOL_SECTION, toolName,
|
config.setString(CONFIG_MERGETOOL_SECTION, toolName,
|
||||||
CONFIG_KEY_TRUST_EXIT_CODE, String.valueOf(Boolean.TRUE));
|
CONFIG_KEY_TRUST_EXIT_CODE, String.valueOf(Boolean.TRUE));
|
||||||
|
|
||||||
MergeTools manager = new MergeTools(db);
|
invokeMerge(toolName);
|
||||||
|
|
||||||
BooleanTriState prompt = BooleanTriState.UNSET;
|
|
||||||
BooleanTriState gui = BooleanTriState.UNSET;
|
|
||||||
|
|
||||||
manager.merge(local, remote, merged, base, null, toolName, prompt, gui);
|
|
||||||
|
|
||||||
fail("Expected exception to be thrown due to external tool exiting with error code: "
|
fail("Expected exception to be thrown due to external tool exiting with error code: "
|
||||||
+ errorReturnCode);
|
+ errorReturnCode);
|
||||||
|
@ -72,31 +75,112 @@ public void testUserToolWithCommandNotFoundError() throws Exception {
|
||||||
config.setString(CONFIG_MERGETOOL_SECTION, toolName, CONFIG_KEY_CMD,
|
config.setString(CONFIG_MERGETOOL_SECTION, toolName, CONFIG_KEY_CMD,
|
||||||
command);
|
command);
|
||||||
|
|
||||||
MergeTools manager = new MergeTools(db);
|
invokeMerge(toolName);
|
||||||
|
|
||||||
BooleanTriState prompt = BooleanTriState.UNSET;
|
|
||||||
BooleanTriState gui = BooleanTriState.UNSET;
|
|
||||||
|
|
||||||
manager.merge(local, remote, merged, base, null, toolName, prompt, gui);
|
|
||||||
|
|
||||||
fail("Expected exception to be thrown due to external tool exiting with error code: "
|
fail("Expected exception to be thrown due to external tool exiting with error code: "
|
||||||
+ errorReturnCode);
|
+ errorReturnCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testToolNames() {
|
public void testKdiff3() throws Exception {
|
||||||
|
assumePosixPlatform();
|
||||||
|
|
||||||
|
CommandLineMergeTool autoMergingTool = CommandLineMergeTool.kdiff3;
|
||||||
|
assumeMergeToolIsAvailable(autoMergingTool);
|
||||||
|
|
||||||
|
CommandLineMergeTool tool = autoMergingTool;
|
||||||
|
PreDefinedMergeTool externalTool = new PreDefinedMergeTool(tool.name(),
|
||||||
|
tool.getPath(), tool.getParameters(true),
|
||||||
|
tool.getParameters(false),
|
||||||
|
tool.isExitCodeTrustable() ? BooleanTriState.TRUE
|
||||||
|
: BooleanTriState.FALSE);
|
||||||
|
|
||||||
MergeTools manager = new MergeTools(db);
|
MergeTools manager = new MergeTools(db);
|
||||||
Set<String> actualToolNames = manager.getToolNames();
|
ExecutionResult result = manager.merge(local, remote, merged, null,
|
||||||
Set<String> expectedToolNames = Collections.emptySet();
|
null, externalTool);
|
||||||
assertEquals("Incorrect set of external merge tool names",
|
assertEquals("Expected merge tool to succeed", 0, result.getRc());
|
||||||
expectedToolNames, actualToolNames);
|
|
||||||
|
List<String> actualLines = Files.readAllLines(mergedFile.toPath());
|
||||||
|
String actualMergeResult = String.join(System.lineSeparator(),
|
||||||
|
actualLines);
|
||||||
|
String expectedMergeResult = DEFAULT_CONTENT;
|
||||||
|
assertEquals(
|
||||||
|
"Failed to merge equal local and remote versions with pre-defined tool: "
|
||||||
|
+ tool.getPath(),
|
||||||
|
expectedMergeResult, actualMergeResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUserDefinedTool() throws Exception {
|
||||||
|
String customToolName = "customTool";
|
||||||
|
String command = getEchoCommand();
|
||||||
|
|
||||||
|
FileBasedConfig config = db.getConfig();
|
||||||
|
config.setString(CONFIG_MERGETOOL_SECTION, customToolName,
|
||||||
|
CONFIG_KEY_CMD, command);
|
||||||
|
|
||||||
|
MergeTools manager = new MergeTools(db);
|
||||||
|
Map<String, ExternalMergeTool> tools = manager.getUserDefinedTools();
|
||||||
|
ExternalMergeTool externalTool = tools.get(customToolName);
|
||||||
|
manager.merge(local, remote, merged, base, null, externalTool);
|
||||||
|
|
||||||
|
assertEchoCommandHasCorrectOutput();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUserDefinedToolWithPrompt() throws Exception {
|
||||||
|
String customToolName = "customTool";
|
||||||
|
String command = getEchoCommand();
|
||||||
|
|
||||||
|
FileBasedConfig config = db.getConfig();
|
||||||
|
config.setString(CONFIG_MERGETOOL_SECTION, customToolName,
|
||||||
|
CONFIG_KEY_CMD, command);
|
||||||
|
|
||||||
|
MergeTools manager = new MergeTools(db);
|
||||||
|
|
||||||
|
PromptHandler promptHandler = PromptHandler.acceptPrompt();
|
||||||
|
MissingToolHandler noToolHandler = new MissingToolHandler();
|
||||||
|
|
||||||
|
manager.merge(local, remote, merged, base, null,
|
||||||
|
Optional.of(customToolName), BooleanTriState.TRUE, false,
|
||||||
|
promptHandler, noToolHandler);
|
||||||
|
|
||||||
|
assertEchoCommandHasCorrectOutput();
|
||||||
|
|
||||||
|
List<String> actualToolPrompts = promptHandler.toolPrompts;
|
||||||
|
List<String> expectedToolPrompts = Arrays.asList("customTool");
|
||||||
|
assertEquals("Expected a user prompt for custom tool call",
|
||||||
|
expectedToolPrompts, actualToolPrompts);
|
||||||
|
|
||||||
|
assertEquals("Expected to no informing about missing tools",
|
||||||
|
Collections.EMPTY_LIST, noToolHandler.missingTools);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUserDefinedToolWithCancelledPrompt() throws Exception {
|
||||||
|
MergeTools manager = new MergeTools(db);
|
||||||
|
|
||||||
|
PromptHandler promptHandler = PromptHandler.cancelPrompt();
|
||||||
|
MissingToolHandler noToolHandler = new MissingToolHandler();
|
||||||
|
|
||||||
|
Optional<ExecutionResult> result = manager.merge(local, remote, merged,
|
||||||
|
base, null, Optional.empty(), BooleanTriState.TRUE, false,
|
||||||
|
promptHandler, noToolHandler);
|
||||||
|
assertFalse("Expected no result if user cancels the operation",
|
||||||
|
result.isPresent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAllTools() {
|
public void testAllTools() {
|
||||||
|
FileBasedConfig config = db.getConfig();
|
||||||
|
String customToolName = "customTool";
|
||||||
|
config.setString(CONFIG_MERGETOOL_SECTION, customToolName,
|
||||||
|
CONFIG_KEY_CMD, "echo");
|
||||||
|
|
||||||
MergeTools manager = new MergeTools(db);
|
MergeTools manager = new MergeTools(db);
|
||||||
Set<String> actualToolNames = manager.getAvailableTools().keySet();
|
Set<String> actualToolNames = manager.getAllToolNames();
|
||||||
Set<String> expectedToolNames = new LinkedHashSet<>();
|
Set<String> expectedToolNames = new LinkedHashSet<>();
|
||||||
|
expectedToolNames.add(customToolName);
|
||||||
CommandLineMergeTool[] defaultTools = CommandLineMergeTool.values();
|
CommandLineMergeTool[] defaultTools = CommandLineMergeTool.values();
|
||||||
for (CommandLineMergeTool defaultTool : defaultTools) {
|
for (CommandLineMergeTool defaultTool : defaultTools) {
|
||||||
String toolName = defaultTool.name();
|
String toolName = defaultTool.name();
|
||||||
|
@ -150,15 +234,6 @@ public void testUserDefinedTools() {
|
||||||
actualToolNames);
|
actualToolNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testNotAvailableTools() {
|
|
||||||
MergeTools manager = new MergeTools(db);
|
|
||||||
Set<String> actualToolNames = manager.getNotAvailableTools().keySet();
|
|
||||||
Set<String> expectedToolNames = Collections.emptySet();
|
|
||||||
assertEquals("Incorrect set of not available external merge tools",
|
|
||||||
expectedToolNames, actualToolNames);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCompare() throws ToolException {
|
public void testCompare() throws ToolException {
|
||||||
String toolName = "customTool";
|
String toolName = "customTool";
|
||||||
|
@ -174,16 +249,12 @@ public void testCompare() throws ToolException {
|
||||||
config.setString(CONFIG_MERGETOOL_SECTION, toolName, CONFIG_KEY_CMD,
|
config.setString(CONFIG_MERGETOOL_SECTION, toolName, CONFIG_KEY_CMD,
|
||||||
command);
|
command);
|
||||||
|
|
||||||
BooleanTriState prompt = BooleanTriState.UNSET;
|
Optional<ExecutionResult> result = invokeMerge(toolName);
|
||||||
BooleanTriState gui = BooleanTriState.UNSET;
|
assertTrue("Expected external merge tool result to be available",
|
||||||
|
result.isPresent());
|
||||||
MergeTools manager = new MergeTools(db);
|
|
||||||
|
|
||||||
int expectedCompareResult = 0;
|
int expectedCompareResult = 0;
|
||||||
ExecutionResult compareResult = manager.merge(local, remote, merged,
|
|
||||||
base, null, toolName, prompt, gui);
|
|
||||||
assertEquals("Incorrect compare result for external merge tool",
|
assertEquals("Incorrect compare result for external merge tool",
|
||||||
expectedCompareResult, compareResult.getRc());
|
expectedCompareResult, result.get().getRc());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -198,17 +269,16 @@ public void testDefaultTool() throws Exception {
|
||||||
toolName);
|
toolName);
|
||||||
|
|
||||||
MergeTools manager = new MergeTools(db);
|
MergeTools manager = new MergeTools(db);
|
||||||
BooleanTriState gui = BooleanTriState.UNSET;
|
boolean gui = false;
|
||||||
String defaultToolName = manager.getDefaultToolName(gui);
|
String defaultToolName = manager.getDefaultToolName(gui);
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"Expected configured mergetool to be the default external merge tool",
|
"Expected configured mergetool to be the default external merge tool",
|
||||||
toolName, defaultToolName);
|
toolName, defaultToolName);
|
||||||
|
|
||||||
gui = BooleanTriState.TRUE;
|
gui = true;
|
||||||
String defaultGuiToolName = manager.getDefaultToolName(gui);
|
String defaultGuiToolName = manager.getDefaultToolName(gui);
|
||||||
assertEquals(
|
assertNull("Expected default mergetool to not be set",
|
||||||
"Expected configured mergetool to be the default external merge tool",
|
defaultGuiToolName);
|
||||||
"my_gui_tool", defaultGuiToolName);
|
|
||||||
|
|
||||||
config.setString(CONFIG_MERGE_SECTION, subsection, CONFIG_KEY_GUITOOL,
|
config.setString(CONFIG_MERGE_SECTION, subsection, CONFIG_KEY_GUITOOL,
|
||||||
guiToolName);
|
guiToolName);
|
||||||
|
@ -216,7 +286,7 @@ public void testDefaultTool() throws Exception {
|
||||||
defaultGuiToolName = manager.getDefaultToolName(gui);
|
defaultGuiToolName = manager.getDefaultToolName(gui);
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"Expected configured mergetool to be the default external merge guitool",
|
"Expected configured mergetool to be the default external merge guitool",
|
||||||
"my_gui_tool", defaultGuiToolName);
|
guiToolName, defaultGuiToolName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -236,7 +306,7 @@ public void testOverridePreDefinedToolPath() {
|
||||||
|
|
||||||
MergeTools manager = new MergeTools(db);
|
MergeTools manager = new MergeTools(db);
|
||||||
Map<String, ExternalMergeTool> availableTools = manager
|
Map<String, ExternalMergeTool> availableTools = manager
|
||||||
.getAvailableTools();
|
.getPredefinedTools(true);
|
||||||
ExternalMergeTool externalMergeTool = availableTools
|
ExternalMergeTool externalMergeTool = availableTools
|
||||||
.get(overridenToolName);
|
.get(overridenToolName);
|
||||||
String actualMergeToolPath = externalMergeTool.getPath();
|
String actualMergeToolPath = externalMergeTool.getPath();
|
||||||
|
@ -254,18 +324,110 @@ public void testOverridePreDefinedToolPath() {
|
||||||
|
|
||||||
@Test(expected = ToolException.class)
|
@Test(expected = ToolException.class)
|
||||||
public void testUndefinedTool() throws Exception {
|
public void testUndefinedTool() throws Exception {
|
||||||
MergeTools manager = new MergeTools(db);
|
|
||||||
|
|
||||||
String toolName = "undefined";
|
String toolName = "undefined";
|
||||||
BooleanTriState prompt = BooleanTriState.UNSET;
|
invokeMerge(toolName);
|
||||||
BooleanTriState gui = BooleanTriState.UNSET;
|
|
||||||
|
|
||||||
manager.merge(local, remote, merged, base, null, toolName, prompt, gui);
|
|
||||||
fail("Expected exception to be thrown due to not defined external merge tool");
|
fail("Expected exception to be thrown due to not defined external merge tool");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDefaultToolExecutionWithPrompt() throws Exception {
|
||||||
|
FileBasedConfig config = db.getConfig();
|
||||||
|
// the default diff tool is configured without a subsection
|
||||||
|
String subsection = null;
|
||||||
|
config.setString("merge", subsection, "tool", "customTool");
|
||||||
|
|
||||||
|
String command = getEchoCommand();
|
||||||
|
|
||||||
|
config.setString("mergetool", "customTool", "cmd", command);
|
||||||
|
|
||||||
|
MergeTools manager = new MergeTools(db);
|
||||||
|
|
||||||
|
PromptHandler promptHandler = PromptHandler.acceptPrompt();
|
||||||
|
MissingToolHandler noToolHandler = new MissingToolHandler();
|
||||||
|
|
||||||
|
manager.merge(local, remote, merged, base, null, Optional.empty(),
|
||||||
|
BooleanTriState.TRUE, false, promptHandler, noToolHandler);
|
||||||
|
|
||||||
|
assertEchoCommandHasCorrectOutput();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoDefaultToolName() {
|
||||||
|
MergeTools manager = new MergeTools(db);
|
||||||
|
boolean gui = false;
|
||||||
|
String defaultToolName = manager.getDefaultToolName(gui);
|
||||||
|
assertNull("Expected no default tool when none is configured",
|
||||||
|
defaultToolName);
|
||||||
|
|
||||||
|
gui = true;
|
||||||
|
defaultToolName = manager.getDefaultToolName(gui);
|
||||||
|
assertNull("Expected no default tool when none is configured",
|
||||||
|
defaultToolName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = ToolException.class)
|
||||||
|
public void testNullTool() throws Exception {
|
||||||
|
MergeTools manager = new MergeTools(db);
|
||||||
|
|
||||||
|
PromptHandler promptHandler = null;
|
||||||
|
MissingToolHandler noToolHandler = null;
|
||||||
|
|
||||||
|
Optional<String> tool = null;
|
||||||
|
|
||||||
|
manager.merge(local, remote, merged, base, null, tool,
|
||||||
|
BooleanTriState.TRUE, false, promptHandler, noToolHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = ToolException.class)
|
||||||
|
public void testNullToolWithPrompt() throws Exception {
|
||||||
|
MergeTools manager = new MergeTools(db);
|
||||||
|
|
||||||
|
PromptHandler promptHandler = PromptHandler.cancelPrompt();
|
||||||
|
MissingToolHandler noToolHandler = new MissingToolHandler();
|
||||||
|
|
||||||
|
Optional<String> tool = null;
|
||||||
|
|
||||||
|
manager.merge(local, remote, merged, base, null, tool,
|
||||||
|
BooleanTriState.TRUE, false, promptHandler, noToolHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<ExecutionResult> invokeMerge(String toolName)
|
||||||
|
throws ToolException {
|
||||||
|
BooleanTriState prompt = BooleanTriState.UNSET;
|
||||||
|
boolean gui = false;
|
||||||
|
|
||||||
|
MergeTools manager = new MergeTools(db);
|
||||||
|
|
||||||
|
PromptHandler promptHandler = PromptHandler.acceptPrompt();
|
||||||
|
MissingToolHandler noToolHandler = new MissingToolHandler();
|
||||||
|
|
||||||
|
Optional<ExecutionResult> result = manager.merge(local, remote, merged,
|
||||||
|
base, null, Optional.of(toolName), prompt, gui, promptHandler,
|
||||||
|
noToolHandler);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assumeMergeToolIsAvailable(
|
||||||
|
CommandLineMergeTool autoMergingTool) {
|
||||||
|
boolean isAvailable = ExternalToolUtils.isToolAvailable(db.getFS(),
|
||||||
|
db.getDirectory(), db.getWorkTree(), autoMergingTool.getPath());
|
||||||
|
assumeTrue("Assuming external tool is available: "
|
||||||
|
+ autoMergingTool.name(), isAvailable);
|
||||||
|
}
|
||||||
|
|
||||||
private String getEchoCommand() {
|
private String getEchoCommand() {
|
||||||
return "(echo \"$LOCAL\" \"$REMOTE\") > "
|
return "(echo $LOCAL $REMOTE $MERGED $BASE) > "
|
||||||
+ commandResult.getAbsolutePath();
|
+ commandResult.getAbsolutePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void assertEchoCommandHasCorrectOutput() throws IOException {
|
||||||
|
List<String> actualLines = Files.readAllLines(commandResult.toPath());
|
||||||
|
String actualContent = String.join(System.lineSeparator(), actualLines);
|
||||||
|
actualLines = Arrays.asList(actualContent.split(" "));
|
||||||
|
List<String> expectedLines = Arrays.asList(localFile.getAbsolutePath(),
|
||||||
|
remoteFile.getAbsolutePath(), mergedFile.getAbsolutePath(),
|
||||||
|
baseFile.getAbsolutePath());
|
||||||
|
assertEquals("Dummy test tool called with unexpected arguments",
|
||||||
|
expectedLines, actualLines);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.eclipse.jgit.junit.RepositoryTestCase;
|
import org.eclipse.jgit.junit.RepositoryTestCase;
|
||||||
import org.eclipse.jgit.util.FS;
|
import org.eclipse.jgit.util.FS;
|
||||||
|
@ -88,4 +90,39 @@ protected static void assumePosixPlatform() {
|
||||||
"This test can run only in Linux tests",
|
"This test can run only in Linux tests",
|
||||||
FS.DETECTED instanceof FS_POSIX);
|
FS.DETECTED instanceof FS_POSIX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static class PromptHandler implements PromptContinueHandler {
|
||||||
|
|
||||||
|
private final boolean promptResult;
|
||||||
|
|
||||||
|
final List<String> toolPrompts = new ArrayList<>();
|
||||||
|
|
||||||
|
private PromptHandler(boolean promptResult) {
|
||||||
|
this.promptResult = promptResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PromptHandler acceptPrompt() {
|
||||||
|
return new PromptHandler(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PromptHandler cancelPrompt() {
|
||||||
|
return new PromptHandler(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean prompt(String toolName) {
|
||||||
|
toolPrompts.add(toolName);
|
||||||
|
return promptResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class MissingToolHandler implements InformNoToolHandler {
|
||||||
|
|
||||||
|
final List<String> missingTools = new ArrayList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void inform(List<String> toolNames) {
|
||||||
|
missingTools.addAll(toolNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,8 @@ Export-Package: org.eclipse.jgit.annotations;version="6.2.0",
|
||||||
org.eclipse.jgit.internal.diffmergetool;version="6.2.0";
|
org.eclipse.jgit.internal.diffmergetool;version="6.2.0";
|
||||||
x-friends:="org.eclipse.jgit.test,
|
x-friends:="org.eclipse.jgit.test,
|
||||||
org.eclipse.jgit.pgm.test,
|
org.eclipse.jgit.pgm.test,
|
||||||
org.eclipse.jgit.pgm",
|
org.eclipse.jgit.pgm,
|
||||||
|
org.eclipse.egit.ui",
|
||||||
org.eclipse.jgit.internal.fsck;version="6.2.0";
|
org.eclipse.jgit.internal.fsck;version="6.2.0";
|
||||||
x-friends:="org.eclipse.jgit.test",
|
x-friends:="org.eclipse.jgit.test",
|
||||||
org.eclipse.jgit.internal.revwalk;version="6.2.0";
|
org.eclipse.jgit.internal.revwalk;version="6.2.0";
|
||||||
|
@ -133,7 +134,8 @@ Export-Package: org.eclipse.jgit.annotations;version="6.2.0",
|
||||||
org.eclipse.jgit.util.time",
|
org.eclipse.jgit.util.time",
|
||||||
org.eclipse.jgit.lib.internal;version="6.2.0";
|
org.eclipse.jgit.lib.internal;version="6.2.0";
|
||||||
x-friends:="org.eclipse.jgit.test,
|
x-friends:="org.eclipse.jgit.test,
|
||||||
org.eclipse.jgit.pgm",
|
org.eclipse.jgit.pgm,
|
||||||
|
org.eclipse.egit.ui",
|
||||||
org.eclipse.jgit.logging;version="6.2.0",
|
org.eclipse.jgit.logging;version="6.2.0",
|
||||||
org.eclipse.jgit.merge;version="6.2.0";
|
org.eclipse.jgit.merge;version="6.2.0";
|
||||||
uses:="org.eclipse.jgit.dircache,
|
uses:="org.eclipse.jgit.dircache,
|
||||||
|
|
|
@ -237,6 +237,9 @@ deleteTagUnexpectedResult=Delete tag returned unexpected result {0}
|
||||||
deletingNotSupported=Deleting {0} not supported.
|
deletingNotSupported=Deleting {0} not supported.
|
||||||
destinationIsNotAWildcard=Destination is not a wildcard.
|
destinationIsNotAWildcard=Destination is not a wildcard.
|
||||||
detachedHeadDetected=HEAD is detached
|
detachedHeadDetected=HEAD is detached
|
||||||
|
diffToolNotGivenError=No diff tool provided and no defaults configured.
|
||||||
|
diffToolNotSpecifiedInGitAttributesError=Diff tool specified in git attributes cannot be found.
|
||||||
|
diffToolNullError=Parameter for diff tool cannot be null.
|
||||||
dirCacheDoesNotHaveABackingFile=DirCache does not have a backing file
|
dirCacheDoesNotHaveABackingFile=DirCache does not have a backing file
|
||||||
dirCacheFileIsNotLocked=DirCache {0} not locked
|
dirCacheFileIsNotLocked=DirCache {0} not locked
|
||||||
dirCacheIsNotLocked=DirCache is not locked
|
dirCacheIsNotLocked=DirCache is not locked
|
||||||
|
@ -457,6 +460,8 @@ mergeStrategyDoesNotSupportHeads=merge strategy {0} does not support {1} heads t
|
||||||
mergeUsingStrategyResultedInDescription=Merge of revisions {0} with base {1} using strategy {2} resulted in: {3}. {4}
|
mergeUsingStrategyResultedInDescription=Merge of revisions {0} with base {1} using strategy {2} resulted in: {3}. {4}
|
||||||
mergeRecursiveConflictsWhenMergingCommonAncestors=Multiple common ancestors were found and merging them resulted in a conflict: {0}, {1}
|
mergeRecursiveConflictsWhenMergingCommonAncestors=Multiple common ancestors were found and merging them resulted in a conflict: {0}, {1}
|
||||||
mergeRecursiveTooManyMergeBasesFor = "More than {0} merge bases for:\n a {1}\n b {2} found:\n count {3}"
|
mergeRecursiveTooManyMergeBasesFor = "More than {0} merge bases for:\n a {1}\n b {2} found:\n count {3}"
|
||||||
|
mergeToolNotGivenError=No merge tool provided and no defaults configured.
|
||||||
|
mergeToolNullError=Parameter for merge tool cannot be null.
|
||||||
messageAndTaggerNotAllowedInUnannotatedTags = Unannotated tags cannot have a message or tagger
|
messageAndTaggerNotAllowedInUnannotatedTags = Unannotated tags cannot have a message or tagger
|
||||||
minutesAgo={0} minutes ago
|
minutesAgo={0} minutes ago
|
||||||
mismatchOffset=mismatch offset for object {0}
|
mismatchOffset=mismatch offset for object {0}
|
||||||
|
|
|
@ -265,6 +265,9 @@ public static JGitText get() {
|
||||||
/***/ public String deletingNotSupported;
|
/***/ public String deletingNotSupported;
|
||||||
/***/ public String destinationIsNotAWildcard;
|
/***/ public String destinationIsNotAWildcard;
|
||||||
/***/ public String detachedHeadDetected;
|
/***/ public String detachedHeadDetected;
|
||||||
|
/***/ public String diffToolNotGivenError;
|
||||||
|
/***/ public String diffToolNotSpecifiedInGitAttributesError;
|
||||||
|
/***/ public String diffToolNullError;
|
||||||
/***/ public String dirCacheDoesNotHaveABackingFile;
|
/***/ public String dirCacheDoesNotHaveABackingFile;
|
||||||
/***/ public String dirCacheFileIsNotLocked;
|
/***/ public String dirCacheFileIsNotLocked;
|
||||||
/***/ public String dirCacheIsNotLocked;
|
/***/ public String dirCacheIsNotLocked;
|
||||||
|
@ -485,6 +488,8 @@ public static JGitText get() {
|
||||||
/***/ public String mergeUsingStrategyResultedInDescription;
|
/***/ public String mergeUsingStrategyResultedInDescription;
|
||||||
/***/ public String mergeRecursiveConflictsWhenMergingCommonAncestors;
|
/***/ public String mergeRecursiveConflictsWhenMergingCommonAncestors;
|
||||||
/***/ public String mergeRecursiveTooManyMergeBasesFor;
|
/***/ public String mergeRecursiveTooManyMergeBasesFor;
|
||||||
|
/***/ public String mergeToolNotGivenError;
|
||||||
|
/***/ public String mergeToolNullError;
|
||||||
/***/ public String messageAndTaggerNotAllowedInUnannotatedTags;
|
/***/ public String messageAndTaggerNotAllowedInUnannotatedTags;
|
||||||
/***/ public String minutesAgo;
|
/***/ public String minutesAgo;
|
||||||
/***/ public String mismatchOffset;
|
/***/ public String mismatchOffset;
|
||||||
|
|
|
@ -14,13 +14,19 @@
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
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.Arrays;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.errors.NoWorkTreeException;
|
||||||
import org.eclipse.jgit.util.FS;
|
import org.eclipse.jgit.util.FS;
|
||||||
import org.eclipse.jgit.util.FS.ExecutionResult;
|
import org.eclipse.jgit.util.FS.ExecutionResult;
|
||||||
import org.eclipse.jgit.util.FS_POSIX;
|
import org.eclipse.jgit.util.FS_POSIX;
|
||||||
import org.eclipse.jgit.util.FS_Win32;
|
import org.eclipse.jgit.util.FS_Win32;
|
||||||
import org.eclipse.jgit.util.FS_Win32_Cygwin;
|
import org.eclipse.jgit.util.FS_Win32_Cygwin;
|
||||||
|
import org.eclipse.jgit.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs a command with help of FS.
|
* Runs a command with help of FS.
|
||||||
|
@ -91,6 +97,49 @@ public ExecutionResult run(String command, File workingDir,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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() {
|
private void deleteCommandArray() {
|
||||||
deleteCommandFile();
|
deleteCommandFile();
|
||||||
}
|
}
|
||||||
|
@ -127,7 +176,7 @@ private String[] createCommandArray(String command)
|
||||||
private void checkUseMsys2(String command) {
|
private void checkUseMsys2(String command) {
|
||||||
useMsys2 = false;
|
useMsys2 = false;
|
||||||
String useMsys2Str = System.getProperty("jgit.usemsys2bash"); //$NON-NLS-1$
|
String useMsys2Str = System.getProperty("jgit.usemsys2bash"); //$NON-NLS-1$
|
||||||
if (useMsys2Str != null && !useMsys2Str.isEmpty()) {
|
if (!StringUtils.isEmptyOrNull(useMsys2Str)) {
|
||||||
if (useMsys2Str.equalsIgnoreCase("auto")) { //$NON-NLS-1$
|
if (useMsys2Str.equalsIgnoreCase("auto")) { //$NON-NLS-1$
|
||||||
useMsys2 = command.contains(".sh"); //$NON-NLS-1$
|
useMsys2 = command.contains(".sh"); //$NON-NLS-1$
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -111,7 +111,7 @@ public enum CommandLineDiffTool {
|
||||||
* See: <a href=
|
* See: <a href=
|
||||||
* "http://vimdoc.sourceforge.net/htmldoc/diff.html">http://vimdoc.sourceforge.net/htmldoc/diff.html</a>
|
* "http://vimdoc.sourceforge.net/htmldoc/diff.html">http://vimdoc.sourceforge.net/htmldoc/diff.html</a>
|
||||||
*/
|
*/
|
||||||
gvimdiff("gviewdiff", "\"$LOCAL\" \"$REMOTE\""),
|
gvimdiff("gvimdiff", "\"$LOCAL\" \"$REMOTE\""),
|
||||||
/**
|
/**
|
||||||
* See: <a href=
|
* See: <a href=
|
||||||
* "http://vimdoc.sourceforge.net/htmldoc/diff.html">http://vimdoc.sourceforge.net/htmldoc/diff.html</a>
|
* "http://vimdoc.sourceforge.net/htmldoc/diff.html">http://vimdoc.sourceforge.net/htmldoc/diff.html</a>
|
||||||
|
@ -160,7 +160,7 @@ public enum CommandLineDiffTool {
|
||||||
* See: <a href=
|
* See: <a href=
|
||||||
* "http://vimdoc.sourceforge.net/htmldoc/diff.html">http://vimdoc.sourceforge.net/htmldoc/diff.html</a>
|
* "http://vimdoc.sourceforge.net/htmldoc/diff.html">http://vimdoc.sourceforge.net/htmldoc/diff.html</a>
|
||||||
*/
|
*/
|
||||||
vimdiff("viewdiff", gvimdiff),
|
vimdiff("vimdiff", gvimdiff),
|
||||||
/**
|
/**
|
||||||
* See: <a href=
|
* See: <a href=
|
||||||
* "http://vimdoc.sourceforge.net/htmldoc/diff.html">http://vimdoc.sourceforge.net/htmldoc/diff.html</a>
|
* "http://vimdoc.sourceforge.net/htmldoc/diff.html">http://vimdoc.sourceforge.net/htmldoc/diff.html</a>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2018-2022, Andre Bossert <andre.bossert@siemens.com>
|
* Copyright (C) 2018-2022, Andre Bossert <andre.bossert@siemens.com>
|
||||||
|
* Copyright (C) 2019, Tim Neumann <tim.neumann@advantest.com>
|
||||||
*
|
*
|
||||||
* This program and the accompanying materials are made available under the
|
* This program and the accompanying materials are made available under the
|
||||||
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
||||||
|
@ -10,14 +11,22 @@
|
||||||
|
|
||||||
package org.eclipse.jgit.internal.diffmergetool;
|
package org.eclipse.jgit.internal.diffmergetool;
|
||||||
|
|
||||||
import java.util.TreeMap;
|
import java.io.File;
|
||||||
import java.util.Collections;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.internal.JGitText;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.lib.StoredConfig;
|
||||||
import org.eclipse.jgit.lib.internal.BooleanTriState;
|
import org.eclipse.jgit.lib.internal.BooleanTriState;
|
||||||
|
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||||
|
import org.eclipse.jgit.util.FS;
|
||||||
import org.eclipse.jgit.util.FS.ExecutionResult;
|
import org.eclipse.jgit.util.FS.ExecutionResult;
|
||||||
import org.eclipse.jgit.util.StringUtils;
|
import org.eclipse.jgit.util.StringUtils;
|
||||||
|
|
||||||
|
@ -26,10 +35,16 @@
|
||||||
*/
|
*/
|
||||||
public class DiffTools {
|
public class DiffTools {
|
||||||
|
|
||||||
private final Repository repo;
|
private final FS fs;
|
||||||
|
|
||||||
|
private final File gitDir;
|
||||||
|
|
||||||
|
private final File workTree;
|
||||||
|
|
||||||
private final DiffToolConfig config;
|
private final DiffToolConfig config;
|
||||||
|
|
||||||
|
private final Repository repo;
|
||||||
|
|
||||||
private final Map<String, ExternalDiffTool> predefinedTools;
|
private final Map<String, ExternalDiffTool> predefinedTools;
|
||||||
|
|
||||||
private final Map<String, ExternalDiffTool> userDefinedTools;
|
private final Map<String, ExternalDiffTool> userDefinedTools;
|
||||||
|
@ -41,10 +56,107 @@ public class DiffTools {
|
||||||
* the repository
|
* the repository
|
||||||
*/
|
*/
|
||||||
public DiffTools(Repository repo) {
|
public DiffTools(Repository repo) {
|
||||||
|
this(repo, repo.getConfig());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the external merge-tools manager for given configuration.
|
||||||
|
*
|
||||||
|
* @param config
|
||||||
|
* the git configuration
|
||||||
|
*/
|
||||||
|
public DiffTools(StoredConfig config) {
|
||||||
|
this(null, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DiffTools(Repository repo, StoredConfig config) {
|
||||||
this.repo = repo;
|
this.repo = repo;
|
||||||
config = repo.getConfig().get(DiffToolConfig.KEY);
|
this.config = config.get(DiffToolConfig.KEY);
|
||||||
|
this.gitDir = repo == null ? null : repo.getDirectory();
|
||||||
|
this.fs = repo == null ? FS.DETECTED : repo.getFS();
|
||||||
|
this.workTree = repo == null ? null : repo.getWorkTree();
|
||||||
predefinedTools = setupPredefinedTools();
|
predefinedTools = setupPredefinedTools();
|
||||||
userDefinedTools = setupUserDefinedTools(config, predefinedTools);
|
userDefinedTools = setupUserDefinedTools(predefinedTools);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare two versions of a file.
|
||||||
|
*
|
||||||
|
* @param localFile
|
||||||
|
* The local/left version of the file.
|
||||||
|
* @param remoteFile
|
||||||
|
* The remote/right version of the file.
|
||||||
|
* @param toolName
|
||||||
|
* Optionally the name of the tool to use. If not given the
|
||||||
|
* default tool will be used.
|
||||||
|
* @param prompt
|
||||||
|
* Optionally a flag whether to prompt the user before compare.
|
||||||
|
* If not given the default will be used.
|
||||||
|
* @param gui
|
||||||
|
* A flag whether to prefer a gui tool.
|
||||||
|
* @param trustExitCode
|
||||||
|
* Optionally a flag whether to trust the exit code of the tool.
|
||||||
|
* If not given the default will be used.
|
||||||
|
* @param promptHandler
|
||||||
|
* The handler to use when needing to prompt the user if he wants
|
||||||
|
* to continue.
|
||||||
|
* @param noToolHandler
|
||||||
|
* The handler to use when needing to inform the user, that no
|
||||||
|
* tool is configured.
|
||||||
|
* @return the optioanl result of executing the tool if it was executed
|
||||||
|
* @throws ToolException
|
||||||
|
* when the tool fails
|
||||||
|
*/
|
||||||
|
public Optional<ExecutionResult> compare(FileElement localFile,
|
||||||
|
FileElement remoteFile, Optional<String> toolName,
|
||||||
|
BooleanTriState prompt, boolean gui, BooleanTriState trustExitCode,
|
||||||
|
PromptContinueHandler promptHandler,
|
||||||
|
InformNoToolHandler noToolHandler) throws ToolException {
|
||||||
|
|
||||||
|
String toolNameToUse;
|
||||||
|
|
||||||
|
if (toolName == null) {
|
||||||
|
throw new ToolException(JGitText.get().diffToolNullError);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toolName.isPresent()) {
|
||||||
|
toolNameToUse = toolName.get();
|
||||||
|
} else {
|
||||||
|
toolNameToUse = getDefaultToolName(gui);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isEmptyOrNull(toolNameToUse)) {
|
||||||
|
throw new ToolException(JGitText.get().diffToolNotGivenError);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean doPrompt;
|
||||||
|
if (prompt != BooleanTriState.UNSET) {
|
||||||
|
doPrompt = prompt == BooleanTriState.TRUE;
|
||||||
|
} else {
|
||||||
|
doPrompt = isInteractive();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doPrompt) {
|
||||||
|
if (!promptHandler.prompt(toolNameToUse)) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean trust;
|
||||||
|
if (trustExitCode != BooleanTriState.UNSET) {
|
||||||
|
trust = trustExitCode == BooleanTriState.TRUE;
|
||||||
|
} else {
|
||||||
|
trust = config.isTrustExitCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
ExternalDiffTool tool = getTool(toolNameToUse);
|
||||||
|
if (tool == null) {
|
||||||
|
throw new ToolException(
|
||||||
|
"External diff tool is not defined: " + toolNameToUse); //$NON-NLS-1$
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.of(
|
||||||
|
compare(localFile, remoteFile, tool, trust));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -54,56 +166,110 @@ public DiffTools(Repository repo) {
|
||||||
* the local file element
|
* the local file element
|
||||||
* @param remoteFile
|
* @param remoteFile
|
||||||
* the remote file element
|
* the remote file element
|
||||||
* @param mergedFile
|
* @param tool
|
||||||
* the merged file element, it's path equals local or remote
|
* the selected tool
|
||||||
* element path
|
|
||||||
* @param toolName
|
|
||||||
* the selected tool name (can be null)
|
|
||||||
* @param prompt
|
|
||||||
* the prompt option
|
|
||||||
* @param gui
|
|
||||||
* the GUI option
|
|
||||||
* @param trustExitCode
|
* @param trustExitCode
|
||||||
* the "trust exit code" option
|
* the "trust exit code" option
|
||||||
* @return the execution result from tool
|
* @return the execution result from tool
|
||||||
* @throws ToolException
|
* @throws ToolException
|
||||||
*/
|
*/
|
||||||
public ExecutionResult compare(FileElement localFile,
|
public ExecutionResult compare(FileElement localFile,
|
||||||
FileElement remoteFile, FileElement mergedFile, String toolName,
|
FileElement remoteFile, ExternalDiffTool tool,
|
||||||
BooleanTriState prompt, BooleanTriState gui,
|
boolean trustExitCode) throws ToolException {
|
||||||
BooleanTriState trustExitCode) throws ToolException {
|
|
||||||
try {
|
try {
|
||||||
// prepare the command (replace the file paths)
|
if (tool == null) {
|
||||||
String command = ExternalToolUtils.prepareCommand(
|
throw new ToolException(JGitText
|
||||||
guessTool(toolName, gui).getCommand(), localFile,
|
.get().diffToolNotSpecifiedInGitAttributesError);
|
||||||
remoteFile, mergedFile, null);
|
|
||||||
// prepare the environment
|
|
||||||
Map<String, String> env = ExternalToolUtils.prepareEnvironment(repo,
|
|
||||||
localFile, remoteFile, mergedFile, null);
|
|
||||||
boolean trust = config.isTrustExitCode();
|
|
||||||
if (trustExitCode != BooleanTriState.UNSET) {
|
|
||||||
trust = trustExitCode == BooleanTriState.TRUE;
|
|
||||||
}
|
}
|
||||||
|
// prepare the command (replace the file paths)
|
||||||
|
String command = ExternalToolUtils.prepareCommand(tool.getCommand(),
|
||||||
|
localFile, remoteFile, null, null);
|
||||||
|
// prepare the environment
|
||||||
|
Map<String, String> env = ExternalToolUtils.prepareEnvironment(
|
||||||
|
gitDir, localFile, remoteFile, null, null);
|
||||||
// execute the tool
|
// execute the tool
|
||||||
CommandExecutor cmdExec = new CommandExecutor(repo.getFS(), trust);
|
CommandExecutor cmdExec = new CommandExecutor(fs, trustExitCode);
|
||||||
return cmdExec.run(command, repo.getWorkTree(), env);
|
return cmdExec.run(command, workTree, env);
|
||||||
} catch (IOException | InterruptedException e) {
|
} catch (IOException | InterruptedException e) {
|
||||||
throw new ToolException(e);
|
throw new ToolException(e);
|
||||||
} finally {
|
} finally {
|
||||||
localFile.cleanTemporaries();
|
localFile.cleanTemporaries();
|
||||||
remoteFile.cleanTemporaries();
|
remoteFile.cleanTemporaries();
|
||||||
mergedFile.cleanTemporaries();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the tool names
|
* Get user defined tool names.
|
||||||
|
*
|
||||||
|
* @return the user defined tool names
|
||||||
*/
|
*/
|
||||||
public Set<String> getToolNames() {
|
public Set<String> getUserDefinedToolNames() {
|
||||||
return config.getToolNames();
|
return userDefinedTools.keySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Get predefined tool names.
|
||||||
|
*
|
||||||
|
* @return the predefined tool names
|
||||||
|
*/
|
||||||
|
public Set<String> getPredefinedToolNames() {
|
||||||
|
return predefinedTools.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all tool names.
|
||||||
|
*
|
||||||
|
* @return the all tool names (default or available tool name is the first
|
||||||
|
* in the set)
|
||||||
|
*/
|
||||||
|
public Set<String> getAllToolNames() {
|
||||||
|
String defaultName = getDefaultToolName(false);
|
||||||
|
if (defaultName == null) {
|
||||||
|
defaultName = getFirstAvailableTool();
|
||||||
|
}
|
||||||
|
return ExternalToolUtils.createSortedToolSet(defaultName,
|
||||||
|
getUserDefinedToolNames(), getPredefinedToolNames());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides {@link Optional} with the name of an external diff tool if
|
||||||
|
* specified in git configuration for a path.
|
||||||
|
*
|
||||||
|
* The formed git configuration results from global rules as well as merged
|
||||||
|
* rules from info and worktree attributes.
|
||||||
|
*
|
||||||
|
* Triggers {@link TreeWalk} until specified path found in the tree.
|
||||||
|
*
|
||||||
|
* @param path
|
||||||
|
* path to the node in repository to parse git attributes for
|
||||||
|
* @return name of the difftool if set
|
||||||
|
* @throws ToolException
|
||||||
|
*/
|
||||||
|
public Optional<String> getExternalToolFromAttributes(final String path)
|
||||||
|
throws ToolException {
|
||||||
|
return ExternalToolUtils.getExternalToolFromAttributes(repo, path,
|
||||||
|
ExternalToolUtils.KEY_DIFF_TOOL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the availability of the predefined tools in the system.
|
||||||
|
*
|
||||||
|
* @return set of predefined available tools
|
||||||
|
*/
|
||||||
|
public Set<String> getPredefinedAvailableTools() {
|
||||||
|
Map<String, ExternalDiffTool> defTools = getPredefinedTools(true);
|
||||||
|
Set<String> availableTools = new LinkedHashSet<>();
|
||||||
|
for (Entry<String, ExternalDiffTool> elem : defTools.entrySet()) {
|
||||||
|
if (elem.getValue().isAvailable()) {
|
||||||
|
availableTools.add(elem.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return availableTools;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user defined tools map.
|
||||||
|
*
|
||||||
* @return the user defined tools
|
* @return the user defined tools
|
||||||
*/
|
*/
|
||||||
public Map<String, ExternalDiffTool> getUserDefinedTools() {
|
public Map<String, ExternalDiffTool> getUserDefinedTools() {
|
||||||
|
@ -111,48 +277,70 @@ public Map<String, ExternalDiffTool> getUserDefinedTools() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the available predefined tools
|
* Get predefined tools map.
|
||||||
|
*
|
||||||
|
* @param checkAvailability
|
||||||
|
* true: for checking if tools can be executed; ATTENTION: this
|
||||||
|
* check took some time, do not execute often (store the map for
|
||||||
|
* other actions); false: availability is NOT checked:
|
||||||
|
* isAvailable() returns default false is this case!
|
||||||
|
* @return the predefined tools with optionally checked availability (long
|
||||||
|
* running operation)
|
||||||
*/
|
*/
|
||||||
public Map<String, ExternalDiffTool> getAvailableTools() {
|
public Map<String, ExternalDiffTool> getPredefinedTools(
|
||||||
|
boolean checkAvailability) {
|
||||||
|
if (checkAvailability) {
|
||||||
|
for (ExternalDiffTool tool : predefinedTools.values()) {
|
||||||
|
PreDefinedDiffTool predefTool = (PreDefinedDiffTool) tool;
|
||||||
|
predefTool.setAvailable(ExternalToolUtils.isToolAvailable(fs,
|
||||||
|
gitDir, workTree, predefTool.getPath()));
|
||||||
|
}
|
||||||
|
}
|
||||||
return Collections.unmodifiableMap(predefinedTools);
|
return Collections.unmodifiableMap(predefinedTools);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the NOT available predefined tools
|
* Get first available tool name.
|
||||||
|
*
|
||||||
|
* @return the name of first available predefined tool or null
|
||||||
*/
|
*/
|
||||||
public Map<String, ExternalDiffTool> getNotAvailableTools() {
|
public String getFirstAvailableTool() {
|
||||||
return Collections.unmodifiableMap(new TreeMap<>());
|
for (ExternalDiffTool tool : predefinedTools.values()) {
|
||||||
|
if (ExternalToolUtils.isToolAvailable(fs, gitDir, workTree,
|
||||||
|
tool.getPath())) {
|
||||||
|
return tool.getName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Get default (gui-)tool name.
|
||||||
|
*
|
||||||
* @param gui
|
* @param gui
|
||||||
* use the diff.guitool setting ?
|
* use the diff.guitool setting ?
|
||||||
* @return the default tool name
|
* @return the default tool name
|
||||||
*/
|
*/
|
||||||
public String getDefaultToolName(BooleanTriState gui) {
|
public String getDefaultToolName(boolean gui) {
|
||||||
return gui != BooleanTriState.UNSET ? "my_gui_tool" //$NON-NLS-1$
|
String guiToolName;
|
||||||
: config.getDefaultToolName();
|
if (gui) {
|
||||||
|
guiToolName = config.getDefaultGuiToolName();
|
||||||
|
if (guiToolName != null) {
|
||||||
|
return guiToolName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config.getDefaultToolName();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Is interactive diff (prompt enabled) ?
|
||||||
|
*
|
||||||
* @return is interactive (config prompt enabled) ?
|
* @return is interactive (config prompt enabled) ?
|
||||||
*/
|
*/
|
||||||
public boolean isInteractive() {
|
public boolean isInteractive() {
|
||||||
return config.isPrompt();
|
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) {
|
private ExternalDiffTool getTool(final String name) {
|
||||||
ExternalDiffTool tool = userDefinedTools.get(name);
|
ExternalDiffTool tool = userDefinedTools.get(name);
|
||||||
if (tool == null) {
|
if (tool == null) {
|
||||||
|
@ -169,10 +357,10 @@ private static Map<String, ExternalDiffTool> setupPredefinedTools() {
|
||||||
return tools;
|
return tools;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Map<String, ExternalDiffTool> setupUserDefinedTools(
|
private Map<String, ExternalDiffTool> setupUserDefinedTools(
|
||||||
DiffToolConfig cfg, Map<String, ExternalDiffTool> predefTools) {
|
Map<String, ExternalDiffTool> predefTools) {
|
||||||
Map<String, ExternalDiffTool> tools = new TreeMap<>();
|
Map<String, ExternalDiffTool> tools = new TreeMap<>();
|
||||||
Map<String, ExternalDiffTool> userTools = cfg.getTools();
|
Map<String, ExternalDiffTool> userTools = config.getTools();
|
||||||
for (String name : userTools.keySet()) {
|
for (String name : userTools.keySet()) {
|
||||||
ExternalDiffTool userTool = userTools.get(name);
|
ExternalDiffTool userTool = userTools.get(name);
|
||||||
// if difftool.<name>.cmd is defined we have user defined tool
|
// if difftool.<name>.cmd is defined we have user defined tool
|
||||||
|
|
|
@ -30,4 +30,10 @@ public interface ExternalDiffTool {
|
||||||
*/
|
*/
|
||||||
String getCommand();
|
String getCommand();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return availability of the tool: true if tool can be executed and false
|
||||||
|
* if not
|
||||||
|
*/
|
||||||
|
boolean isAvailable();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,16 +10,38 @@
|
||||||
package org.eclipse.jgit.internal.diffmergetool;
|
package org.eclipse.jgit.internal.diffmergetool;
|
||||||
|
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.attributes.Attributes;
|
||||||
|
import org.eclipse.jgit.errors.RevisionSyntaxException;
|
||||||
import org.eclipse.jgit.lib.Constants;
|
import org.eclipse.jgit.lib.Constants;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.treewalk.FileTreeIterator;
|
||||||
|
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||||
|
import org.eclipse.jgit.treewalk.WorkingTreeIterator;
|
||||||
|
import org.eclipse.jgit.treewalk.filter.NotIgnoredFilter;
|
||||||
|
import org.eclipse.jgit.util.FS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utilities for diff- and merge-tools.
|
* Utilities for diff- and merge-tools.
|
||||||
*/
|
*/
|
||||||
public class ExternalToolUtils {
|
public class ExternalToolUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key for merge tool git configuration section
|
||||||
|
*/
|
||||||
|
public static final String KEY_MERGE_TOOL = "mergetool"; //$NON-NLS-1$
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key for diff tool git configuration section
|
||||||
|
*/
|
||||||
|
public static final String KEY_DIFF_TOOL = "difftool"; //$NON-NLS-1$
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare command for execution.
|
* Prepare command for execution.
|
||||||
*
|
*
|
||||||
|
@ -39,9 +61,15 @@ public class ExternalToolUtils {
|
||||||
public static String prepareCommand(String command, FileElement localFile,
|
public static String prepareCommand(String command, FileElement localFile,
|
||||||
FileElement remoteFile, FileElement mergedFile,
|
FileElement remoteFile, FileElement mergedFile,
|
||||||
FileElement baseFile) throws IOException {
|
FileElement baseFile) throws IOException {
|
||||||
command = localFile.replaceVariable(command);
|
if (localFile != null) {
|
||||||
command = remoteFile.replaceVariable(command);
|
command = localFile.replaceVariable(command);
|
||||||
command = mergedFile.replaceVariable(command);
|
}
|
||||||
|
if (remoteFile != null) {
|
||||||
|
command = remoteFile.replaceVariable(command);
|
||||||
|
}
|
||||||
|
if (mergedFile != null) {
|
||||||
|
command = mergedFile.replaceVariable(command);
|
||||||
|
}
|
||||||
if (baseFile != null) {
|
if (baseFile != null) {
|
||||||
command = baseFile.replaceVariable(command);
|
command = baseFile.replaceVariable(command);
|
||||||
}
|
}
|
||||||
|
@ -51,8 +79,8 @@ public static String prepareCommand(String command, FileElement localFile,
|
||||||
/**
|
/**
|
||||||
* Prepare environment needed for execution.
|
* Prepare environment needed for execution.
|
||||||
*
|
*
|
||||||
* @param repo
|
* @param gitDir
|
||||||
* the repository
|
* the .git directory
|
||||||
* @param localFile
|
* @param localFile
|
||||||
* the local file (ours)
|
* the local file (ours)
|
||||||
* @param remoteFile
|
* @param remoteFile
|
||||||
|
@ -64,18 +92,151 @@ public static String prepareCommand(String command, FileElement localFile,
|
||||||
* @return the environment map with variables and values (file paths)
|
* @return the environment map with variables and values (file paths)
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public static Map<String, String> prepareEnvironment(Repository repo,
|
public static Map<String, String> prepareEnvironment(File gitDir,
|
||||||
FileElement localFile, FileElement remoteFile,
|
FileElement localFile, FileElement remoteFile,
|
||||||
FileElement mergedFile, FileElement baseFile) throws IOException {
|
FileElement mergedFile, FileElement baseFile) throws IOException {
|
||||||
Map<String, String> env = new TreeMap<>();
|
Map<String, String> env = new TreeMap<>();
|
||||||
env.put(Constants.GIT_DIR_KEY, repo.getDirectory().getAbsolutePath());
|
if (gitDir != null) {
|
||||||
localFile.addToEnv(env);
|
env.put(Constants.GIT_DIR_KEY, gitDir.getAbsolutePath());
|
||||||
remoteFile.addToEnv(env);
|
}
|
||||||
mergedFile.addToEnv(env);
|
if (localFile != null) {
|
||||||
|
localFile.addToEnv(env);
|
||||||
|
}
|
||||||
|
if (remoteFile != null) {
|
||||||
|
remoteFile.addToEnv(env);
|
||||||
|
}
|
||||||
|
if (mergedFile != null) {
|
||||||
|
mergedFile.addToEnv(env);
|
||||||
|
}
|
||||||
if (baseFile != null) {
|
if (baseFile != null) {
|
||||||
baseFile.addToEnv(env);
|
baseFile.addToEnv(env);
|
||||||
}
|
}
|
||||||
return env;
|
return env;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param path
|
||||||
|
* the path to be quoted
|
||||||
|
* @return quoted path if it contains spaces
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("nls")
|
||||||
|
public static String quotePath(String path) {
|
||||||
|
// handling of spaces in path
|
||||||
|
if (path.contains(" ")) {
|
||||||
|
// add quotes before if needed
|
||||||
|
if (!path.startsWith("\"")) {
|
||||||
|
path = "\"" + path;
|
||||||
|
}
|
||||||
|
// add quotes after if needed
|
||||||
|
if (!path.endsWith("\"")) {
|
||||||
|
path = path + "\"";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param fs
|
||||||
|
* the file system abstraction
|
||||||
|
* @param gitDir
|
||||||
|
* the .git directory
|
||||||
|
* @param directory
|
||||||
|
* the working directory
|
||||||
|
* @param path
|
||||||
|
* the tool path
|
||||||
|
* @return true if tool available and false otherwise
|
||||||
|
*/
|
||||||
|
public static boolean isToolAvailable(FS fs, File gitDir, File directory,
|
||||||
|
String path) {
|
||||||
|
boolean available = true;
|
||||||
|
try {
|
||||||
|
CommandExecutor cmdExec = new CommandExecutor(fs, false);
|
||||||
|
available = cmdExec.checkExecutable(path, directory,
|
||||||
|
prepareEnvironment(gitDir, null, null, null, null));
|
||||||
|
} catch (Exception e) {
|
||||||
|
available = false;
|
||||||
|
}
|
||||||
|
return available;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param defaultName
|
||||||
|
* the default tool name
|
||||||
|
* @param userDefinedNames
|
||||||
|
* the user defined tool names
|
||||||
|
* @param preDefinedNames
|
||||||
|
* the pre defined tool names
|
||||||
|
* @return the sorted tool names set: first element is default tool name if
|
||||||
|
* valid, then user defined tool names and then pre defined tool
|
||||||
|
* names
|
||||||
|
*/
|
||||||
|
public static Set<String> createSortedToolSet(String defaultName,
|
||||||
|
Set<String> userDefinedNames, Set<String> preDefinedNames) {
|
||||||
|
Set<String> names = new LinkedHashSet<>();
|
||||||
|
if (defaultName != null) {
|
||||||
|
// remove defaultName from both sets
|
||||||
|
Set<String> namesPredef = new LinkedHashSet<>();
|
||||||
|
Set<String> namesUser = new LinkedHashSet<>();
|
||||||
|
namesUser.addAll(userDefinedNames);
|
||||||
|
namesUser.remove(defaultName);
|
||||||
|
namesPredef.addAll(preDefinedNames);
|
||||||
|
namesPredef.remove(defaultName);
|
||||||
|
// add defaultName as first in set
|
||||||
|
names.add(defaultName);
|
||||||
|
names.addAll(namesUser);
|
||||||
|
names.addAll(namesPredef);
|
||||||
|
} else {
|
||||||
|
names.addAll(userDefinedNames);
|
||||||
|
names.addAll(preDefinedNames);
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides {@link Optional} with the name of an external tool if specified
|
||||||
|
* in git configuration for a path.
|
||||||
|
*
|
||||||
|
* The formed git configuration results from global rules as well as merged
|
||||||
|
* rules from info and worktree attributes.
|
||||||
|
*
|
||||||
|
* Triggers {@link TreeWalk} until specified path found in the tree.
|
||||||
|
*
|
||||||
|
* @param repository
|
||||||
|
* target repository to traverse into
|
||||||
|
* @param path
|
||||||
|
* path to the node in repository to parse git attributes for
|
||||||
|
* @param toolKey
|
||||||
|
* config key name for the tool
|
||||||
|
* @return attribute value for the given tool key if set
|
||||||
|
* @throws ToolException
|
||||||
|
*/
|
||||||
|
public static Optional<String> getExternalToolFromAttributes(
|
||||||
|
final Repository repository, final String path,
|
||||||
|
final String toolKey) throws ToolException {
|
||||||
|
try {
|
||||||
|
WorkingTreeIterator treeIterator = new FileTreeIterator(repository);
|
||||||
|
try (TreeWalk walk = new TreeWalk(repository)) {
|
||||||
|
walk.addTree(treeIterator);
|
||||||
|
walk.setFilter(new NotIgnoredFilter(0));
|
||||||
|
while (walk.next()) {
|
||||||
|
String treePath = walk.getPathString();
|
||||||
|
if (treePath.equals(path)) {
|
||||||
|
Attributes attrs = walk.getAttributes();
|
||||||
|
if (attrs.containsKey(toolKey)) {
|
||||||
|
return Optional.of(attrs.getValue(toolKey));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (walk.isSubtree()) {
|
||||||
|
walk.enterSubtree();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// no external tool specified
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (RevisionSyntaxException | IOException e) {
|
||||||
|
throw new ToolException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,8 @@ public enum Type {
|
||||||
|
|
||||||
private final Type type;
|
private final Type type;
|
||||||
|
|
||||||
|
private final File workDir;
|
||||||
|
|
||||||
private InputStream stream;
|
private InputStream stream;
|
||||||
|
|
||||||
private File tempFile;
|
private File tempFile;
|
||||||
|
@ -70,7 +72,7 @@ public enum Type {
|
||||||
* the element type
|
* the element type
|
||||||
*/
|
*/
|
||||||
public FileElement(String path, Type type) {
|
public FileElement(String path, Type type) {
|
||||||
this(path, type, null, null);
|
this(path, type, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -80,17 +82,31 @@ public FileElement(String path, Type type) {
|
||||||
* the file path
|
* the file path
|
||||||
* @param type
|
* @param type
|
||||||
* the element type
|
* the element type
|
||||||
* @param tempFile
|
* @param workDir
|
||||||
* the temporary file to be used (can be null and will be created
|
* the working directory of the path (can be null, then current
|
||||||
* then)
|
* working dir is used)
|
||||||
* @param stream
|
|
||||||
* the object stream to load instead of file
|
|
||||||
*/
|
*/
|
||||||
public FileElement(String path, Type type, File tempFile,
|
public FileElement(String path, Type type, File workDir) {
|
||||||
|
this(path, type, workDir, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param path
|
||||||
|
* the file path
|
||||||
|
* @param type
|
||||||
|
* the element type
|
||||||
|
* @param workDir
|
||||||
|
* the working directory of the path (can be null, then current
|
||||||
|
* working dir is used)
|
||||||
|
* @param stream
|
||||||
|
* the object stream to load and write on demand, @see getFile(),
|
||||||
|
* to tempFile once (can be null)
|
||||||
|
*/
|
||||||
|
public FileElement(String path, Type type, File workDir,
|
||||||
InputStream stream) {
|
InputStream stream) {
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.tempFile = tempFile;
|
this.workDir = workDir;
|
||||||
this.stream = stream;
|
this.stream = stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,41 +125,39 @@ public Type getType() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a temporary file within passed directory and fills it with stream
|
* Return
|
||||||
* if valid.
|
* <ul>
|
||||||
*
|
* <li>a temporary file if already created and stream is not valid</li>
|
||||||
* @param directory
|
* <li>OR a real file from work tree: if no temp file was created (@see
|
||||||
* the directory where the temporary file is created
|
* createTempFile()) and if no stream was set</li>
|
||||||
* @param midName
|
* <li>OR an empty temporary file if path is "/dev/null"</li>
|
||||||
* name added in the middle of generated temporary file name
|
* <li>OR a temporary file with stream content if stream is valid (not
|
||||||
* @return the object stream
|
* null); stream is closed and invalidated (set to null) after write to temp
|
||||||
* @throws IOException
|
* file, so stream is used only once during first call!</li>
|
||||||
*/
|
* </ul>
|
||||||
public File getFile(File directory, String midName) throws IOException {
|
|
||||||
if ((tempFile != null) && (stream == null)) {
|
|
||||||
return tempFile;
|
|
||||||
}
|
|
||||||
tempFile = getTempFile(path, directory, midName);
|
|
||||||
return copyFromStream(tempFile, stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return 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
|
* @return the object stream
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public File getFile() throws IOException {
|
public File getFile() throws IOException {
|
||||||
|
// if we have already temp file and no stream
|
||||||
|
// then just return this temp file (it was filled from outside)
|
||||||
if ((tempFile != null) && (stream == null)) {
|
if ((tempFile != null) && (stream == null)) {
|
||||||
return tempFile;
|
return tempFile;
|
||||||
}
|
}
|
||||||
File file = new File(path);
|
File file = new File(workDir, path);
|
||||||
// if we have a stream or file is missing ("/dev/null") then create
|
// if we have a stream or file is missing (path is "/dev/null")
|
||||||
// temporary file
|
// then optionally create temporary file and fill it with stream content
|
||||||
if ((stream != null) || isNullPath()) {
|
if ((stream != null) || isNullPath()) {
|
||||||
tempFile = getTempFile(file);
|
if (tempFile == null) {
|
||||||
return copyFromStream(tempFile, stream);
|
tempFile = getTempFile(file, type.name(), null);
|
||||||
|
}
|
||||||
|
if (stream != null) {
|
||||||
|
copyFromStream(tempFile, stream);
|
||||||
|
}
|
||||||
|
// invalidate the stream, because it is used once
|
||||||
|
stream = null;
|
||||||
|
return tempFile;
|
||||||
}
|
}
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
@ -158,7 +172,7 @@ public boolean isNullPath() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create temporary file in given or system temporary directory
|
* Create temporary file in given or system temporary directory.
|
||||||
*
|
*
|
||||||
* @param directory
|
* @param directory
|
||||||
* the directory for the file (can be null); if null system
|
* the directory for the file (can be null); if null system
|
||||||
|
@ -168,75 +182,23 @@ public boolean isNullPath() {
|
||||||
*/
|
*/
|
||||||
public File createTempFile(File directory) throws IOException {
|
public File createTempFile(File directory) throws IOException {
|
||||||
if (tempFile == null) {
|
if (tempFile == null) {
|
||||||
File file = new File(path);
|
tempFile = getTempFile(new File(path), type.name(), directory);
|
||||||
if (directory != null) {
|
|
||||||
tempFile = getTempFile(file, directory, type.name());
|
|
||||||
} else {
|
|
||||||
tempFile = getTempFile(file);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return tempFile;
|
return tempFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static File getTempFile(File file) throws IOException {
|
|
||||||
return File.createTempFile(".__", "__" + file.getName()); //$NON-NLS-1$ //$NON-NLS-2$
|
|
||||||
}
|
|
||||||
|
|
||||||
private static File getTempFile(File file, File directory, String midName)
|
|
||||||
throws IOException {
|
|
||||||
String[] fileNameAndExtension = splitBaseFileNameAndExtension(file);
|
|
||||||
return File.createTempFile(
|
|
||||||
fileNameAndExtension[0] + "_" + midName + "_", //$NON-NLS-1$ //$NON-NLS-2$
|
|
||||||
fileNameAndExtension[1], directory);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static File getTempFile(String path, File directory, String midName)
|
|
||||||
throws IOException {
|
|
||||||
return getTempFile(new File(path), directory, midName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete and invalidate temporary file if necessary.
|
* Delete and invalidate temporary file if necessary.
|
||||||
*/
|
*/
|
||||||
public void cleanTemporaries() {
|
public void cleanTemporaries() {
|
||||||
if (tempFile != null && tempFile.exists())
|
if (tempFile != null && tempFile.exists()) {
|
||||||
tempFile.delete();
|
tempFile.delete();
|
||||||
|
}
|
||||||
tempFile = null;
|
tempFile = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static File copyFromStream(File file, final InputStream stream)
|
|
||||||
throws IOException, FileNotFoundException {
|
|
||||||
if (stream != null) {
|
|
||||||
try (OutputStream outStream = new FileOutputStream(file)) {
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String[] splitBaseFileNameAndExtension(File file) {
|
|
||||||
String[] result = new String[2];
|
|
||||||
result[0] = file.getName();
|
|
||||||
result[1] = ""; //$NON-NLS-1$
|
|
||||||
int idx = result[0].lastIndexOf("."); //$NON-NLS-1$
|
|
||||||
// if "." was found (>-1) and last-index is not first char (>0), then
|
|
||||||
// split (same behavior like cgit)
|
|
||||||
if (idx > 0) {
|
|
||||||
result[1] = result[0].substring(idx, result[0].length());
|
|
||||||
result[0] = result[0].substring(0, idx);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replace variable in input
|
* Replace variable in input.
|
||||||
*
|
*
|
||||||
* @param input
|
* @param input
|
||||||
* the input string
|
* the input string
|
||||||
|
@ -258,4 +220,43 @@ public void addToEnv(Map<String, String> env) throws IOException {
|
||||||
env.put(type.name(), getFile().getPath());
|
env.put(type.name(), getFile().getPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static File getTempFile(final File file, final String midName,
|
||||||
|
final File workingDir) throws IOException {
|
||||||
|
String[] fileNameAndExtension = splitBaseFileNameAndExtension(file);
|
||||||
|
// TODO: avoid long random file name (number generated by
|
||||||
|
// createTempFile)
|
||||||
|
return File.createTempFile(
|
||||||
|
fileNameAndExtension[0] + "_" + midName + "_", //$NON-NLS-1$ //$NON-NLS-2$
|
||||||
|
fileNameAndExtension[1], workingDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void copyFromStream(final File file,
|
||||||
|
final InputStream stream)
|
||||||
|
throws IOException, FileNotFoundException {
|
||||||
|
try (OutputStream outStream = new FileOutputStream(file)) {
|
||||||
|
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 and invalidate
|
||||||
|
stream.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String[] splitBaseFileNameAndExtension(File file) {
|
||||||
|
String[] result = new String[2];
|
||||||
|
result[0] = file.getName();
|
||||||
|
result[1] = ""; //$NON-NLS-1$
|
||||||
|
int idx = result[0].lastIndexOf("."); //$NON-NLS-1$
|
||||||
|
// if "." was found (>-1) and last-index is not first char (>0), then
|
||||||
|
// split (same behavior like cgit)
|
||||||
|
if (idx > 0) {
|
||||||
|
result[1] = result[0].substring(idx, result[0].length());
|
||||||
|
result[0] = result[0].substring(0, idx);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018-2019, Tim Neumann <Tim.Neumann@advantest.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.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A handler for when the diff/merge tool manager wants to inform the user that
|
||||||
|
* no tool has been configured and one of the default tools will be used.
|
||||||
|
*/
|
||||||
|
public interface InformNoToolHandler {
|
||||||
|
/**
|
||||||
|
* Inform the user, that no tool is configured and that one of the given
|
||||||
|
* tools is used.
|
||||||
|
*
|
||||||
|
* @param toolNames
|
||||||
|
* The tools which are tried
|
||||||
|
*/
|
||||||
|
void inform(List<String> toolNames);
|
||||||
|
}
|
|
@ -31,7 +31,7 @@
|
||||||
import org.eclipse.jgit.lib.internal.BooleanTriState;
|
import org.eclipse.jgit.lib.internal.BooleanTriState;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keeps track of difftool related configuration options.
|
* Keeps track of merge tool related configuration options.
|
||||||
*/
|
*/
|
||||||
public class MergeToolConfig {
|
public class MergeToolConfig {
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2018-2022, Andre Bossert <andre.bossert@siemens.com>
|
* Copyright (C) 2018-2022, Andre Bossert <andre.bossert@siemens.com>
|
||||||
|
* Copyright (C) 2019, Tim Neumann <tim.neumann@advantest.com>
|
||||||
*
|
*
|
||||||
* This program and the accompanying materials are made available under the
|
* This program and the accompanying materials are made available under the
|
||||||
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
||||||
|
@ -15,13 +16,23 @@
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.internal.JGitText;
|
||||||
import org.eclipse.jgit.internal.diffmergetool.FileElement.Type;
|
import org.eclipse.jgit.internal.diffmergetool.FileElement.Type;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.lib.StoredConfig;
|
||||||
import org.eclipse.jgit.lib.internal.BooleanTriState;
|
import org.eclipse.jgit.lib.internal.BooleanTriState;
|
||||||
|
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||||
|
import org.eclipse.jgit.util.FS;
|
||||||
|
import org.eclipse.jgit.util.StringUtils;
|
||||||
import org.eclipse.jgit.util.FS.ExecutionResult;
|
import org.eclipse.jgit.util.FS.ExecutionResult;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,26 +40,135 @@
|
||||||
*/
|
*/
|
||||||
public class MergeTools {
|
public class MergeTools {
|
||||||
|
|
||||||
Repository repo;
|
private final FS fs;
|
||||||
|
|
||||||
|
private final File gitDir;
|
||||||
|
|
||||||
|
private final File workTree;
|
||||||
|
|
||||||
private final MergeToolConfig config;
|
private final MergeToolConfig config;
|
||||||
|
|
||||||
|
private final Repository repo;
|
||||||
|
|
||||||
private final Map<String, ExternalMergeTool> predefinedTools;
|
private final Map<String, ExternalMergeTool> predefinedTools;
|
||||||
|
|
||||||
private final Map<String, ExternalMergeTool> userDefinedTools;
|
private final Map<String, ExternalMergeTool> userDefinedTools;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Creates the external merge-tools manager for given repository.
|
||||||
|
*
|
||||||
* @param repo
|
* @param repo
|
||||||
* the repository
|
* the repository
|
||||||
*/
|
*/
|
||||||
public MergeTools(Repository repo) {
|
public MergeTools(Repository repo) {
|
||||||
this.repo = repo;
|
this(repo, repo.getConfig());
|
||||||
config = repo.getConfig().get(MergeToolConfig.KEY);
|
|
||||||
predefinedTools = setupPredefinedTools();
|
|
||||||
userDefinedTools = setupUserDefinedTools(config, predefinedTools);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Creates the external diff-tools manager for given configuration.
|
||||||
|
*
|
||||||
|
* @param config
|
||||||
|
* the git configuration
|
||||||
|
*/
|
||||||
|
public MergeTools(StoredConfig config) {
|
||||||
|
this(null, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MergeTools(Repository repo, StoredConfig config) {
|
||||||
|
this.repo = repo;
|
||||||
|
this.config = config.get(MergeToolConfig.KEY);
|
||||||
|
this.gitDir = repo == null ? null : repo.getDirectory();
|
||||||
|
this.fs = repo == null ? FS.DETECTED : repo.getFS();
|
||||||
|
this.workTree = repo == null ? null : repo.getWorkTree();
|
||||||
|
predefinedTools = setupPredefinedTools();
|
||||||
|
userDefinedTools = setupUserDefinedTools(predefinedTools);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge two versions of a file with optional base file.
|
||||||
|
*
|
||||||
|
* @param localFile
|
||||||
|
* The local/left version of the file.
|
||||||
|
* @param remoteFile
|
||||||
|
* The remote/right version of the file.
|
||||||
|
* @param mergedFile
|
||||||
|
* The file for the result.
|
||||||
|
* @param baseFile
|
||||||
|
* The base version of the file. May be null.
|
||||||
|
* @param tempDir
|
||||||
|
* The tmepDir used for the files. May be null.
|
||||||
|
* @param toolName
|
||||||
|
* Optionally the name of the tool to use. If not given the
|
||||||
|
* default tool will be used.
|
||||||
|
* @param prompt
|
||||||
|
* Optionally a flag whether to prompt the user before compare.
|
||||||
|
* If not given the default will be used.
|
||||||
|
* @param gui
|
||||||
|
* A flag whether to prefer a gui tool.
|
||||||
|
* @param promptHandler
|
||||||
|
* The handler to use when needing to prompt the user if he wants
|
||||||
|
* to continue.
|
||||||
|
* @param noToolHandler
|
||||||
|
* The handler to use when needing to inform the user, that no
|
||||||
|
* tool is configured.
|
||||||
|
* @return the optional result of executing the tool if it was executed
|
||||||
|
* @throws ToolException
|
||||||
|
* when the tool fails
|
||||||
|
*/
|
||||||
|
public Optional<ExecutionResult> merge(FileElement localFile,
|
||||||
|
FileElement remoteFile, FileElement mergedFile,
|
||||||
|
FileElement baseFile, File tempDir, Optional<String> toolName,
|
||||||
|
BooleanTriState prompt, boolean gui,
|
||||||
|
PromptContinueHandler promptHandler,
|
||||||
|
InformNoToolHandler noToolHandler) throws ToolException {
|
||||||
|
|
||||||
|
String toolNameToUse;
|
||||||
|
|
||||||
|
if (toolName == null) {
|
||||||
|
throw new ToolException(JGitText.get().diffToolNullError);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toolName.isPresent()) {
|
||||||
|
toolNameToUse = toolName.get();
|
||||||
|
} else {
|
||||||
|
toolNameToUse = getDefaultToolName(gui);
|
||||||
|
|
||||||
|
if (StringUtils.isEmptyOrNull(toolNameToUse)) {
|
||||||
|
noToolHandler.inform(new ArrayList<>(predefinedTools.keySet()));
|
||||||
|
toolNameToUse = getFirstAvailableTool();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isEmptyOrNull(toolNameToUse)) {
|
||||||
|
throw new ToolException(JGitText.get().diffToolNotGivenError);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean doPrompt;
|
||||||
|
if (prompt != BooleanTriState.UNSET) {
|
||||||
|
doPrompt = prompt == BooleanTriState.TRUE;
|
||||||
|
} else {
|
||||||
|
doPrompt = isInteractive();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doPrompt) {
|
||||||
|
if (!promptHandler.prompt(toolNameToUse)) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ExternalMergeTool tool = getTool(toolNameToUse);
|
||||||
|
if (tool == null) {
|
||||||
|
throw new ToolException(
|
||||||
|
"External merge tool is not defined: " + toolNameToUse); //$NON-NLS-1$
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.of(merge(localFile, remoteFile, mergedFile, baseFile,
|
||||||
|
tempDir, tool));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge two versions of a file with optional base file.
|
||||||
|
*
|
||||||
* @param localFile
|
* @param localFile
|
||||||
* the local file element
|
* the local file element
|
||||||
* @param remoteFile
|
* @param remoteFile
|
||||||
|
@ -60,38 +180,31 @@ public MergeTools(Repository repo) {
|
||||||
* @param tempDir
|
* @param tempDir
|
||||||
* the temporary directory (needed for backup and auto-remove,
|
* the temporary directory (needed for backup and auto-remove,
|
||||||
* can be null)
|
* can be null)
|
||||||
* @param toolName
|
* @param tool
|
||||||
* the selected tool name (can be null)
|
* the selected tool
|
||||||
* @param prompt
|
|
||||||
* the prompt option
|
|
||||||
* @param gui
|
|
||||||
* the GUI option
|
|
||||||
* @return the execution result from tool
|
* @return the execution result from tool
|
||||||
* @throws ToolException
|
* @throws ToolException
|
||||||
*/
|
*/
|
||||||
public ExecutionResult merge(FileElement localFile, FileElement remoteFile,
|
public ExecutionResult merge(FileElement localFile, FileElement remoteFile,
|
||||||
FileElement mergedFile, FileElement baseFile, File tempDir,
|
FileElement mergedFile, FileElement baseFile, File tempDir,
|
||||||
String toolName, BooleanTriState prompt, BooleanTriState gui)
|
ExternalMergeTool tool) throws ToolException {
|
||||||
throws ToolException {
|
|
||||||
ExternalMergeTool tool = guessTool(toolName, gui);
|
|
||||||
FileElement backup = null;
|
FileElement backup = null;
|
||||||
ExecutionResult result = null;
|
ExecutionResult result = null;
|
||||||
try {
|
try {
|
||||||
File workingDir = repo.getWorkTree();
|
|
||||||
// create additional backup file (copy worktree file)
|
// create additional backup file (copy worktree file)
|
||||||
backup = createBackupFile(mergedFile.getPath(),
|
backup = createBackupFile(mergedFile,
|
||||||
tempDir != null ? tempDir : workingDir);
|
tempDir != null ? tempDir : workTree);
|
||||||
// prepare the command (replace the file paths)
|
// prepare the command (replace the file paths)
|
||||||
boolean trust = tool.getTrustExitCode() == BooleanTriState.TRUE;
|
|
||||||
String command = ExternalToolUtils.prepareCommand(
|
String command = ExternalToolUtils.prepareCommand(
|
||||||
tool.getCommand(baseFile != null), localFile, remoteFile,
|
tool.getCommand(baseFile != null), localFile, remoteFile,
|
||||||
mergedFile, baseFile);
|
mergedFile, baseFile);
|
||||||
// prepare the environment
|
// prepare the environment
|
||||||
Map<String, String> env = ExternalToolUtils.prepareEnvironment(repo,
|
Map<String, String> env = ExternalToolUtils.prepareEnvironment(
|
||||||
localFile, remoteFile, mergedFile, baseFile);
|
gitDir, localFile, remoteFile, mergedFile, baseFile);
|
||||||
|
boolean trust = tool.getTrustExitCode() == BooleanTriState.TRUE;
|
||||||
// execute the tool
|
// execute the tool
|
||||||
CommandExecutor cmdExec = new CommandExecutor(repo.getFS(), trust);
|
CommandExecutor cmdExec = new CommandExecutor(fs, trust);
|
||||||
result = cmdExec.run(command, workingDir, env);
|
result = cmdExec.run(command, workTree, env);
|
||||||
// keep backup as .orig file
|
// keep backup as .orig file
|
||||||
if (backup != null) {
|
if (backup != null) {
|
||||||
keepBackupFile(mergedFile.getPath(), backup);
|
keepBackupFile(mergedFile.getPath(), backup);
|
||||||
|
@ -123,19 +236,21 @@ public ExecutionResult merge(FileElement localFile, FileElement remoteFile,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private FileElement createBackupFile(String filePath, File parentDir)
|
private FileElement createBackupFile(FileElement from, File toParentDir)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
FileElement backup = null;
|
FileElement backup = null;
|
||||||
Path path = Paths.get(filePath);
|
Path path = Paths.get(from.getPath());
|
||||||
if (Files.exists(path)) {
|
if (Files.exists(path)) {
|
||||||
backup = new FileElement(filePath, Type.BACKUP);
|
backup = new FileElement(from.getPath(), Type.BACKUP);
|
||||||
Files.copy(path, backup.createTempFile(parentDir).toPath(),
|
Files.copy(path, backup.createTempFile(toParentDir).toPath(),
|
||||||
StandardCopyOption.REPLACE_EXISTING);
|
StandardCopyOption.REPLACE_EXISTING);
|
||||||
}
|
}
|
||||||
return backup;
|
return backup;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Create temporary directory.
|
||||||
|
*
|
||||||
* @return the created temporary directory if (mergetol.writeToTemp == true)
|
* @return the created temporary directory if (mergetol.writeToTemp == true)
|
||||||
* or null if not configured or false.
|
* or null if not configured or false.
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
|
@ -147,60 +262,138 @@ public File createTempDirectory() throws IOException {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the tool names
|
* Get user defined tool names.
|
||||||
|
*
|
||||||
|
* @return the user defined tool names
|
||||||
*/
|
*/
|
||||||
public Set<String> getToolNames() {
|
public Set<String> getUserDefinedToolNames() {
|
||||||
return config.getToolNames();
|
return userDefinedTools.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the predefined tool names
|
||||||
|
*/
|
||||||
|
public Set<String> getPredefinedToolNames() {
|
||||||
|
return predefinedTools.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all tool names.
|
||||||
|
*
|
||||||
|
* @return the all tool names (default or available tool name is the first
|
||||||
|
* in the set)
|
||||||
|
*/
|
||||||
|
public Set<String> getAllToolNames() {
|
||||||
|
String defaultName = getDefaultToolName(false);
|
||||||
|
if (defaultName == null) {
|
||||||
|
defaultName = getFirstAvailableTool();
|
||||||
|
}
|
||||||
|
return ExternalToolUtils.createSortedToolSet(defaultName,
|
||||||
|
getUserDefinedToolNames(), getPredefinedToolNames());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides {@link Optional} with the name of an external merge tool if
|
||||||
|
* specified in git configuration for a path.
|
||||||
|
*
|
||||||
|
* The formed git configuration results from global rules as well as merged
|
||||||
|
* rules from info and worktree attributes.
|
||||||
|
*
|
||||||
|
* Triggers {@link TreeWalk} until specified path found in the tree.
|
||||||
|
*
|
||||||
|
* @param path
|
||||||
|
* path to the node in repository to parse git attributes for
|
||||||
|
* @return name of the difftool if set
|
||||||
|
* @throws ToolException
|
||||||
|
*/
|
||||||
|
public Optional<String> getExternalToolFromAttributes(final String path)
|
||||||
|
throws ToolException {
|
||||||
|
return ExternalToolUtils.getExternalToolFromAttributes(repo, path,
|
||||||
|
ExternalToolUtils.KEY_MERGE_TOOL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the availability of the predefined tools in the system.
|
||||||
|
*
|
||||||
|
* @return set of predefined available tools
|
||||||
|
*/
|
||||||
|
public Set<String> getPredefinedAvailableTools() {
|
||||||
|
Map<String, ExternalMergeTool> defTools = getPredefinedTools(true);
|
||||||
|
Set<String> availableTools = new LinkedHashSet<>();
|
||||||
|
for (Entry<String, ExternalMergeTool> elem : defTools.entrySet()) {
|
||||||
|
if (elem.getValue().isAvailable()) {
|
||||||
|
availableTools.add(elem.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return availableTools;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the user defined tools
|
* @return the user defined tools
|
||||||
*/
|
*/
|
||||||
public Map<String, ExternalMergeTool> getUserDefinedTools() {
|
public Map<String, ExternalMergeTool> getUserDefinedTools() {
|
||||||
return userDefinedTools;
|
return Collections.unmodifiableMap(userDefinedTools);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the available predefined tools
|
* Get predefined tools map.
|
||||||
|
*
|
||||||
|
* @param checkAvailability
|
||||||
|
* true: for checking if tools can be executed; ATTENTION: this
|
||||||
|
* check took some time, do not execute often (store the map for
|
||||||
|
* other actions); false: availability is NOT checked:
|
||||||
|
* isAvailable() returns default false is this case!
|
||||||
|
* @return the predefined tools with optionally checked availability (long
|
||||||
|
* running operation)
|
||||||
*/
|
*/
|
||||||
public Map<String, ExternalMergeTool> getAvailableTools() {
|
public Map<String, ExternalMergeTool> getPredefinedTools(
|
||||||
return predefinedTools;
|
boolean checkAvailability) {
|
||||||
|
if (checkAvailability) {
|
||||||
|
for (ExternalMergeTool tool : predefinedTools.values()) {
|
||||||
|
PreDefinedMergeTool predefTool = (PreDefinedMergeTool) tool;
|
||||||
|
predefTool.setAvailable(ExternalToolUtils.isToolAvailable(fs,
|
||||||
|
gitDir, workTree, predefTool.getPath()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableMap(predefinedTools);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the NOT available predefined tools
|
* Get first available tool name.
|
||||||
|
*
|
||||||
|
* @return the name of first available predefined tool or null
|
||||||
*/
|
*/
|
||||||
public Map<String, ExternalMergeTool> getNotAvailableTools() {
|
public String getFirstAvailableTool() {
|
||||||
return new TreeMap<>();
|
String name = null;
|
||||||
}
|
for (ExternalMergeTool tool : predefinedTools.values()) {
|
||||||
|
if (ExternalToolUtils.isToolAvailable(fs, gitDir, workTree,
|
||||||
/**
|
tool.getPath())) {
|
||||||
* @param gui
|
name = tool.getName();
|
||||||
* use the diff.guitool setting ?
|
break;
|
||||||
* @return the default tool name
|
}
|
||||||
*/
|
}
|
||||||
public String getDefaultToolName(BooleanTriState gui) {
|
return name;
|
||||||
return gui != BooleanTriState.UNSET ? "my_gui_tool" //$NON-NLS-1$
|
|
||||||
: config.getDefaultToolName();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Is interactive merge (prompt enabled) ?
|
||||||
|
*
|
||||||
* @return is interactive (config prompt enabled) ?
|
* @return is interactive (config prompt enabled) ?
|
||||||
*/
|
*/
|
||||||
public boolean isInteractive() {
|
public boolean isInteractive() {
|
||||||
return config.isPrompt();
|
return config.isPrompt();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExternalMergeTool guessTool(String toolName, BooleanTriState gui)
|
/**
|
||||||
throws ToolException {
|
* Get the default (gui-)tool name.
|
||||||
if ((toolName == null) || toolName.isEmpty()) {
|
*
|
||||||
toolName = getDefaultToolName(gui);
|
* @param gui
|
||||||
}
|
* use the diff.guitool setting ?
|
||||||
ExternalMergeTool tool = getTool(toolName);
|
* @return the default tool name
|
||||||
if (tool == null) {
|
*/
|
||||||
throw new ToolException("Unknown diff tool " + toolName); //$NON-NLS-1$
|
public String getDefaultToolName(boolean gui) {
|
||||||
}
|
return gui ? config.getDefaultGuiToolName()
|
||||||
return tool;
|
: config.getDefaultToolName();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExternalMergeTool getTool(final String name) {
|
private ExternalMergeTool getTool(final String name) {
|
||||||
|
@ -231,9 +424,9 @@ private Map<String, ExternalMergeTool> setupPredefinedTools() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, ExternalMergeTool> setupUserDefinedTools(
|
private Map<String, ExternalMergeTool> setupUserDefinedTools(
|
||||||
MergeToolConfig cfg, Map<String, ExternalMergeTool> predefTools) {
|
Map<String, ExternalMergeTool> predefTools) {
|
||||||
Map<String, ExternalMergeTool> tools = new TreeMap<>();
|
Map<String, ExternalMergeTool> tools = new TreeMap<>();
|
||||||
Map<String, ExternalMergeTool> userTools = cfg.getTools();
|
Map<String, ExternalMergeTool> userTools = config.getTools();
|
||||||
for (String name : userTools.keySet()) {
|
for (String name : userTools.keySet()) {
|
||||||
ExternalMergeTool userTool = userTools.get(name);
|
ExternalMergeTool userTool = userTools.get(name);
|
||||||
// if mergetool.<name>.cmd is defined we have user defined tool
|
// if mergetool.<name>.cmd is defined we have user defined tool
|
||||||
|
|
|
@ -56,7 +56,7 @@ public void setPath(String path) {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String getCommand() {
|
public String getCommand() {
|
||||||
return getPath() + " " + super.getCommand(); //$NON-NLS-1$
|
return ExternalToolUtils.quotePath(getPath()) + " " + super.getCommand(); //$NON-NLS-1$
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,7 +84,7 @@ public String getCommand() {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String getCommand(boolean withBase) {
|
public String getCommand(boolean withBase) {
|
||||||
return getPath() + " " //$NON-NLS-1$
|
return ExternalToolUtils.quotePath(getPath()) + " " //$NON-NLS-1$
|
||||||
+ (withBase ? super.getCommand() : parametersWithoutBase);
|
+ (withBase ? super.getCommand() : parametersWithoutBase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018-2019, Tim Neumann <Tim.Neumann@advantest.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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A handler for when the diff/merge tool manager wants to prompt the user
|
||||||
|
* whether to continue
|
||||||
|
*/
|
||||||
|
public interface PromptContinueHandler {
|
||||||
|
/**
|
||||||
|
* Prompt the user whether to continue with the next file by opening a given
|
||||||
|
* tool.
|
||||||
|
*
|
||||||
|
* @param toolName
|
||||||
|
* The name of the tool to open
|
||||||
|
* @return Whether the user wants to continue
|
||||||
|
*/
|
||||||
|
boolean prompt(String toolName);
|
||||||
|
}
|
|
@ -110,6 +110,9 @@ public boolean isCommandExecutionError() {
|
||||||
* @return the result Stderr
|
* @return the result Stderr
|
||||||
*/
|
*/
|
||||||
public String getResultStderr() {
|
public String getResultStderr() {
|
||||||
|
if (result == null) {
|
||||||
|
return ""; //$NON-NLS-1$
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
return new String(result.getStderr().toByteArray());
|
return new String(result.getStderr().toByteArray());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -122,6 +125,9 @@ public String getResultStderr() {
|
||||||
* @return the result Stdout
|
* @return the result Stdout
|
||||||
*/
|
*/
|
||||||
public String getResultStdout() {
|
public String getResultStdout() {
|
||||||
|
if (result == null) {
|
||||||
|
return ""; //$NON-NLS-1$
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
return new String(result.getStdout().toByteArray());
|
return new String(result.getStdout().toByteArray());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
public class UserDefinedDiffTool implements ExternalDiffTool {
|
public class UserDefinedDiffTool implements ExternalDiffTool {
|
||||||
|
|
||||||
|
private boolean available;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the diff tool name
|
* the diff tool name
|
||||||
*/
|
*/
|
||||||
|
@ -98,6 +100,23 @@ public String getCommand() {
|
||||||
return cmd;
|
return cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return availability of the tool: true if tool can be executed and false
|
||||||
|
* if not
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isAvailable() {
|
||||||
|
return available;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param available
|
||||||
|
* true if tool can be found and false if not
|
||||||
|
*/
|
||||||
|
public void setAvailable(boolean available) {
|
||||||
|
this.available = available;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overrides the path for the given tool. Equivalent to setting
|
* Overrides the path for the given tool. Equivalent to setting
|
||||||
* {@code difftool.<tool>.path}.
|
* {@code difftool.<tool>.path}.
|
||||||
|
|
Loading…
Reference in New Issue