Merge "Add config reader for user-defined mergetools"
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
new file mode 100644
index 0000000..96fd102
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalMergeToolTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2020-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.internal.diffmergetool;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Collections;
+import java.util.Set;
+
+import org.eclipse.jgit.lib.internal.BooleanTriState;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.junit.Test;
+
+/**
+ * Testing external merge tools.
+ */
+public class ExternalMergeToolTest extends ExternalToolTestCase {
+
+	@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",
+				expectedToolNames, actualToolNames);
+	}
+
+	@Test
+	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);
+	}
+
+	@Test
+	public void testUserDefinedTools() {
+		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);
+	}
+
+	@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 diff tools",
+				expectedToolNames, actualToolNames);
+	}
+
+	@Test
+	public void testCompare() throws ToolException {
+		MergeTools manager = new MergeTools(db);
+
+		String newPath = "";
+		String oldPath = "";
+		String newId = "";
+		String oldId = "";
+		String toolName = "";
+		BooleanTriState prompt = BooleanTriState.UNSET;
+		BooleanTriState gui = BooleanTriState.UNSET;
+		BooleanTriState trustExitCode = BooleanTriState.UNSET;
+
+		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);
+	}
+
+	@Test
+	public void testDefaultTool() throws Exception {
+		FileBasedConfig config = db.getConfig();
+		// the default diff tool is configured without a subsection
+		String subsection = null;
+		config.setString("diff", subsection, "tool", "customTool");
+
+		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);
+
+		gui = BooleanTriState.TRUE;
+		String defaultGuiToolName = manager.getDefaultToolName(gui);
+		assertEquals(
+				"Expected configured difftool to be the default external diff tool",
+				"my_gui_tool", defaultGuiToolName);
+
+		config.setString("diff", subsection, "guitool", "customGuiTool");
+		manager = new MergeTools(db);
+		defaultGuiToolName = manager.getDefaultToolName(gui);
+		assertEquals(
+				"Expected configured difftool to be the default external diff guitool",
+				"my_gui_tool", defaultGuiToolName);
+	}
+}
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
new file mode 100644
index 0000000..bcc749a
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalMergeTool.java
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+/**
+ * The merge tool interface.
+ */
+public interface ExternalMergeTool extends ExternalDiffTool {
+
+	/**
+	 * @return the tool "trust exit code" option
+	 */
+	boolean isTrustExitCode();
+
+}
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
new file mode 100644
index 0000000..e912822
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeToolConfig.java
@@ -0,0 +1,141 @@
+/*
+ * 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 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;
+
+/**
+ * Keeps track of difftool related configuration options.
+ */
+public class MergeToolConfig {
+
+	/** Key for {@link Config#get(SectionParser)}. */
+	public static final Config.SectionParser<MergeToolConfig> KEY = MergeToolConfig::new;
+
+	private final String toolName;
+
+	private final String guiToolName;
+
+	private final boolean prompt;
+
+	private final boolean keepBackup;
+
+	private final boolean keepTemporaries;
+
+	private final boolean writeToTemp;
+
+	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);
+		tools = new HashMap<>();
+		Set<String> subsections = rc
+				.getSubsections(ConfigConstants.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);
+			BooleanTriState trustExitCode = BooleanTriState.FALSE;
+			String trustStr = rc.getString(
+					ConfigConstants.CONFIG_MERGETOOL_SECTION, name,
+					ConfigConstants.CONFIG_KEY_TRUST_EXIT_CODE);
+			if (trustStr != null) {
+				trustExitCode = Boolean.valueOf(trustStr).booleanValue()
+						? BooleanTriState.TRUE
+						: BooleanTriState.FALSE;
+			} else {
+				trustExitCode = BooleanTriState.UNSET;
+			}
+			if ((cmd != null) || (path != null)) {
+				tools.put(name,
+						new UserDefinedMergeTool(name, path, cmd,
+								trustExitCode));
+			}
+		}
+	}
+
+	/**
+	 * @return the default merge tool name (merge.tool)
+	 */
+	public String getDefaultToolName() {
+		return toolName;
+	}
+
+	/**
+	 * @return the default GUI merge tool name (merge.guitool)
+	 */
+	public String getDefaultGuiToolName() {
+		return guiToolName;
+	}
+
+	/**
+	 * @return the merge tool "prompt" option (mergetool.prompt)
+	 */
+	public boolean isPrompt() {
+		return prompt;
+	}
+
+	/**
+	 * @return the tool "keep backup" option
+	 */
+	public boolean isKeepBackup() {
+		return keepBackup;
+	}
+
+	/**
+	 * @return the tool "keepTemporaries" option
+	 */
+	public boolean isKeepTemporaries() {
+		return keepTemporaries;
+	}
+
+	/**
+	 * @return the tool "write to temp" option
+	 */
+	public boolean isWriteToTemp() {
+		return writeToTemp;
+	}
+
+	/**
+	 * @return the tools map
+	 */
+	public Map<String, ExternalMergeTool> getTools() {
+		return tools;
+	}
+
+	/**
+	 * @return the tool names
+	 */
+	public Set<String> getToolNames() {
+		return tools.keySet();
+	}
+
+}
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
new file mode 100644
index 0000000..bb5d73e
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeTools.java
@@ -0,0 +1,119 @@
+/*
+ * 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 java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.internal.BooleanTriState;
+
+/**
+ * Manages merge tools.
+ */
+public class MergeTools {
+	private final MergeToolConfig config;
+
+	private final Map<String, ExternalMergeTool> predefinedTools;
+
+	private final Map<String, ExternalMergeTool> userDefinedTools;
+
+	/**
+	 * @param repo
+	 *            the repository database
+	 */
+	public MergeTools(Repository repo) {
+		config = repo.getConfig().get(MergeToolConfig.KEY);
+		predefinedTools = setupPredefinedTools();
+		userDefinedTools = setupUserDefinedTools();
+	}
+
+	/**
+	 * @param localFile
+	 *            the local file element
+	 * @param remoteFile
+	 *            the remote file element
+	 * @param baseFile
+	 *            the base file element
+	 * @param mergedFilePath
+	 *            the path of 'merged' file
+	 * @param toolName
+	 *            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)
+			throws ToolException {
+		return 0;
+	}
+
+	/**
+	 * @return the tool names
+	 */
+	public Set<String> getToolNames() {
+		return config.getToolNames();
+	}
+
+	/**
+	 * @return the user defined tools
+	 */
+	public Map<String, ExternalMergeTool> getUserDefinedTools() {
+		return userDefinedTools;
+	}
+
+	/**
+	 * @return the available predefined tools
+	 */
+	public Map<String, ExternalMergeTool> getAvailableTools() {
+		return predefinedTools;
+	}
+
+	/**
+	 * @return the NOT available predefined tools
+	 */
+	public Map<String, ExternalMergeTool> getNotAvailableTools() {
+		return new TreeMap<>();
+	}
+
+	/**
+	 * @param gui
+	 *            use the diff.guitool setting ?
+	 * @return the default tool name
+	 */
+	public String getDefaultToolName(BooleanTriState gui) {
+		return gui != BooleanTriState.UNSET ? "my_gui_tool" //$NON-NLS-1$
+				: "my_default_toolname"; //$NON-NLS-1$
+	}
+
+	/**
+	 * @return is interactive (config prompt enabled) ?
+	 */
+	public boolean isInteractive() {
+		return config.isPrompt();
+	}
+
+	private Map<String, ExternalMergeTool> setupPredefinedTools() {
+		return new TreeMap<>();
+	}
+
+	private Map<String, ExternalMergeTool> setupUserDefinedTools() {
+		return new TreeMap<>();
+	}
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..df4d8cb
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/UserDefinedMergeTool.java
@@ -0,0 +1,59 @@
+/*
+ * 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 user-defined merge tool.
+ */
+public class UserDefinedMergeTool extends UserDefinedDiffTool
+		implements ExternalMergeTool {
+
+	/**
+	 * the merge tool "trust exit code" option
+	 */
+	private final BooleanTriState trustExitCode;
+
+	/**
+	 * Creates the merge tool
+	 *
+	 * @param name
+	 *            the name
+	 * @param path
+	 *            the path
+	 * @param cmd
+	 *            the command
+	 * @param trustExitCode
+	 *            the "trust exit code" option
+	 */
+	public UserDefinedMergeTool(String name, String path, String cmd,
+			BooleanTriState trustExitCode) {
+		super(name, path, cmd);
+		this.trustExitCode = trustExitCode;
+	}
+
+	/**
+	 * @return the "trust exit code" flag
+	 */
+	@Override
+	public boolean isTrustExitCode() {
+		return trustExitCode == BooleanTriState.TRUE;
+	}
+
+	/**
+	 * @return the "trust exit code" option
+	 */
+	public BooleanTriState getTrustExitCode() {
+		return trustExitCode;
+	}
+
+}
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 8ad32d4..e982a33 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
@@ -31,14 +31,14 @@ public final class ConfigConstants {
 	public static final String CONFIG_DIFF_SECTION = "diff";
 
 	/**
-	 * The "tool" key within "diff" section
+	 * The "tool" key within "diff" or "merge" section
 	 *
 	 * @since 6.1
 	 */
 	public static final String CONFIG_KEY_TOOL = "tool";
 
 	/**
-	 * The "guitool" key within "diff" section
+	 * The "guitool" key within "diff" or "merge" section
 	 *
 	 * @since 6.1
 	 */
@@ -52,14 +52,14 @@ public final class ConfigConstants {
 	public static final String CONFIG_DIFFTOOL_SECTION = "difftool";
 
 	/**
-	 * The "prompt" key within "difftool" section
+	 * The "prompt" key within "difftool" or "mergetool" section
 	 *
 	 * @since 6.1
 	 */
 	public static final String CONFIG_KEY_PROMPT = "prompt";
 
 	/**
-	 * The "trustExitCode" key within "difftool" section
+	 * The "trustExitCode" key within "difftool" or "mergetool.<name>." section
 	 *
 	 * @since 6.1
 	 */
@@ -124,6 +124,34 @@ public final class ConfigConstants {
 	public static final String CONFIG_MERGE_SECTION = "merge";
 
 	/**
+	 * The "mergetool" section
+	 *
+	 * @since 5.13
+	 */
+	public static final String CONFIG_MERGETOOL_SECTION = "mergetool";
+
+	/**
+	 * The "keepBackup" key within "mergetool" section
+	 *
+	 * @since 5.13
+	 */
+	public static final String CONFIG_KEY_KEEP_BACKUP = "keepBackup";
+
+	/**
+	 * The "keepTemporaries" key within "mergetool" section
+	 *
+	 * @since 5.13
+	 */
+	public static final String CONFIG_KEY_KEEP_TEMPORARIES = "keepTemporaries";
+
+	/**
+	 * The "writeToTemp" key within "mergetool" section
+	 *
+	 * @since 5.13
+	 */
+	public static final String CONFIG_KEY_WRITE_TO_TEMP = "writeToTemp";
+
+	/**
 	 * The "filter" section
 	 * @since 4.6
 	 */