aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DiffToolTest.java106
-rw-r--r--org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ExternalToolTestCase.java136
-rw-r--r--org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/MergeToolTest.java136
-rw-r--r--org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin1
-rw-r--r--org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties3
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTool.java4
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/MergeTool.java212
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalMergeToolTest.java217
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandLineMergeTool.java327
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalMergeTool.java12
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeToolConfig.java58
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeTools.java103
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedMergeTool.java91
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/UserDefinedMergeTool.java24
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java3
16 files changed, 1269 insertions, 171 deletions
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DiffToolTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DiffToolTest.java
index e2ff189276..017a5d994f 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DiffToolTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DiffToolTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021, Simeon Andreev <simeon.danailov.andreev@gmail.com> and others.
+ * Copyright (C) 2021-2022, Simeon Andreev <simeon.danailov.andreev@gmail.com> and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -14,68 +14,30 @@ import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DIFF_SECTION;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_CMD;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PROMPT;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_TOOL;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.internal.diffmergetool.CommandLineDiffTool;
-import org.eclipse.jgit.lib.CLIRepositoryTestCase;
import org.eclipse.jgit.lib.StoredConfig;
-import org.eclipse.jgit.pgm.opt.CmdLineParser;
-import org.eclipse.jgit.pgm.opt.SubcommandHandler;
import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.treewalk.FileTreeIterator;
-import org.eclipse.jgit.treewalk.TreeWalk;
import org.junit.Before;
import org.junit.Test;
-import org.kohsuke.args4j.Argument;
/**
* Testing the {@code difftool} command.
*/
-public class DiffToolTest extends CLIRepositoryTestCase {
- public static class GitCliJGitWrapperParser {
- @Argument(index = 0, metaVar = "metaVar_command", required = true, handler = SubcommandHandler.class)
- TextBuiltin subcommand;
+public class DiffToolTest extends ExternalToolTestCase {
- @Argument(index = 1, metaVar = "metaVar_arg")
- List<String> arguments = new ArrayList<>();
- }
-
- private String[] runAndCaptureUsingInitRaw(String... args)
- throws Exception {
- CLIGitCommand.Result result = new CLIGitCommand.Result();
-
- GitCliJGitWrapperParser bean = new GitCliJGitWrapperParser();
- CmdLineParser clp = new CmdLineParser(bean);
- clp.parseArgument(args);
-
- TextBuiltin cmd = bean.subcommand;
- cmd.initRaw(db, null, null, result.out, result.err);
- cmd.execute(bean.arguments.toArray(new String[bean.arguments.size()]));
- if (cmd.getOutputWriter() != null) {
- cmd.getOutputWriter().flush();
- }
- if (cmd.getErrorWriter() != null) {
- cmd.getErrorWriter().flush();
- }
- return result.outLines().toArray(new String[0]);
- }
-
- private static final String TOOL_NAME = "some_tool";
- private Git git;
+ private static final String DIFF_TOOL = CONFIG_DIFFTOOL_SECTION;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
- git = new Git(db);
- git.commit().setMessage("initial commit").call();
configureEchoTool(TOOL_NAME);
}
@@ -83,7 +45,7 @@ public class DiffToolTest extends CLIRepositoryTestCase {
public void testNotDefinedTool() throws Exception {
createUnstagedChanges();
- runAndCaptureUsingInitRaw("difftool", "--tool", "undefined");
+ runAndCaptureUsingInitRaw(DIFF_TOOL, "--tool", "undefined");
fail("Expected exception when trying to run undefined tool");
}
@@ -91,7 +53,7 @@ public class DiffToolTest extends CLIRepositoryTestCase {
public void testTool() throws Exception {
RevCommit commit = createUnstagedChanges();
List<DiffEntry> changes = getRepositoryChanges(commit);
- String[] expectedOutput = getExpectedDiffToolOutput(changes);
+ String[] expectedOutput = getExpectedToolOutput(changes);
String[] options = {
"--tool",
@@ -101,7 +63,7 @@ public class DiffToolTest extends CLIRepositoryTestCase {
for (String option : options) {
assertArrayOfLinesEquals("Incorrect output for option: " + option,
expectedOutput,
- runAndCaptureUsingInitRaw("difftool", option,
+ runAndCaptureUsingInitRaw(DIFF_TOOL, option,
TOOL_NAME));
}
}
@@ -110,13 +72,13 @@ public class DiffToolTest extends CLIRepositoryTestCase {
public void testToolTrustExitCode() throws Exception {
RevCommit commit = createUnstagedChanges();
List<DiffEntry> changes = getRepositoryChanges(commit);
- String[] expectedOutput = getExpectedDiffToolOutput(changes);
+ String[] expectedOutput = getExpectedToolOutput(changes);
String[] options = { "--tool", "-t", };
for (String option : options) {
assertArrayOfLinesEquals("Incorrect output for option: " + option,
- expectedOutput, runAndCaptureUsingInitRaw("difftool",
+ expectedOutput, runAndCaptureUsingInitRaw(DIFF_TOOL,
"--trust-exit-code", option, TOOL_NAME));
}
}
@@ -125,13 +87,13 @@ public class DiffToolTest extends CLIRepositoryTestCase {
public void testToolNoGuiNoPromptNoTrustExitcode() throws Exception {
RevCommit commit = createUnstagedChanges();
List<DiffEntry> changes = getRepositoryChanges(commit);
- String[] expectedOutput = getExpectedDiffToolOutput(changes);
+ String[] expectedOutput = getExpectedToolOutput(changes);
String[] options = { "--tool", "-t", };
for (String option : options) {
assertArrayOfLinesEquals("Incorrect output for option: " + option,
- expectedOutput, runAndCaptureUsingInitRaw("difftool",
+ expectedOutput, runAndCaptureUsingInitRaw(DIFF_TOOL,
"--no-gui", "--no-prompt", "--no-trust-exit-code",
option, TOOL_NAME));
}
@@ -141,13 +103,13 @@ public class DiffToolTest extends CLIRepositoryTestCase {
public void testToolCached() throws Exception {
RevCommit commit = createStagedChanges();
List<DiffEntry> changes = getRepositoryChanges(commit);
- String[] expectedOutput = getExpectedDiffToolOutput(changes);
+ String[] expectedOutput = getExpectedToolOutput(changes);
String[] options = { "--cached", "--staged", };
for (String option : options) {
assertArrayOfLinesEquals("Incorrect output for option: " + option,
- expectedOutput, runAndCaptureUsingInitRaw("difftool",
+ expectedOutput, runAndCaptureUsingInitRaw(DIFF_TOOL,
option, "--tool", TOOL_NAME));
}
}
@@ -174,7 +136,8 @@ public class DiffToolTest extends CLIRepositoryTestCase {
String option = "--tool-help";
assertArrayOfLinesEquals("Incorrect output for option: " + option,
- expectedOutput.toArray(new String[0]), runAndCaptureUsingInitRaw("difftool", option));
+ expectedOutput.toArray(new String[0]),
+ runAndCaptureUsingInitRaw(DIFF_TOOL, option));
}
private void configureEchoTool(String toolName) {
@@ -196,33 +159,7 @@ public class DiffToolTest extends CLIRepositoryTestCase {
String.valueOf(false));
}
- private RevCommit createUnstagedChanges() throws Exception {
- writeTrashFile("a", "Hello world a");
- writeTrashFile("b", "Hello world b");
- git.add().addFilepattern(".").call();
- RevCommit commit = git.commit().setMessage("files a & b").call();
- writeTrashFile("a", "New Hello world a");
- writeTrashFile("b", "New Hello world b");
- return commit;
- }
-
- private RevCommit createStagedChanges() throws Exception {
- RevCommit commit = createUnstagedChanges();
- git.add().addFilepattern(".").call();
- return commit;
- }
-
- private List<DiffEntry> getRepositoryChanges(RevCommit commit)
- throws Exception {
- TreeWalk tw = new TreeWalk(db);
- tw.addTree(commit.getTree());
- FileTreeIterator modifiedTree = new FileTreeIterator(db);
- tw.addTree(modifiedTree);
- List<DiffEntry> changes = DiffEntry.scan(tw);
- return changes;
- }
-
- private String[] getExpectedDiffToolOutput(List<DiffEntry> changes) {
+ private String[] getExpectedToolOutput(List<DiffEntry> changes) {
String[] expectedToolOutput = new String[changes.size()];
for (int i = 0; i < changes.size(); ++i) {
DiffEntry change = changes.get(i);
@@ -232,17 +169,4 @@ public class DiffToolTest extends CLIRepositoryTestCase {
}
return expectedToolOutput;
}
-
- private static void assertArrayOfLinesEquals(String failMessage,
- String[] expected, String[] actual) {
- assertEquals(failMessage, toString(expected), toString(actual));
- }
-
- private static String getEchoCommand() {
- /*
- * use 'MERGED' placeholder, as both 'LOCAL' and 'REMOTE' will be
- * replaced with full paths to a temporary file during some of the tests
- */
- return "(echo \"$MERGED\")";
- }
}
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ExternalToolTestCase.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ExternalToolTestCase.java
new file mode 100644
index 0000000000..e10b13efb1
--- /dev/null
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ExternalToolTestCase.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2022, Simeon Andreev <simeon.danailov.andreev@gmail.com> and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.pgm;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jgit.api.CherryPickResult;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.diff.DiffEntry;
+import org.eclipse.jgit.lib.CLIRepositoryTestCase;
+import org.eclipse.jgit.pgm.opt.CmdLineParser;
+import org.eclipse.jgit.pgm.opt.SubcommandHandler;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.treewalk.FileTreeIterator;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.junit.Before;
+import org.kohsuke.args4j.Argument;
+
+/**
+ * Base test case for the {@code difftool} and {@code mergetool} commands.
+ */
+public abstract class ExternalToolTestCase extends CLIRepositoryTestCase {
+
+ public static class GitCliJGitWrapperParser {
+ @Argument(index = 0, metaVar = "metaVar_command", required = true, handler = SubcommandHandler.class)
+ TextBuiltin subcommand;
+
+ @Argument(index = 1, metaVar = "metaVar_arg")
+ List<String> arguments = new ArrayList<>();
+ }
+
+ protected static final String TOOL_NAME = "some_tool";
+
+ private static final String TEST_BRANCH_NAME = "test_branch";
+
+ private Git git;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ git = new Git(db);
+ git.commit().setMessage("initial commit").call();
+ git.branchCreate().setName(TEST_BRANCH_NAME).call();
+ }
+
+ protected String[] runAndCaptureUsingInitRaw(String... args)
+ throws Exception {
+ CLIGitCommand.Result result = new CLIGitCommand.Result();
+
+ GitCliJGitWrapperParser bean = new GitCliJGitWrapperParser();
+ CmdLineParser clp = new CmdLineParser(bean);
+ clp.parseArgument(args);
+
+ TextBuiltin cmd = bean.subcommand;
+ cmd.initRaw(db, null, null, result.out, result.err);
+ cmd.execute(bean.arguments.toArray(new String[bean.arguments.size()]));
+ if (cmd.getOutputWriter() != null) {
+ cmd.getOutputWriter().flush();
+ }
+ if (cmd.getErrorWriter() != null) {
+ cmd.getErrorWriter().flush();
+ }
+ return result.outLines().toArray(new String[0]);
+ }
+
+ protected CherryPickResult createMergeConflict() throws Exception {
+ writeTrashFile("a", "Hello world a");
+ writeTrashFile("b", "Hello world b");
+ git.add().addFilepattern(".").call();
+ git.commit().setMessage("files a & b added").call();
+ writeTrashFile("a", "Hello world a 1");
+ writeTrashFile("b", "Hello world b 1");
+ git.add().addFilepattern(".").call();
+ RevCommit commit1 = git.commit().setMessage("files a & b commit 1")
+ .call();
+ git.branchCreate().setName("branch_1").call();
+ git.checkout().setName(TEST_BRANCH_NAME).call();
+ writeTrashFile("a", "Hello world a 2");
+ writeTrashFile("b", "Hello world b 2");
+ git.add().addFilepattern(".").call();
+ git.commit().setMessage("files a & b commit 2").call();
+ git.branchCreate().setName("branch_2").call();
+ CherryPickResult result = git.cherryPick().include(commit1).call();
+ return result;
+ }
+
+ protected RevCommit createUnstagedChanges() throws Exception {
+ writeTrashFile("a", "Hello world a");
+ writeTrashFile("b", "Hello world b");
+ git.add().addFilepattern(".").call();
+ RevCommit commit = git.commit().setMessage("files a & b").call();
+ writeTrashFile("a", "New Hello world a");
+ writeTrashFile("b", "New Hello world b");
+ return commit;
+ }
+
+ protected RevCommit createStagedChanges() throws Exception {
+ RevCommit commit = createUnstagedChanges();
+ git.add().addFilepattern(".").call();
+ return commit;
+ }
+
+ protected List<DiffEntry> getRepositoryChanges(RevCommit commit)
+ throws Exception {
+ TreeWalk tw = new TreeWalk(db);
+ tw.addTree(commit.getTree());
+ FileTreeIterator modifiedTree = new FileTreeIterator(db);
+ tw.addTree(modifiedTree);
+ List<DiffEntry> changes = DiffEntry.scan(tw);
+ return changes;
+ }
+
+ protected static void assertArrayOfLinesEquals(String failMessage,
+ String[] expected, String[] actual) {
+ assertEquals(failMessage, toString(expected), toString(actual));
+ }
+
+ protected static String getEchoCommand() {
+ /*
+ * use 'MERGED' placeholder, as both 'LOCAL' and 'REMOTE' will be
+ * replaced with full paths to a temporary file during some of the tests
+ */
+ return "(echo \"$MERGED\")";
+ }
+}
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/MergeToolTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/MergeToolTest.java
new file mode 100644
index 0000000000..32cd60415e
--- /dev/null
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/MergeToolTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2022, Simeon Andreev <simeon.danailov.andreev@gmail.com> and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.pgm;
+
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_CMD;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PROMPT;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_TOOL;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_MERGETOOL_SECTION;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_MERGE_SECTION;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.jgit.internal.diffmergetool.CommandLineMergeTool;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Testing the {@code mergetool} command.
+ */
+public class MergeToolTest extends ExternalToolTestCase {
+
+ private static final String MERGE_TOOL = CONFIG_MERGETOOL_SECTION;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ configureEchoTool(TOOL_NAME);
+ }
+
+ @Test
+ public void testTool() throws Exception {
+ createMergeConflict();
+ String[] expectedOutput = getExpectedToolOutput();
+
+ String[] options = {
+ "--tool",
+ "-t",
+ };
+
+ for (String option : options) {
+ assertArrayOfLinesEquals("Incorrect output for option: " + option,
+ expectedOutput,
+ runAndCaptureUsingInitRaw(MERGE_TOOL, option,
+ TOOL_NAME));
+ }
+ }
+
+ @Test
+ public void testToolNoGuiNoPrompt() throws Exception {
+ createMergeConflict();
+ String[] expectedOutput = getExpectedToolOutput();
+
+ String[] options = { "--tool", "-t", };
+
+ for (String option : options) {
+ assertArrayOfLinesEquals("Incorrect output for option: " + option,
+ expectedOutput, runAndCaptureUsingInitRaw(MERGE_TOOL,
+ "--no-gui", "--no-prompt", option, TOOL_NAME));
+ }
+ }
+
+ @Test
+ public void testToolHelp() throws Exception {
+ CommandLineMergeTool[] defaultTools = CommandLineMergeTool.values();
+ List<String> expectedOutput = new ArrayList<>();
+ expectedOutput.add(
+ "'git mergetool --tool=<tool>' may be set to one of the following:");
+ for (CommandLineMergeTool defaultTool : defaultTools) {
+ String toolName = defaultTool.name();
+ expectedOutput.add(toolName);
+ }
+ String customToolHelpLine = TOOL_NAME + "." + CONFIG_KEY_CMD + " "
+ + getEchoCommand();
+ expectedOutput.add("user-defined:");
+ expectedOutput.add(customToolHelpLine);
+ String[] userDefinedToolsHelp = {
+ "The following tools are valid, but not currently available:",
+ "Some of the tools listed above only work in a windowed",
+ "environment. If run in a terminal-only session, they will fail.",
+ };
+ expectedOutput.addAll(Arrays.asList(userDefinedToolsHelp));
+
+ String option = "--tool-help";
+ assertArrayOfLinesEquals("Incorrect output for option: " + option,
+ expectedOutput.toArray(new String[0]),
+ runAndCaptureUsingInitRaw(MERGE_TOOL, option));
+ }
+
+ private void configureEchoTool(String toolName) {
+ 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,
+ toolName);
+
+ String command = getEchoCommand();
+
+ config.setString(CONFIG_MERGETOOL_SECTION, toolName, CONFIG_KEY_CMD,
+ command);
+ /*
+ * prevent prompts as we are running in tests and there is no user to
+ * interact with on the command line
+ */
+ config.setString(CONFIG_MERGETOOL_SECTION, toolName, CONFIG_KEY_PROMPT,
+ String.valueOf(false));
+ }
+
+ private String[] getExpectedToolOutput() {
+ String[] mergeConflictFilenames = { "a", "b", };
+ List<String> expectedOutput = new ArrayList<>();
+ expectedOutput.add("Merging:");
+ for (String mergeConflictFilename : mergeConflictFilenames) {
+ expectedOutput.add(mergeConflictFilename);
+ }
+ for (String mergeConflictFilename : mergeConflictFilenames) {
+ expectedOutput.add("Normal merge conflict for '"
+ + mergeConflictFilename + "':");
+ expectedOutput.add("{local}: modified file");
+ expectedOutput.add("{remote}: modified file");
+ expectedOutput.add("TODO: Launch mergetool '" + TOOL_NAME
+ + "' for path '" + mergeConflictFilename + "'...");
+ }
+ return expectedOutput.toArray(new String[0]);
+ }
+}
diff --git a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
index 8c44764c63..ea1d1e3faa 100644
--- a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
+++ b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
@@ -25,6 +25,7 @@ org.eclipse.jgit.pgm.LsRemote
org.eclipse.jgit.pgm.LsTree
org.eclipse.jgit.pgm.Merge
org.eclipse.jgit.pgm.MergeBase
+org.eclipse.jgit.pgm.MergeTool
org.eclipse.jgit.pgm.Push
org.eclipse.jgit.pgm.ReceivePack
org.eclipse.jgit.pgm.Reflog
diff --git a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
index 3653b9d8fc..8e2eef7eb2 100644
--- a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
+++ b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
@@ -255,6 +255,7 @@ usage_DisplayTheVersionOfJgit=Display the version of jgit
usage_Gc=Cleanup unnecessary files and optimize the local repository
usage_Glog=View commit history as a graph
usage_DiffGuiTool=When git-difftool is invoked with the -g or --gui option the default diff tool will be read from the configured diff.guitool variable instead of diff.tool.
+usage_MergeGuiTool=When git-mergetool is invoked with the -g or --gui option the default merge tool will be read from the configured merge.guitool variable instead of merge.tool.
usage_noGui=The --no-gui option can be used to override -g or --gui setting.
usage_IndexPack=Build pack index file for an existing packed archive
usage_LFSDirectory=Directory to store large objects
@@ -303,6 +304,7 @@ usage_Status=Show the working tree status
usage_StopTrackingAFile=Stop tracking a file
usage_TextHashFunctions=Scan repository to compute maximum number of collisions for hash functions
usage_ToolForDiff=Use the diff tool specified by <tool>. Run git difftool --tool-help for the list of valid <tool> settings.\nIf a diff tool is not specified, git difftool will use the configuration variable diff.tool.
+usage_ToolForMerge=Use the merge resolution program specified by <tool>. Run git mergetool --tool-help for the list of valid <tool> settings.\nIf a merge resolution program is not specified, git mergetool will use the configuration variable merge.tool.
usage_UpdateRemoteRepositoryFromLocalRefs=Update remote repository from local refs
usage_UseAll=Use all refs found in refs/
usage_UseTags=Use any tag including lightweight tags
@@ -350,6 +352,7 @@ usage_date=date format, one of default, rfc, local, iso, short, raw (as defined
usage_detectRenames=detect renamed files
usage_diffAlgorithm=the diff algorithm to use. Currently supported are: 'myers', 'histogram'
usage_DiffTool=git difftool is a Git command that allows you to compare and edit files between revisions using common diff tools.\ngit difftool is a frontend to git diff and accepts the same options and arguments.
+usage_MergeTool=git-mergetool - Run merge conflict resolution tools to resolve merge conflicts.\nUse git mergetool to run one of several merge utilities to resolve merge conflicts. It is typically run after git merge.
usage_directoriesToExport=directories to export
usage_disableTheServiceInAllRepositories=disable the service in all repositories
usage_displayAListOfAllRegisteredJgitCommands=Display a list of all registered jgit commands
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTool.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTool.java
index 2f74177452..2e90d52cba 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTool.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTool.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018-2021, Andre Bossert <andre.bossert@siemens.com>
+ * Copyright (C) 2018-2022, Andre Bossert <andre.bossert@siemens.com>
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -192,7 +192,7 @@ class DiffTool extends TextBuiltin {
outw.flush();
errw.println(e.getMessage());
throw die(MessageFormat.format(
- CLIText.get().diffToolDied, mergedFilePath, e));
+ CLIText.get().diffToolDied, mergedFilePath), e);
}
} else {
break;
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/MergeTool.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/MergeTool.java
new file mode 100644
index 0000000000..37afa54c78
--- /dev/null
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/MergeTool.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2018-2022, Andre Bossert <andre.bossert@siemens.com>
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.pgm;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.TreeMap;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.Status;
+import org.eclipse.jgit.api.StatusCommand;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.internal.diffmergetool.ExternalMergeTool;
+import org.eclipse.jgit.errors.NoWorkTreeException;
+import org.eclipse.jgit.errors.RevisionSyntaxException;
+import org.eclipse.jgit.internal.diffmergetool.MergeTools;
+import org.eclipse.jgit.lib.IndexDiff.StageState;
+import org.eclipse.jgit.lib.internal.BooleanTriState;
+import org.eclipse.jgit.lib.Repository;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
+import org.kohsuke.args4j.spi.RestOfArgumentsHandler;
+
+@Command(name = "mergetool", common = true, usage = "usage_MergeTool")
+class MergeTool extends TextBuiltin {
+ private MergeTools mergeTools;
+
+ @Option(name = "--tool", aliases = {
+ "-t" }, metaVar = "metaVar_tool", usage = "usage_ToolForMerge")
+ private String toolName;
+
+ private Optional<Boolean> prompt = Optional.empty();
+
+ @Option(name = "--prompt", usage = "usage_prompt")
+ void setPrompt(@SuppressWarnings("unused") boolean on) {
+ prompt = Optional.of(Boolean.TRUE);
+ }
+
+ @Option(name = "--no-prompt", aliases = { "-y" }, usage = "usage_noPrompt")
+ void noPrompt(@SuppressWarnings("unused") boolean on) {
+ prompt = Optional.of(Boolean.FALSE);
+ }
+
+ @Option(name = "--tool-help", usage = "usage_toolHelp")
+ private boolean toolHelp;
+
+ private BooleanTriState gui = BooleanTriState.UNSET;
+
+ @Option(name = "--gui", aliases = { "-g" }, usage = "usage_MergeGuiTool")
+ void setGui(@SuppressWarnings("unused") boolean on) {
+ gui = BooleanTriState.TRUE;
+ }
+
+ @Option(name = "--no-gui", usage = "usage_noGui")
+ void noGui(@SuppressWarnings("unused") boolean on) {
+ gui = BooleanTriState.FALSE;
+ }
+
+ @Argument(required = false, index = 0, metaVar = "metaVar_paths")
+ @Option(name = "--", metaVar = "metaVar_paths", handler = RestOfArgumentsHandler.class)
+ protected List<String> filterPaths;
+
+ @Override
+ protected void init(Repository repository, String gitDir) {
+ super.init(repository, gitDir);
+ mergeTools = new MergeTools(repository);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ if (toolHelp) {
+ showToolHelp();
+ } else {
+ // get prompt
+ boolean showPrompt = mergeTools.isInteractive();
+ if (prompt.isPresent()) {
+ showPrompt = prompt.get().booleanValue();
+ }
+ // get passed or default tool name
+ String toolNameSelected = toolName;
+ if ((toolNameSelected == null) || toolNameSelected.isEmpty()) {
+ toolNameSelected = mergeTools.getDefaultToolName(gui);
+ }
+ // get the changed files
+ Map<String, StageState> files = getFiles();
+ if (files.size() > 0) {
+ merge(files, showPrompt, toolNameSelected);
+ } else {
+ outw.println("No files need merging"); //$NON-NLS-1$
+ }
+ }
+ outw.flush();
+ } catch (Exception e) {
+ throw die(e.getMessage(), e);
+ }
+ }
+
+ private void merge(Map<String, StageState> files, boolean showPrompt,
+ String toolNamePrompt) throws Exception {
+ // sort file names
+ List<String> fileNames = new ArrayList<>(files.keySet());
+ Collections.sort(fileNames);
+ // show the files
+ outw.println("Merging:"); //$NON-NLS-1$
+ for (String fileName : fileNames) {
+ outw.println(fileName);
+ }
+ outw.flush();
+ for (String fileName : fileNames) {
+ StageState fileState = files.get(fileName);
+ // only both-modified is valid for mergetool
+ if (fileState == StageState.BOTH_MODIFIED) {
+ outw.println("\nNormal merge conflict for '" + fileName + "':"); //$NON-NLS-1$ //$NON-NLS-2$
+ outw.println(" {local}: modified file"); //$NON-NLS-1$
+ outw.println(" {remote}: modified file"); //$NON-NLS-1$
+ // check if user wants to launch merge resolution tool
+ boolean launch = true;
+ if (showPrompt) {
+ launch = isLaunch(toolNamePrompt);
+ }
+ if (launch) {
+ outw.println("TODO: Launch mergetool '" + toolNamePrompt //$NON-NLS-1$
+ + "' for path '" + fileName + "'..."); //$NON-NLS-1$ //$NON-NLS-2$
+ } else {
+ break;
+ }
+ } else if ((fileState == StageState.DELETED_BY_US) || (fileState == StageState.DELETED_BY_THEM)) {
+ outw.println("\nDeleted merge conflict for '" + fileName + "':"); //$NON-NLS-1$ //$NON-NLS-2$
+ } else {
+ outw.println(
+ "\nUnknown merge conflict for '" + fileName + "':"); //$NON-NLS-1$ //$NON-NLS-2$
+ break;
+ }
+ }
+ }
+
+ private boolean isLaunch(String toolNamePrompt)
+ throws IOException {
+ boolean launch = true;
+ outw.println("Hit return to start merge resolution tool (" //$NON-NLS-1$
+ + toolNamePrompt + "): "); //$NON-NLS-1$
+ outw.flush();
+ BufferedReader br = new BufferedReader(new InputStreamReader(ins));
+ String line = null;
+ if ((line = br.readLine()) != null) {
+ if (!line.equalsIgnoreCase("Y") && !line.equalsIgnoreCase("")) { //$NON-NLS-1$ //$NON-NLS-2$
+ launch = false;
+ }
+ }
+ return launch;
+ }
+
+ private void showToolHelp() throws IOException {
+ outw.println(
+ "'git mergetool --tool=<tool>' may be set to one of the following:"); //$NON-NLS-1$
+ for (String name : mergeTools.getAvailableTools().keySet()) {
+ outw.println("\t\t" + name); //$NON-NLS-1$
+ }
+ outw.println(""); //$NON-NLS-1$
+ outw.println("\tuser-defined:"); //$NON-NLS-1$
+ Map<String, ExternalMergeTool> userTools = mergeTools
+ .getUserDefinedTools();
+ for (String name : userTools.keySet()) {
+ outw.println("\t\t" + name + ".cmd " //$NON-NLS-1$ //$NON-NLS-2$
+ + userTools.get(name).getCommand());
+ }
+ outw.println(""); //$NON-NLS-1$
+ outw.println(
+ "The following tools are valid, but not currently available:"); //$NON-NLS-1$
+ for (String name : mergeTools.getNotAvailableTools().keySet()) {
+ outw.println("\t\t" + name); //$NON-NLS-1$
+ }
+ outw.println(""); //$NON-NLS-1$
+ outw.println("Some of the tools listed above only work in a windowed"); //$NON-NLS-1$
+ outw.println(
+ "environment. If run in a terminal-only session, they will fail."); //$NON-NLS-1$
+ return;
+ }
+
+ private Map<String, StageState> getFiles()
+ throws RevisionSyntaxException, NoWorkTreeException,
+ GitAPIException {
+ Map<String, StageState> files = new TreeMap<>();
+ try (Git git = new Git(db)) {
+ StatusCommand statusCommand = git.status();
+ if (filterPaths != null && filterPaths.size() > 0) {
+ for (String path : filterPaths) {
+ statusCommand.addPath(path);
+ }
+ }
+ Status status = statusCommand.call();
+ files = status.getConflictingStageState();
+ }
+ return files;
+ }
+
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalMergeToolTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalMergeToolTest.java
index 96fd1026c7..1dea44eaac 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalMergeToolTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalMergeToolTest.java
@@ -9,13 +9,27 @@
*/
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_GUITOOL;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PATH;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PROMPT;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_TOOL;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_TRUST_EXIT_CODE;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Map;
import java.util.Set;
import org.eclipse.jgit.lib.internal.BooleanTriState;
import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS.ExecutionResult;
import org.junit.Test;
/**
@@ -23,12 +37,60 @@ import org.junit.Test;
*/
public class ExternalMergeToolTest extends ExternalToolTestCase {
+ @Test(expected = ToolException.class)
+ public void testUserToolWithError() throws Exception {
+ String toolName = "customTool";
+
+ int errorReturnCode = 1;
+ String command = "exit " + errorReturnCode;
+
+ FileBasedConfig config = db.getConfig();
+ config.setString(CONFIG_MERGETOOL_SECTION, toolName, CONFIG_KEY_CMD,
+ command);
+ config.setString(CONFIG_MERGETOOL_SECTION, toolName,
+ CONFIG_KEY_TRUST_EXIT_CODE, String.valueOf(Boolean.TRUE));
+
+ MergeTools manager = new MergeTools(db);
+
+ BooleanTriState prompt = BooleanTriState.UNSET;
+ BooleanTriState gui = BooleanTriState.UNSET;
+
+ manager.merge(db, local, remote, base, merged.getPath(), toolName,
+ prompt, gui);
+
+ fail("Expected exception to be thrown due to external tool exiting with error code: "
+ + errorReturnCode);
+ }
+
+ @Test(expected = ToolException.class)
+ public void testUserToolWithCommandNotFoundError() throws Exception {
+ String toolName = "customTool";
+
+ int errorReturnCode = 127; // command not found
+ String command = "exit " + errorReturnCode;
+
+ FileBasedConfig config = db.getConfig();
+ config.setString(CONFIG_MERGETOOL_SECTION, toolName, CONFIG_KEY_CMD,
+ command);
+
+ MergeTools manager = new MergeTools(db);
+
+ BooleanTriState prompt = BooleanTriState.UNSET;
+ BooleanTriState gui = BooleanTriState.UNSET;
+
+ manager.merge(db, local, remote, base, merged.getPath(), toolName,
+ prompt, gui);
+
+ fail("Expected exception to be thrown due to external tool exiting with error code: "
+ + errorReturnCode);
+ }
+
@Test
public void testToolNames() {
MergeTools manager = new MergeTools(db);
Set<String> actualToolNames = manager.getToolNames();
Set<String> expectedToolNames = Collections.emptySet();
- assertEquals("Incorrect set of external diff tool names",
+ assertEquals("Incorrect set of external merge tool names",
expectedToolNames, actualToolNames);
}
@@ -36,18 +98,58 @@ public class ExternalMergeToolTest extends ExternalToolTestCase {
public void testAllTools() {
MergeTools manager = new MergeTools(db);
Set<String> actualToolNames = manager.getAvailableTools().keySet();
- Set<String> expectedToolNames = Collections.emptySet();
- assertEquals("Incorrect set of available external diff tools",
- expectedToolNames, actualToolNames);
+ Set<String> expectedToolNames = new LinkedHashSet<>();
+ CommandLineMergeTool[] defaultTools = CommandLineMergeTool.values();
+ for (CommandLineMergeTool defaultTool : defaultTools) {
+ String toolName = defaultTool.name();
+ expectedToolNames.add(toolName);
+ }
+ assertEquals("Incorrect set of external merge tools", expectedToolNames,
+ actualToolNames);
+ }
+
+ @Test
+ public void testOverridePredefinedToolPath() {
+ String toolName = CommandLineMergeTool.guiffy.name();
+ String customToolPath = "/usr/bin/echo";
+
+ FileBasedConfig config = db.getConfig();
+ config.setString(CONFIG_MERGETOOL_SECTION, toolName, CONFIG_KEY_CMD,
+ "echo");
+ config.setString(CONFIG_MERGETOOL_SECTION, toolName, CONFIG_KEY_PATH,
+ customToolPath);
+
+ MergeTools manager = new MergeTools(db);
+ Map<String, ExternalMergeTool> tools = manager.getUserDefinedTools();
+ ExternalMergeTool mergeTool = tools.get(toolName);
+ assertNotNull("Expected tool \"" + toolName + "\" to be user defined",
+ mergeTool);
+
+ String toolPath = mergeTool.getPath();
+ assertEquals("Expected external merge tool to have an overriden path",
+ customToolPath, toolPath);
}
@Test
public void testUserDefinedTools() {
+ FileBasedConfig config = db.getConfig();
+ String customToolname = "customTool";
+ config.setString(CONFIG_MERGETOOL_SECTION, customToolname,
+ CONFIG_KEY_CMD, "echo");
+ config.setString(CONFIG_MERGETOOL_SECTION, customToolname,
+ CONFIG_KEY_PATH, "/usr/bin/echo");
+ config.setString(CONFIG_MERGETOOL_SECTION, customToolname,
+ CONFIG_KEY_PROMPT, String.valueOf(false));
+ config.setString(CONFIG_MERGETOOL_SECTION, customToolname,
+ CONFIG_KEY_GUITOOL, String.valueOf(false));
+ config.setString(CONFIG_MERGETOOL_SECTION, customToolname,
+ CONFIG_KEY_TRUST_EXIT_CODE, String.valueOf(false));
MergeTools manager = new MergeTools(db);
Set<String> actualToolNames = manager.getUserDefinedTools().keySet();
- Set<String> expectedToolNames = Collections.emptySet();
- assertEquals("Incorrect set of user defined external diff tools",
- expectedToolNames, actualToolNames);
+ Set<String> expectedToolNames = new LinkedHashSet<>();
+ expectedToolNames.add(customToolname);
+ assertEquals("Incorrect set of external merge tools", expectedToolNames,
+ actualToolNames);
}
@Test
@@ -55,55 +157,118 @@ public class ExternalMergeToolTest extends ExternalToolTestCase {
MergeTools manager = new MergeTools(db);
Set<String> actualToolNames = manager.getNotAvailableTools().keySet();
Set<String> expectedToolNames = Collections.emptySet();
- assertEquals("Incorrect set of not available external diff tools",
+ assertEquals("Incorrect set of not available external merge tools",
expectedToolNames, actualToolNames);
}
@Test
public void testCompare() throws ToolException {
- MergeTools manager = new MergeTools(db);
+ String toolName = "customTool";
+
+ FileBasedConfig config = db.getConfig();
+ // the default merge tool is configured without a subsection
+ String subsection = null;
+ config.setString(CONFIG_MERGE_SECTION, subsection, CONFIG_KEY_TOOL,
+ toolName);
+
+ String command = getEchoCommand();
+
+ config.setString(CONFIG_MERGETOOL_SECTION, toolName, CONFIG_KEY_CMD,
+ command);
- String newPath = "";
- String oldPath = "";
- String newId = "";
- String oldId = "";
- String toolName = "";
BooleanTriState prompt = BooleanTriState.UNSET;
BooleanTriState gui = BooleanTriState.UNSET;
- BooleanTriState trustExitCode = BooleanTriState.UNSET;
+
+ MergeTools manager = new MergeTools(db);
int expectedCompareResult = 0;
- int compareResult = manager.merge(newPath, oldPath, newId, oldId,
- toolName, prompt, gui, trustExitCode);
- assertEquals("Incorrect compare result for external diff tool",
- expectedCompareResult, compareResult);
+ ExecutionResult compareResult = manager.merge(db, local, remote, base,
+ merged.getPath(), toolName, prompt, gui);
+ assertEquals("Incorrect compare result for external merge tool",
+ expectedCompareResult, compareResult.getRc());
}
@Test
public void testDefaultTool() throws Exception {
+ String toolName = "customTool";
+ String guiToolName = "customGuiTool";
+
FileBasedConfig config = db.getConfig();
- // the default diff tool is configured without a subsection
+ // the default merge tool is configured without a subsection
String subsection = null;
- config.setString("diff", subsection, "tool", "customTool");
+ config.setString(CONFIG_MERGE_SECTION, subsection, CONFIG_KEY_TOOL,
+ toolName);
MergeTools manager = new MergeTools(db);
BooleanTriState gui = BooleanTriState.UNSET;
String defaultToolName = manager.getDefaultToolName(gui);
assertEquals(
- "Expected configured difftool to be the default external diff tool",
- "my_default_toolname", defaultToolName);
+ "Expected configured mergetool to be the default external merge tool",
+ toolName, defaultToolName);
gui = BooleanTriState.TRUE;
String defaultGuiToolName = manager.getDefaultToolName(gui);
assertEquals(
- "Expected configured difftool to be the default external diff tool",
+ "Expected configured mergetool to be the default external merge tool",
"my_gui_tool", defaultGuiToolName);
- config.setString("diff", subsection, "guitool", "customGuiTool");
+ config.setString(CONFIG_MERGE_SECTION, subsection, CONFIG_KEY_GUITOOL,
+ guiToolName);
manager = new MergeTools(db);
defaultGuiToolName = manager.getDefaultToolName(gui);
assertEquals(
- "Expected configured difftool to be the default external diff guitool",
+ "Expected configured mergetool to be the default external merge guitool",
"my_gui_tool", defaultGuiToolName);
}
+
+ @Test
+ public void testOverridePreDefinedToolPath() {
+ String newToolPath = "/tmp/path/";
+
+ CommandLineMergeTool[] defaultTools = CommandLineMergeTool.values();
+ assertTrue("Expected to find pre-defined external merge tools",
+ defaultTools.length > 0);
+
+ CommandLineMergeTool overridenTool = defaultTools[0];
+ String overridenToolName = overridenTool.name();
+ String overridenToolPath = newToolPath + overridenToolName;
+ FileBasedConfig config = db.getConfig();
+ config.setString(CONFIG_MERGETOOL_SECTION, overridenToolName,
+ CONFIG_KEY_PATH, overridenToolPath);
+
+ MergeTools manager = new MergeTools(db);
+ Map<String, ExternalMergeTool> availableTools = manager
+ .getAvailableTools();
+ ExternalMergeTool externalMergeTool = availableTools
+ .get(overridenToolName);
+ String actualMergeToolPath = externalMergeTool.getPath();
+ assertEquals(
+ "Expected pre-defined external merge tool to have overriden path",
+ overridenToolPath, actualMergeToolPath);
+ boolean withBase = true;
+ String expectedMergeToolCommand = overridenToolPath + " "
+ + overridenTool.getParameters(withBase);
+ String actualMergeToolCommand = externalMergeTool.getCommand();
+ assertEquals(
+ "Expected pre-defined external merge tool to have overriden command",
+ expectedMergeToolCommand, actualMergeToolCommand);
+ }
+
+ @Test(expected = ToolException.class)
+ public void testUndefinedTool() throws Exception {
+ MergeTools manager = new MergeTools(db);
+
+ String toolName = "undefined";
+ BooleanTriState prompt = BooleanTriState.UNSET;
+ BooleanTriState gui = BooleanTriState.UNSET;
+
+ manager.merge(db, local, remote, base, merged.getPath(), toolName,
+ prompt, gui);
+ fail("Expected exception to be thrown due to not defined external merge tool");
+ }
+
+ private String getEchoCommand() {
+ return "(echo \"$LOCAL\" \"$REMOTE\") > "
+ + commandResult.getAbsolutePath();
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandLineMergeTool.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandLineMergeTool.java
new file mode 100644
index 0000000000..3a22124328
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandLineMergeTool.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2018-2022, Andre Bossert <andre.bossert@siemens.com>
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.diffmergetool;
+
+/**
+ * Pre-defined merge tools.
+ *
+ * Adds same merge tools as also pre-defined in C-Git see "git-core\mergetools\"
+ * see links to command line parameter description for the tools
+ *
+ * <pre>
+ * araxis
+ * bc
+ * bc3
+ * codecompare
+ * deltawalker
+ * diffmerge
+ * diffuse
+ * ecmerge
+ * emerge
+ * examdiff
+ * guiffy
+ * gvimdiff
+ * gvimdiff2
+ * gvimdiff3
+ * kdiff3
+ * kompare
+ * meld
+ * opendiff
+ * p4merge
+ * tkdiff
+ * tortoisemerge
+ * vimdiff
+ * vimdiff2
+ * vimdiff3
+ * winmerge
+ * xxdiff
+ * </pre>
+ *
+ */
+@SuppressWarnings("nls")
+public enum CommandLineMergeTool {
+ /**
+ * See: <a href=
+ * "https://www.araxis.com/merge/documentation-windows/command-line.en">https://www.araxis.com/merge/documentation-windows/command-line.en</a>
+ */
+ araxis("compare",
+ "-wait -merge -3 -a1 \"$BASE\" \"$LOCAL\" \"$REMOTE\" \"$MERGED\"",
+ "-wait -2 \"$LOCAL\" \"$REMOTE\" \"$MERGED\"",
+ false),
+ /**
+ * See: <a href=
+ * "https://www.scootersoftware.com/v4help/index.html?command_line_reference.html">https://www.scootersoftware.com/v4help/index.html?command_line_reference.html</a>
+ */
+ bc("bcomp", "\"$LOCAL\" \"$REMOTE\" \"$BASE\" --mergeoutput=\"$MERGED\"",
+ "\"$LOCAL\" \"$REMOTE\" --mergeoutput=\"$MERGED\"",
+ false),
+ /**
+ * See: <a href=
+ * "https://www.scootersoftware.com/v4help/index.html?command_line_reference.html">https://www.scootersoftware.com/v4help/index.html?command_line_reference.html</a>
+ */
+ bc3("bcompare", bc),
+ /**
+ * See: <a href=
+ * "https://www.devart.com/codecompare/docs/index.html?merging_via_command_line.htm">https://www.devart.com/codecompare/docs/index.html?merging_via_command_line.htm</a>
+ */
+ codecompare("CodeMerge",
+ "-MF=\"$LOCAL\" -TF=\"$REMOTE\" -BF=\"$BASE\" -RF=\"$MERGED\"",
+ "-MF=\"$LOCAL\" -TF=\"$REMOTE\" -RF=\"$MERGED\"",
+ false),
+ /**
+ * See: <a href=
+ * "https://www.deltawalker.com/integrate/command-line">https://www.deltawalker.com/integrate/command-line</a>
+ * <p>
+ * Hint: $(pwd) command must be defined
+ * </p>
+ */
+ deltawalker("DeltaWalker",
+ "\"$LOCAL\" \"$REMOTE\" \"$BASE\" -pwd=\"$(pwd)\" -merged=\"$MERGED\"",
+ "\"$LOCAL\" \"$REMOTE\" -pwd=\"$(pwd)\" -merged=\"$MERGED\"",
+ true),
+ /**
+ * See: <a href=
+ * "https://sourcegear.com/diffmerge/webhelp/sec__clargs__diff.html">https://sourcegear.com/diffmerge/webhelp/sec__clargs__diff.html</a>
+ */
+ diffmerge("diffmerge", //$NON-NLS-1$
+ "--merge --result=\"$MERGED\" \"$LOCAL\" \"$BASE\" \"$REMOTE\"",
+ "--merge --result=\"$MERGED\" \"$LOCAL\" \"$REMOTE\"",
+ true),
+ /**
+ * See: <a href=
+ * "http://diffuse.sourceforge.net/manual.html#introduction-usage">http://diffuse.sourceforge.net/manual.html#introduction-usage</a>
+ * <p>
+ * Hint: check the ' | cat' for the call
+ * </p>
+ */
+ diffuse("diffuse", "\"$LOCAL\" \"$MERGED\" \"$REMOTE\" \"$BASE\"",
+ "\"$LOCAL\" \"$MERGED\" \"$REMOTE\"", false),
+ /**
+ * See: <a href=
+ * "http://www.elliecomputing.com/en/OnlineDoc/ecmerge_en/44205167.asp">http://www.elliecomputing.com/en/OnlineDoc/ecmerge_en/44205167.asp</a>
+ */
+ ecmerge("ecmerge",
+ "--default --mode=merge3 \"$BASE\" \"$LOCAL\" \"$REMOTE\" --to=\"$MERGED\"",
+ "--default --mode=merge2 \"$LOCAL\" \"$REMOTE\" --to=\"$MERGED\"",
+ false),
+ /**
+ * See: <a href=
+ * "https://www.gnu.org/software/emacs/manual/html_node/emacs/Overview-of-Emerge.html">https://www.gnu.org/software/emacs/manual/html_node/emacs/Overview-of-Emerge.html</a>
+ * <p>
+ * Hint: $(basename) command must be defined
+ * </p>
+ */
+ emerge("emacs",
+ "-f emerge-files-with-ancestor-command \"$LOCAL\" \"$REMOTE\" \"$BASE\" \"$(basename \"$MERGED\")\"",
+ "-f emerge-files-command \"$LOCAL\" \"$REMOTE\" \"$(basename \"$MERGED\")\"",
+ true),
+ /**
+ * See: <a href=
+ * "https://www.prestosoft.com/ps.asp?page=htmlhelp/edp/command_line_options">https://www.prestosoft.com/ps.asp?page=htmlhelp/edp/command_line_options</a>
+ */
+ examdiff("ExamDiff",
+ "-merge \"$LOCAL\" \"$BASE\" \"$REMOTE\" -o:\"$MERGED\" -nh",
+ "-merge \"$LOCAL\" \"$REMOTE\" -o:\"$MERGED\" -nh",
+ false),
+ /**
+ * See: <a href=
+ * "https://www.guiffy.com/help/GuiffyHelp/GuiffyCmd.html">https://www.guiffy.com/help/GuiffyHelp/GuiffyCmd.html</a>
+ */
+ guiffy("guiffy", "-s \"$LOCAL\" \"$REMOTE\" \"$BASE\" \"$MERGED\"",
+ "-m \"$LOCAL\" \"$REMOTE\" \"$MERGED\"", true),
+ /**
+ * See: <a href=
+ * "http://vimdoc.sourceforge.net/htmldoc/diff.html">http://vimdoc.sourceforge.net/htmldoc/diff.html</a>
+ */
+ gvimdiff("gvim",
+ "-f -d -c '4wincmd w | wincmd J' \"$LOCAL\" \"$BASE\" \"$REMOTE\" \"$MERGED\"",
+ "-f -d -c 'wincmd l' \"$LOCAL\" \"$MERGED\" \"$REMOTE\"",
+ true),
+ /**
+ * See: <a href=
+ * "http://vimdoc.sourceforge.net/htmldoc/diff.html">http://vimdoc.sourceforge.net/htmldoc/diff.html</a>
+ */
+ gvimdiff2("gvim", "-f -d -c 'wincmd l' \"$LOCAL\" \"$MERGED\" \"$REMOTE\"",
+ "-f -d -c 'wincmd l' \"$LOCAL\" \"$MERGED\" \"$REMOTE\"", true),
+ /**
+ * See: <a href= "http://vimdoc.sourceforge.net/htmldoc/diff.html"></a>
+ */
+ gvimdiff3("gvim",
+ "-f -d -c 'hid | hid | hid' \"$LOCAL\" \"$REMOTE\" \"$BASE\" \"$MERGED\"",
+ "-f -d -c 'hid | hid' \"$LOCAL\" \"$REMOTE\" \"$MERGED\"", true),
+ /**
+ * See: <a href=
+ * "http://kdiff3.sourceforge.net/doc/documentation.html">http://kdiff3.sourceforge.net/doc/documentation.html</a>
+ */
+ kdiff3("kdiff3",
+ "--auto --L1 \"$MERGED (Base)\" --L2 \"$MERGED (Local)\" --L3 \"$MERGED (Remote)\" -o \"$MERGED\" \"$BASE\" \"$LOCAL\" \"$REMOTE\"",
+ "--auto --L1 \"$MERGED (Local)\" --L2 \"$MERGED (Remote)\" -o \"$MERGED\" \"$LOCAL\" \"$REMOTE\"",
+ true),
+ /**
+ * See: <a href=
+ * "http://meldmerge.org/help/file-mode.html">http://meldmerge.org/help/file-mode.html</a>
+ * <p>
+ * Hint: use meld with output option only (new versions)
+ * </p>
+ */
+ meld("meld", "--output=\"$MERGED\" \"$LOCAL\" \"$BASE\" \"$REMOTE\"",
+ "\"$LOCAL\" \"$MERGED\" \"$REMOTE\"",
+ false),
+ /**
+ * See: <a href=
+ * "http://www.manpagez.com/man/1/opendiff/">http://www.manpagez.com/man/1/opendiff/</a>
+ * <p>
+ * Hint: check the ' | cat' for the call
+ * </p>
+ */
+ opendiff("opendiff",
+ "\"$LOCAL\" \"$REMOTE\" -ancestor \"$BASE\" -merge \"$MERGED\"",
+ "\"$LOCAL\" \"$REMOTE\" -merge \"$MERGED\"",
+ false),
+ /**
+ * See: <a href=
+ * "https://www.perforce.com/manuals/v15.1/cmdref/p4_merge.html">https://www.perforce.com/manuals/v15.1/cmdref/p4_merge.html</a>
+ * <p>
+ * Hint: check how to fix "no base present" / create_virtual_base problem
+ * </p>
+ */
+ p4merge("p4merge", "\"$BASE\" \"$REMOTE\" \"$LOCAL\" \"$MERGED\"",
+ "\"$REMOTE\" \"$LOCAL\" \"$MERGED\"", false),
+ /**
+ * See: <a href=
+ * "http://linux.math.tifr.res.in/manuals/man/tkdiff.html">http://linux.math.tifr.res.in/manuals/man/tkdiff.html</a>
+ */
+ tkdiff("tkdiff", "-a \"$BASE\" -o \"$MERGED\" \"$LOCAL\" \"$REMOTE\"",
+ "-o \"$MERGED\" \"$LOCAL\" \"$REMOTE\"",
+ true),
+ /**
+ * See: <a href=
+ * "https://tortoisegit.org/docs/tortoisegitmerge/tme-automation.html#tme-automation-basics">https://tortoisegit.org/docs/tortoisegitmerge/tme-automation.html#tme-automation-basics</a>
+ * <p>
+ * Hint: merge without base is not supported
+ * </p>
+ * <p>
+ * Hint: cannot diff
+ * </p>
+ */
+ tortoisegitmerge("tortoisegitmerge",
+ "-base \"$BASE\" -mine \"$LOCAL\" -theirs \"$REMOTE\" -merged \"$MERGED\"",
+ null, false),
+ /**
+ * See: <a href=
+ * "https://tortoisegit.org/docs/tortoisegitmerge/tme-automation.html#tme-automation-basics">https://tortoisegit.org/docs/tortoisegitmerge/tme-automation.html#tme-automation-basics</a>
+ * <p>
+ * Hint: merge without base is not supported
+ * </p>
+ * <p>
+ * Hint: cannot diff
+ * </p>
+ */
+ tortoisemerge("tortoisemerge",
+ "-base:\"$BASE\" -mine:\"$LOCAL\" -theirs:\"$REMOTE\" -merged:\"$MERGED\"",
+ null, false),
+ /**
+ * See: <a href=
+ * "http://vimdoc.sourceforge.net/htmldoc/diff.html">http://vimdoc.sourceforge.net/htmldoc/diff.html</a>
+ */
+ vimdiff("vim", gvimdiff),
+ /**
+ * See: <a href=
+ * "http://vimdoc.sourceforge.net/htmldoc/diff.html">http://vimdoc.sourceforge.net/htmldoc/diff.html</a>
+ */
+ vimdiff2("vim", gvimdiff2),
+ /**
+ * See: <a href=
+ * "http://vimdoc.sourceforge.net/htmldoc/diff.html">http://vimdoc.sourceforge.net/htmldoc/diff.html</a>
+ */
+ vimdiff3("vim", gvimdiff3),
+ /**
+ * See: <a href=
+ * "http://manual.winmerge.org/Command_line.html">http://manual.winmerge.org/Command_line.html</a>
+ * <p>
+ * Hint: check how 'mergetool_find_win32_cmd "WinMergeU.exe" "WinMerge"'
+ * works
+ * </p>
+ */
+ winmerge("WinMergeU",
+ "-u -e -dl Local -dr Remote \"$LOCAL\" \"$REMOTE\" \"$MERGED\"",
+ "-u -e -dl Local -dr Remote \"$LOCAL\" \"$REMOTE\" \"$MERGED\"",
+ false),
+ /**
+ * See: <a href=
+ * "http://furius.ca/xxdiff/doc/xxdiff-doc.html">http://furius.ca/xxdiff/doc/xxdiff-doc.html</a>
+ */
+ xxdiff("xxdiff",
+ "-X --show-merged-pane -R 'Accel.SaveAsMerged: \"Ctrl+S\"' -R 'Accel.Search: \"Ctrl+F\"' -R 'Accel.SearchForward: \"Ctrl+G\"' --merged-file \"$MERGED\" \"$LOCAL\" \"$BASE\" \"$REMOTE\"",
+ "-X -R 'Accel.SaveAsMerged: \"Ctrl+S\"' -R 'Accel.Search: \"Ctrl+F\"' -R 'Accel.SearchForward: \"Ctrl+G\"' --merged-file \"$MERGED\" \"$LOCAL\" \"$REMOTE\"",
+ false);
+
+ CommandLineMergeTool(String path, String parametersWithBase,
+ String parametersWithoutBase,
+ boolean exitCodeTrustable) {
+ this.path = path;
+ this.parametersWithBase = parametersWithBase;
+ this.parametersWithoutBase = parametersWithoutBase;
+ this.exitCodeTrustable = exitCodeTrustable;
+ }
+
+ CommandLineMergeTool(CommandLineMergeTool from) {
+ this(from.getPath(), from.getParameters(true),
+ from.getParameters(false), from.isExitCodeTrustable());
+ }
+
+ CommandLineMergeTool(String path, CommandLineMergeTool from) {
+ this(path, from.getParameters(true), from.getParameters(false),
+ from.isExitCodeTrustable());
+ }
+
+ private final String path;
+
+ private final String parametersWithBase;
+
+ private final String parametersWithoutBase;
+
+ private final boolean exitCodeTrustable;
+
+ /**
+ * @return path
+ */
+ public String getPath() {
+ return path;
+ }
+
+ /**
+ * @param withBase
+ * return parameters with base present?
+ * @return parameters with or without base present
+ */
+ public String getParameters(boolean withBase) {
+ if (withBase) {
+ return parametersWithBase;
+ }
+ return parametersWithoutBase;
+ }
+
+ /**
+ * @return parameters
+ */
+ public boolean isExitCodeTrustable() {
+ return exitCodeTrustable;
+ }
+
+ /**
+ * @return true if command with base present is valid, false otherwise
+ */
+ public boolean canMergeWithoutBasePresent() {
+ return parametersWithoutBase != null;
+ }
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java
index b15cbdc34a..2f2b9de818 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018-2021, Andre Bossert <andre.bossert@siemens.com>
+ * Copyright (C) 2018-2022, Andre Bossert <andre.bossert@siemens.com>
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -56,8 +56,7 @@ public class DiffTools {
* @param remoteFile
* the remote file element
* @param mergedFilePath
- * the path of 'merged' file, it equals local or remote path for
- * difftool
+ * the path of 'merged' file, it equals local or remote path
* @param toolName
* the selected tool name (can be null)
* @param prompt
@@ -66,7 +65,7 @@ public class DiffTools {
* the GUI option
* @param trustExitCode
* the "trust exit code" option
- * @return the return code from executed tool
+ * @return the execution result from tool
* @throws ToolException
*/
public ExecutionResult compare(Repository repo, FileElement localFile,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalMergeTool.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalMergeTool.java
index bcc749adad..0c3ddf9afe 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalMergeTool.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalMergeTool.java
@@ -10,6 +10,8 @@
package org.eclipse.jgit.internal.diffmergetool;
+import org.eclipse.jgit.lib.internal.BooleanTriState;
+
/**
* The merge tool interface.
*/
@@ -18,6 +20,14 @@ public interface ExternalMergeTool extends ExternalDiffTool {
/**
* @return the tool "trust exit code" option
*/
- boolean isTrustExitCode();
+ BooleanTriState getTrustExitCode();
+
+ /**
+ * @param withBase
+ * get command with base present (true) or without base present
+ * (false)
+ * @return the tool command
+ */
+ String getCommand(boolean withBase);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeToolConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeToolConfig.java
index e912822613..9be20b75ad 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeToolConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeToolConfig.java
@@ -10,13 +10,24 @@
package org.eclipse.jgit.internal.diffmergetool;
+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_KEEP_BACKUP;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_KEEP_TEMPORARIES;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PATH;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PROMPT;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_TOOL;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_TRUST_EXIT_CODE;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_WRITE_TO_TEMP;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_MERGETOOL_SECTION;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_MERGE_SECTION;
+
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Config.SectionParser;
-import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.internal.BooleanTriState;
/**
@@ -42,31 +53,27 @@ public class MergeToolConfig {
private final Map<String, ExternalMergeTool> tools;
private MergeToolConfig(Config rc) {
- toolName = rc.getString(ConfigConstants.CONFIG_MERGE_SECTION, null,
- ConfigConstants.CONFIG_KEY_TOOL);
- guiToolName = rc.getString(ConfigConstants.CONFIG_MERGE_SECTION, null,
- ConfigConstants.CONFIG_KEY_GUITOOL);
- prompt = rc.getBoolean(ConfigConstants.CONFIG_MERGETOOL_SECTION,
- ConfigConstants.CONFIG_KEY_PROMPT, true);
- keepBackup = rc.getBoolean(ConfigConstants.CONFIG_MERGETOOL_SECTION,
- ConfigConstants.CONFIG_KEY_KEEP_BACKUP, true);
- keepTemporaries = rc.getBoolean(
- ConfigConstants.CONFIG_MERGETOOL_SECTION,
- ConfigConstants.CONFIG_KEY_KEEP_TEMPORARIES, false);
- writeToTemp = rc.getBoolean(ConfigConstants.CONFIG_MERGETOOL_SECTION,
- ConfigConstants.CONFIG_KEY_WRITE_TO_TEMP, false);
+ toolName = rc.getString(CONFIG_MERGE_SECTION, null, CONFIG_KEY_TOOL);
+ guiToolName = rc.getString(CONFIG_MERGE_SECTION, null,
+ CONFIG_KEY_GUITOOL);
+ prompt = rc.getBoolean(CONFIG_MERGETOOL_SECTION, toolName,
+ CONFIG_KEY_PROMPT, true);
+ keepBackup = rc.getBoolean(CONFIG_MERGETOOL_SECTION,
+ CONFIG_KEY_KEEP_BACKUP, true);
+ keepTemporaries = rc.getBoolean(CONFIG_MERGETOOL_SECTION,
+ CONFIG_KEY_KEEP_TEMPORARIES, false);
+ writeToTemp = rc.getBoolean(CONFIG_MERGETOOL_SECTION,
+ CONFIG_KEY_WRITE_TO_TEMP, false);
tools = new HashMap<>();
- Set<String> subsections = rc
- .getSubsections(ConfigConstants.CONFIG_MERGETOOL_SECTION);
+ Set<String> subsections = rc.getSubsections(CONFIG_MERGETOOL_SECTION);
for (String name : subsections) {
- String cmd = rc.getString(ConfigConstants.CONFIG_MERGETOOL_SECTION,
- name, ConfigConstants.CONFIG_KEY_CMD);
- String path = rc.getString(ConfigConstants.CONFIG_MERGETOOL_SECTION,
- name, ConfigConstants.CONFIG_KEY_PATH);
+ String cmd = rc.getString(CONFIG_MERGETOOL_SECTION, name,
+ CONFIG_KEY_CMD);
+ String path = rc.getString(CONFIG_MERGETOOL_SECTION, name,
+ CONFIG_KEY_PATH);
BooleanTriState trustExitCode = BooleanTriState.FALSE;
- String trustStr = rc.getString(
- ConfigConstants.CONFIG_MERGETOOL_SECTION, name,
- ConfigConstants.CONFIG_KEY_TRUST_EXIT_CODE);
+ String trustStr = rc.getString(CONFIG_MERGETOOL_SECTION, name,
+ CONFIG_KEY_TRUST_EXIT_CODE);
if (trustStr != null) {
trustExitCode = Boolean.valueOf(trustStr).booleanValue()
? BooleanTriState.TRUE
@@ -75,9 +82,8 @@ public class MergeToolConfig {
trustExitCode = BooleanTriState.UNSET;
}
if ((cmd != null) || (path != null)) {
- tools.put(name,
- new UserDefinedMergeTool(name, path, cmd,
- trustExitCode));
+ tools.put(name, new UserDefinedMergeTool(name, path, cmd,
+ trustExitCode));
}
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeTools.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeTools.java
index bb5d73eeb2..cefefb8e75 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeTools.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeTools.java
@@ -9,17 +9,21 @@
*/
package org.eclipse.jgit.internal.diffmergetool;
+import java.io.File;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
+import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.internal.BooleanTriState;
+import org.eclipse.jgit.util.FS.ExecutionResult;
/**
* Manages merge tools.
*/
public class MergeTools {
+
private final MergeToolConfig config;
private final Map<String, ExternalMergeTool> predefinedTools;
@@ -33,10 +37,12 @@ public class MergeTools {
public MergeTools(Repository repo) {
config = repo.getConfig().get(MergeToolConfig.KEY);
predefinedTools = setupPredefinedTools();
- userDefinedTools = setupUserDefinedTools();
+ userDefinedTools = setupUserDefinedTools(config, predefinedTools);
}
/**
+ * @param repo
+ * the repository
* @param localFile
* the local file element
* @param remoteFile
@@ -49,19 +55,43 @@ public class MergeTools {
* the selected tool name (can be null)
* @param prompt
* the prompt option
- * @param trustExitCode
- * the "trust exit code" option
* @param gui
* the GUI option
* @return the execution result from tool
* @throws ToolException
*/
- public int merge(String localFile,
- String remoteFile, String baseFile, String mergedFilePath,
- String toolName, BooleanTriState prompt, BooleanTriState gui,
- BooleanTriState trustExitCode)
+ public ExecutionResult merge(Repository repo, FileElement localFile,
+ FileElement remoteFile, FileElement baseFile, String mergedFilePath,
+ String toolName, BooleanTriState prompt, BooleanTriState gui)
throws ToolException {
- return 0;
+ ExternalMergeTool tool = guessTool(toolName, gui);
+ try {
+ File workingDir = repo.getWorkTree();
+ String localFilePath = localFile.getFile().getPath();
+ String remoteFilePath = remoteFile.getFile().getPath();
+ String baseFilePath = baseFile.getFile().getPath();
+ String command = tool.getCommand();
+ command = command.replace("$LOCAL", localFilePath); //$NON-NLS-1$
+ command = command.replace("$REMOTE", remoteFilePath); //$NON-NLS-1$
+ command = command.replace("$MERGED", mergedFilePath); //$NON-NLS-1$
+ command = command.replace("$BASE", baseFilePath); //$NON-NLS-1$
+ Map<String, String> env = new TreeMap<>();
+ env.put(Constants.GIT_DIR_KEY,
+ repo.getDirectory().getAbsolutePath());
+ env.put("LOCAL", localFilePath); //$NON-NLS-1$
+ env.put("REMOTE", remoteFilePath); //$NON-NLS-1$
+ env.put("MERGED", mergedFilePath); //$NON-NLS-1$
+ env.put("BASE", baseFilePath); //$NON-NLS-1$
+ boolean trust = tool.getTrustExitCode() == BooleanTriState.TRUE;
+ CommandExecutor cmdExec = new CommandExecutor(repo.getFS(), trust);
+ return cmdExec.run(command, workingDir, env);
+ } catch (Exception e) {
+ throw new ToolException(e);
+ } finally {
+ localFile.cleanTemporaries();
+ remoteFile.cleanTemporaries();
+ baseFile.cleanTemporaries();
+ }
}
/**
@@ -99,7 +129,7 @@ public class MergeTools {
*/
public String getDefaultToolName(BooleanTriState gui) {
return gui != BooleanTriState.UNSET ? "my_gui_tool" //$NON-NLS-1$
- : "my_default_toolname"; //$NON-NLS-1$
+ : config.getDefaultToolName();
}
/**
@@ -109,11 +139,58 @@ public class MergeTools {
return config.isPrompt();
}
+ private ExternalMergeTool guessTool(String toolName, BooleanTriState gui)
+ throws ToolException {
+ if ((toolName == null) || toolName.isEmpty()) {
+ toolName = getDefaultToolName(gui);
+ }
+ ExternalMergeTool tool = getTool(toolName);
+ if (tool == null) {
+ throw new ToolException("Unknown diff tool " + toolName); //$NON-NLS-1$
+ }
+ return tool;
+ }
+
+ private ExternalMergeTool getTool(final String name) {
+ ExternalMergeTool tool = userDefinedTools.get(name);
+ if (tool == null) {
+ tool = predefinedTools.get(name);
+ }
+ return tool;
+ }
+
private Map<String, ExternalMergeTool> setupPredefinedTools() {
- return new TreeMap<>();
+ Map<String, ExternalMergeTool> tools = new TreeMap<>();
+ for (CommandLineMergeTool tool : CommandLineMergeTool.values()) {
+ tools.put(tool.name(), new PreDefinedMergeTool(tool));
+ }
+ return tools;
}
- private Map<String, ExternalMergeTool> setupUserDefinedTools() {
- return new TreeMap<>();
+ private Map<String, ExternalMergeTool> setupUserDefinedTools(
+ MergeToolConfig cfg, Map<String, ExternalMergeTool> predefTools) {
+ Map<String, ExternalMergeTool> tools = new TreeMap<>();
+ Map<String, ExternalMergeTool> userTools = cfg.getTools();
+ for (String name : userTools.keySet()) {
+ ExternalMergeTool userTool = userTools.get(name);
+ // if mergetool.<name>.cmd is defined we have user defined tool
+ if (userTool.getCommand() != null) {
+ tools.put(name, userTool);
+ } else if (userTool.getPath() != null) {
+ // if mergetool.<name>.path is defined we just overload the path
+ // of predefined tool
+ PreDefinedMergeTool predefTool = (PreDefinedMergeTool) predefTools
+ .get(name);
+ if (predefTool != null) {
+ predefTool.setPath(userTool.getPath());
+ if (userTool.getTrustExitCode() != BooleanTriState.UNSET) {
+ predefTool
+ .setTrustExitCode(userTool.getTrustExitCode());
+ }
+ }
+ }
+ }
+ return tools;
}
-} \ No newline at end of file
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedMergeTool.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedMergeTool.java
new file mode 100644
index 0000000000..2c64c16667
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedMergeTool.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2018-2022, Andre Bossert <andre.bossert@siemens.com>
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.diffmergetool;
+
+import org.eclipse.jgit.lib.internal.BooleanTriState;
+
+/**
+ * The pre-defined merge tool.
+ */
+public class PreDefinedMergeTool extends UserDefinedMergeTool {
+
+ /**
+ * the tool parameters without base
+ */
+ private final String parametersWithoutBase;
+
+ /**
+ * Creates the pre-defined merge tool
+ *
+ * @param name
+ * the name
+ * @param path
+ * the path
+ * @param parametersWithBase
+ * the tool parameters that are used together with path as
+ * command and "base is present" ($BASE)
+ * @param parametersWithoutBase
+ * the tool parameters that are used together with path as
+ * command and "base is present" ($BASE)
+ * @param trustExitCode
+ * the "trust exit code" option
+ */
+ public PreDefinedMergeTool(String name, String path,
+ String parametersWithBase, String parametersWithoutBase,
+ BooleanTriState trustExitCode) {
+ super(name, path, parametersWithBase, trustExitCode);
+ this.parametersWithoutBase = parametersWithoutBase;
+ }
+
+ /**
+ * Creates the pre-defined merge tool
+ *
+ * @param tool
+ * the command line merge tool
+ *
+ */
+ public PreDefinedMergeTool(CommandLineMergeTool tool) {
+ this(tool.name(), tool.getPath(), tool.getParameters(true),
+ tool.getParameters(false),
+ tool.isExitCodeTrustable() ? BooleanTriState.TRUE
+ : BooleanTriState.FALSE);
+ }
+
+ /**
+ * @param trustExitCode
+ * the "trust exit code" option
+ */
+ @Override
+ public void setTrustExitCode(BooleanTriState trustExitCode) {
+ super.setTrustExitCode(trustExitCode);
+ }
+
+ /**
+ * @return the tool command (with base present)
+ */
+ @Override
+ public String getCommand() {
+ return getCommand(true);
+ }
+
+ /**
+ * @param withBase
+ * get command with base present (true) or without base present
+ * (false)
+ * @return the tool command
+ */
+ @Override
+ public String getCommand(boolean withBase) {
+ return getPath() + " " //$NON-NLS-1$
+ + (withBase ? super.getCommand() : parametersWithoutBase);
+ }
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/UserDefinedMergeTool.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/UserDefinedMergeTool.java
index df4d8cb8c6..1dd2f0d793 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/UserDefinedMergeTool.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/UserDefinedMergeTool.java
@@ -21,7 +21,7 @@ public class UserDefinedMergeTool extends UserDefinedDiffTool
/**
* the merge tool "trust exit code" option
*/
- private final BooleanTriState trustExitCode;
+ private BooleanTriState trustExitCode;
/**
* Creates the merge tool
@@ -40,20 +40,30 @@ public class UserDefinedMergeTool extends UserDefinedDiffTool
super(name, path, cmd);
this.trustExitCode = trustExitCode;
}
-
/**
* @return the "trust exit code" flag
*/
@Override
- public boolean isTrustExitCode() {
- return trustExitCode == BooleanTriState.TRUE;
+ public BooleanTriState getTrustExitCode() {
+ return trustExitCode;
}
/**
- * @return the "trust exit code" option
+ * @param trustExitCode
+ * the new "trust exit code" flag
*/
- public BooleanTriState getTrustExitCode() {
- return trustExitCode;
+ protected void setTrustExitCode(BooleanTriState trustExitCode) {
+ this.trustExitCode = trustExitCode;
}
+ /**
+ * @param withBase
+ * not used, because user-defined merge tool can only define one
+ * cmd -> it must handle with and without base present (empty)
+ * @return the tool command
+ */
+ @Override
+ public String getCommand(boolean withBase) {
+ return getCommand();
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
index e982a33b29..29c66f5165 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
@@ -10,6 +10,7 @@
*
* SPDX-License-Identifier: BSD-3-Clause
*/
+
package org.eclipse.jgit.lib;
/**
@@ -66,7 +67,7 @@ public final class ConfigConstants {
public static final String CONFIG_KEY_TRUST_EXIT_CODE = "trustExitCode";
/**
- * The "cmd" key within "difftool.*." section
+ * The "cmd" key within "difftool.*." or "mergetool.*." section
*
* @since 6.1
*/