Add command line support for "git mergetool"

see: https://git-scm.com/docs/git-mergetool
see: https://git-scm.com/docs/git-config

* add command line support for "git mergetool"
  * add option handling for "--tool-help", "--tool=<mytool>",
"--[no-]prompt",  "--[no-]gui"
  * handle prompt
  * add MergeTools
  * add pre-defined mergetools
  * print merge actions --> no execute, will be done later

Bug: 356832
Change-Id: I6e505ffc3d03f75ecf4bba452a25d25dfcf5793f
Signed-off-by: Andre Bossert <andre.bossert@siemens.com>
This commit is contained in:
Andre Bossert 2020-01-19 20:52:56 +01:00 committed by Andrey Loskutov
parent 24171b05f0
commit 8573435635
16 changed files with 1272 additions and 174 deletions

View File

@ -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 * 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
@ -14,68 +14,30 @@
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_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.junit.Assert.assertEquals;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
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 org.eclipse.jgit.api.Git;
import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.internal.diffmergetool.CommandLineDiffTool; import org.eclipse.jgit.internal.diffmergetool.CommandLineDiffTool;
import org.eclipse.jgit.lib.CLIRepositoryTestCase;
import org.eclipse.jgit.lib.StoredConfig; 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.revwalk.RevCommit;
import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.kohsuke.args4j.Argument;
/** /**
* Testing the {@code difftool} command. * Testing the {@code difftool} command.
*/ */
public class DiffToolTest extends CLIRepositoryTestCase { public class DiffToolTest extends ExternalToolTestCase {
public static class GitCliJGitWrapperParser {
@Argument(index = 0, metaVar = "metaVar_command", required = true, handler = SubcommandHandler.class)
TextBuiltin subcommand;
@Argument(index = 1, metaVar = "metaVar_arg") private static final String DIFF_TOOL = CONFIG_DIFFTOOL_SECTION;
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;
@Override @Override
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
super.setUp(); super.setUp();
git = new Git(db);
git.commit().setMessage("initial commit").call();
configureEchoTool(TOOL_NAME); configureEchoTool(TOOL_NAME);
} }
@ -83,7 +45,7 @@ public void setUp() throws Exception {
public void testNotDefinedTool() throws Exception { public void testNotDefinedTool() throws Exception {
createUnstagedChanges(); createUnstagedChanges();
runAndCaptureUsingInitRaw("difftool", "--tool", "undefined"); runAndCaptureUsingInitRaw(DIFF_TOOL, "--tool", "undefined");
fail("Expected exception when trying to run undefined tool"); fail("Expected exception when trying to run undefined tool");
} }
@ -91,7 +53,7 @@ public void testNotDefinedTool() throws Exception {
public void testTool() throws Exception { public void testTool() throws Exception {
RevCommit commit = createUnstagedChanges(); RevCommit commit = createUnstagedChanges();
List<DiffEntry> changes = getRepositoryChanges(commit); List<DiffEntry> changes = getRepositoryChanges(commit);
String[] expectedOutput = getExpectedDiffToolOutput(changes); String[] expectedOutput = getExpectedToolOutput(changes);
String[] options = { String[] options = {
"--tool", "--tool",
@ -101,7 +63,7 @@ public void testTool() throws Exception {
for (String option : options) { for (String option : options) {
assertArrayOfLinesEquals("Incorrect output for option: " + option, assertArrayOfLinesEquals("Incorrect output for option: " + option,
expectedOutput, expectedOutput,
runAndCaptureUsingInitRaw("difftool", option, runAndCaptureUsingInitRaw(DIFF_TOOL, option,
TOOL_NAME)); TOOL_NAME));
} }
} }
@ -110,13 +72,13 @@ public void testTool() throws Exception {
public void testToolTrustExitCode() throws Exception { public void testToolTrustExitCode() throws Exception {
RevCommit commit = createUnstagedChanges(); RevCommit commit = createUnstagedChanges();
List<DiffEntry> changes = getRepositoryChanges(commit); List<DiffEntry> changes = getRepositoryChanges(commit);
String[] expectedOutput = getExpectedDiffToolOutput(changes); String[] expectedOutput = getExpectedToolOutput(changes);
String[] options = { "--tool", "-t", }; String[] options = { "--tool", "-t", };
for (String option : options) { for (String option : options) {
assertArrayOfLinesEquals("Incorrect output for option: " + option, assertArrayOfLinesEquals("Incorrect output for option: " + option,
expectedOutput, runAndCaptureUsingInitRaw("difftool", expectedOutput, runAndCaptureUsingInitRaw(DIFF_TOOL,
"--trust-exit-code", option, TOOL_NAME)); "--trust-exit-code", option, TOOL_NAME));
} }
} }
@ -125,13 +87,13 @@ expectedOutput, runAndCaptureUsingInitRaw("difftool",
public void testToolNoGuiNoPromptNoTrustExitcode() throws Exception { public void testToolNoGuiNoPromptNoTrustExitcode() throws Exception {
RevCommit commit = createUnstagedChanges(); RevCommit commit = createUnstagedChanges();
List<DiffEntry> changes = getRepositoryChanges(commit); List<DiffEntry> changes = getRepositoryChanges(commit);
String[] expectedOutput = getExpectedDiffToolOutput(changes); String[] expectedOutput = getExpectedToolOutput(changes);
String[] options = { "--tool", "-t", }; String[] options = { "--tool", "-t", };
for (String option : options) { for (String option : options) {
assertArrayOfLinesEquals("Incorrect output for option: " + option, assertArrayOfLinesEquals("Incorrect output for option: " + option,
expectedOutput, runAndCaptureUsingInitRaw("difftool", expectedOutput, runAndCaptureUsingInitRaw(DIFF_TOOL,
"--no-gui", "--no-prompt", "--no-trust-exit-code", "--no-gui", "--no-prompt", "--no-trust-exit-code",
option, TOOL_NAME)); option, TOOL_NAME));
} }
@ -141,13 +103,13 @@ expectedOutput, runAndCaptureUsingInitRaw("difftool",
public void testToolCached() throws Exception { public void testToolCached() throws Exception {
RevCommit commit = createStagedChanges(); RevCommit commit = createStagedChanges();
List<DiffEntry> changes = getRepositoryChanges(commit); List<DiffEntry> changes = getRepositoryChanges(commit);
String[] expectedOutput = getExpectedDiffToolOutput(changes); String[] expectedOutput = getExpectedToolOutput(changes);
String[] options = { "--cached", "--staged", }; String[] options = { "--cached", "--staged", };
for (String option : options) { for (String option : options) {
assertArrayOfLinesEquals("Incorrect output for option: " + option, assertArrayOfLinesEquals("Incorrect output for option: " + option,
expectedOutput, runAndCaptureUsingInitRaw("difftool", expectedOutput, runAndCaptureUsingInitRaw(DIFF_TOOL,
option, "--tool", TOOL_NAME)); option, "--tool", TOOL_NAME));
} }
} }
@ -174,7 +136,8 @@ public void testToolHelp() throws Exception {
String option = "--tool-help"; String option = "--tool-help";
assertArrayOfLinesEquals("Incorrect output for option: " + option, 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) { private void configureEchoTool(String toolName) {
@ -196,33 +159,7 @@ private void configureEchoTool(String toolName) {
String.valueOf(false)); String.valueOf(false));
} }
private RevCommit createUnstagedChanges() throws Exception { private String[] getExpectedToolOutput(List<DiffEntry> changes) {
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) {
String[] expectedToolOutput = new String[changes.size()]; String[] expectedToolOutput = new String[changes.size()];
for (int i = 0; i < changes.size(); ++i) { for (int i = 0; i < changes.size(); ++i) {
DiffEntry change = changes.get(i); DiffEntry change = changes.get(i);
@ -232,17 +169,4 @@ private String[] getExpectedDiffToolOutput(List<DiffEntry> changes) {
} }
return expectedToolOutput; 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\")";
}
} }

View File

@ -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\")";
}
}

View File

@ -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]);
}
}

View File

@ -25,6 +25,7 @@ org.eclipse.jgit.pgm.LsRemote
org.eclipse.jgit.pgm.LsTree org.eclipse.jgit.pgm.LsTree
org.eclipse.jgit.pgm.Merge org.eclipse.jgit.pgm.Merge
org.eclipse.jgit.pgm.MergeBase org.eclipse.jgit.pgm.MergeBase
org.eclipse.jgit.pgm.MergeTool
org.eclipse.jgit.pgm.Push org.eclipse.jgit.pgm.Push
org.eclipse.jgit.pgm.ReceivePack org.eclipse.jgit.pgm.ReceivePack
org.eclipse.jgit.pgm.Reflog org.eclipse.jgit.pgm.Reflog

View File

@ -255,6 +255,7 @@ usage_DisplayTheVersionOfJgit=Display the version of jgit
usage_Gc=Cleanup unnecessary files and optimize the local repository usage_Gc=Cleanup unnecessary files and optimize the local repository
usage_Glog=View commit history as a graph 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_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_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_IndexPack=Build pack index file for an existing packed archive
usage_LFSDirectory=Directory to store large objects 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_StopTrackingAFile=Stop tracking a file
usage_TextHashFunctions=Scan repository to compute maximum number of collisions for hash functions 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_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_UpdateRemoteRepositoryFromLocalRefs=Update remote repository from local refs
usage_UseAll=Use all refs found in refs/ usage_UseAll=Use all refs found in refs/
usage_UseTags=Use any tag including lightweight tags 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_detectRenames=detect renamed files
usage_diffAlgorithm=the diff algorithm to use. Currently supported are: 'myers', 'histogram' 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_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_directoriesToExport=directories to export
usage_disableTheServiceInAllRepositories=disable the service in all repositories usage_disableTheServiceInAllRepositories=disable the service in all repositories
usage_displayAListOfAllRegisteredJgitCommands=Display a list of all registered jgit commands usage_displayAListOfAllRegisteredJgitCommands=Display a list of all registered jgit commands

View File

@ -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 * 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
@ -192,7 +192,7 @@ private void compare(List<DiffEntry> files, boolean showPrompt,
outw.flush(); outw.flush();
errw.println(e.getMessage()); errw.println(e.getMessage());
throw die(MessageFormat.format( throw die(MessageFormat.format(
CLIText.get().diffToolDied, mergedFilePath, e)); CLIText.get().diffToolDied, mergedFilePath), e);
} }
} else { } else {
break; break;

View File

@ -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;
}
}

View File

@ -9,13 +9,27 @@
*/ */
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_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.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.Collections;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set; import java.util.Set;
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.junit.Test; import org.junit.Test;
/** /**
@ -23,12 +37,60 @@
*/ */
public class ExternalMergeToolTest extends ExternalToolTestCase { 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 @Test
public void testToolNames() { public void testToolNames() {
MergeTools manager = new MergeTools(db); MergeTools manager = new MergeTools(db);
Set<String> actualToolNames = manager.getToolNames(); Set<String> actualToolNames = manager.getToolNames();
Set<String> expectedToolNames = Collections.emptySet(); Set<String> expectedToolNames = Collections.emptySet();
assertEquals("Incorrect set of external diff tool names", assertEquals("Incorrect set of external merge tool names",
expectedToolNames, actualToolNames); expectedToolNames, actualToolNames);
} }
@ -36,18 +98,58 @@ public void testToolNames() {
public void testAllTools() { public void testAllTools() {
MergeTools manager = new MergeTools(db); MergeTools manager = new MergeTools(db);
Set<String> actualToolNames = manager.getAvailableTools().keySet(); Set<String> actualToolNames = manager.getAvailableTools().keySet();
Set<String> expectedToolNames = Collections.emptySet(); Set<String> expectedToolNames = new LinkedHashSet<>();
assertEquals("Incorrect set of available external diff tools", CommandLineMergeTool[] defaultTools = CommandLineMergeTool.values();
expectedToolNames, actualToolNames); 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 @Test
public void testUserDefinedTools() { 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); MergeTools manager = new MergeTools(db);
Set<String> actualToolNames = manager.getUserDefinedTools().keySet(); Set<String> actualToolNames = manager.getUserDefinedTools().keySet();
Set<String> expectedToolNames = Collections.emptySet(); Set<String> expectedToolNames = new LinkedHashSet<>();
assertEquals("Incorrect set of user defined external diff tools", expectedToolNames.add(customToolname);
expectedToolNames, actualToolNames); assertEquals("Incorrect set of external merge tools", expectedToolNames,
actualToolNames);
} }
@Test @Test
@ -55,55 +157,118 @@ public void testNotAvailableTools() {
MergeTools manager = new MergeTools(db); MergeTools manager = new MergeTools(db);
Set<String> actualToolNames = manager.getNotAvailableTools().keySet(); Set<String> actualToolNames = manager.getNotAvailableTools().keySet();
Set<String> expectedToolNames = Collections.emptySet(); 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); expectedToolNames, actualToolNames);
} }
@Test @Test
public void testCompare() throws ToolException { 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 prompt = BooleanTriState.UNSET;
BooleanTriState gui = BooleanTriState.UNSET; BooleanTriState gui = BooleanTriState.UNSET;
BooleanTriState trustExitCode = BooleanTriState.UNSET;
MergeTools manager = new MergeTools(db);
int expectedCompareResult = 0; int expectedCompareResult = 0;
int compareResult = manager.merge(newPath, oldPath, newId, oldId, ExecutionResult compareResult = manager.merge(db, local, remote, base,
toolName, prompt, gui, trustExitCode); merged.getPath(), toolName, prompt, gui);
assertEquals("Incorrect compare result for external diff tool", assertEquals("Incorrect compare result for external merge tool",
expectedCompareResult, compareResult); expectedCompareResult, compareResult.getRc());
} }
@Test @Test
public void testDefaultTool() throws Exception { public void testDefaultTool() throws Exception {
String toolName = "customTool";
String guiToolName = "customGuiTool";
FileBasedConfig config = db.getConfig(); 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; String subsection = null;
config.setString("diff", subsection, "tool", "customTool"); config.setString(CONFIG_MERGE_SECTION, subsection, CONFIG_KEY_TOOL,
toolName);
MergeTools manager = new MergeTools(db); MergeTools manager = new MergeTools(db);
BooleanTriState gui = BooleanTriState.UNSET; BooleanTriState gui = BooleanTriState.UNSET;
String defaultToolName = manager.getDefaultToolName(gui); String defaultToolName = manager.getDefaultToolName(gui);
assertEquals( assertEquals(
"Expected configured difftool to be the default external diff tool", "Expected configured mergetool to be the default external merge tool",
"my_default_toolname", defaultToolName); toolName, defaultToolName);
gui = BooleanTriState.TRUE; gui = BooleanTriState.TRUE;
String defaultGuiToolName = manager.getDefaultToolName(gui); String defaultGuiToolName = manager.getDefaultToolName(gui);
assertEquals( 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); "my_gui_tool", defaultGuiToolName);
config.setString("diff", subsection, "guitool", "customGuiTool"); config.setString(CONFIG_MERGE_SECTION, subsection, CONFIG_KEY_GUITOOL,
guiToolName);
manager = new MergeTools(db); manager = new MergeTools(db);
defaultGuiToolName = manager.getDefaultToolName(gui); defaultGuiToolName = manager.getDefaultToolName(gui);
assertEquals( 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); "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();
}
} }

View File

@ -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;
}
}

View File

@ -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 * 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
@ -56,8 +56,7 @@ public DiffTools(Repository repo) {
* @param remoteFile * @param remoteFile
* the remote file element * the remote file element
* @param mergedFilePath * @param mergedFilePath
* the path of 'merged' file, it equals local or remote path for * the path of 'merged' file, it equals local or remote path
* difftool
* @param toolName * @param toolName
* the selected tool name (can be null) * the selected tool name (can be null)
* @param prompt * @param prompt
@ -66,7 +65,7 @@ public DiffTools(Repository repo) {
* the GUI option * the GUI option
* @param trustExitCode * @param trustExitCode
* the "trust exit code" option * the "trust exit code" option
* @return the return code from executed tool * @return the execution result from tool
* @throws ToolException * @throws ToolException
*/ */
public ExecutionResult compare(Repository repo, FileElement localFile, public ExecutionResult compare(Repository repo, FileElement localFile,

View File

@ -10,6 +10,8 @@
package org.eclipse.jgit.internal.diffmergetool; package org.eclipse.jgit.internal.diffmergetool;
import org.eclipse.jgit.lib.internal.BooleanTriState;
/** /**
* The merge tool interface. * The merge tool interface.
*/ */
@ -18,6 +20,14 @@ public interface ExternalMergeTool extends ExternalDiffTool {
/** /**
* @return the tool "trust exit code" option * @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);
} }

View File

@ -10,13 +10,24 @@
package org.eclipse.jgit.internal.diffmergetool; 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.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Config.SectionParser; import org.eclipse.jgit.lib.Config.SectionParser;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.internal.BooleanTriState; import org.eclipse.jgit.lib.internal.BooleanTriState;
/** /**
@ -42,31 +53,27 @@ public class MergeToolConfig {
private final Map<String, ExternalMergeTool> tools; private final Map<String, ExternalMergeTool> tools;
private MergeToolConfig(Config rc) { private MergeToolConfig(Config rc) {
toolName = rc.getString(ConfigConstants.CONFIG_MERGE_SECTION, null, toolName = rc.getString(CONFIG_MERGE_SECTION, null, CONFIG_KEY_TOOL);
ConfigConstants.CONFIG_KEY_TOOL); guiToolName = rc.getString(CONFIG_MERGE_SECTION, null,
guiToolName = rc.getString(ConfigConstants.CONFIG_MERGE_SECTION, null, CONFIG_KEY_GUITOOL);
ConfigConstants.CONFIG_KEY_GUITOOL); prompt = rc.getBoolean(CONFIG_MERGETOOL_SECTION, toolName,
prompt = rc.getBoolean(ConfigConstants.CONFIG_MERGETOOL_SECTION, CONFIG_KEY_PROMPT, true);
ConfigConstants.CONFIG_KEY_PROMPT, true); keepBackup = rc.getBoolean(CONFIG_MERGETOOL_SECTION,
keepBackup = rc.getBoolean(ConfigConstants.CONFIG_MERGETOOL_SECTION, CONFIG_KEY_KEEP_BACKUP, true);
ConfigConstants.CONFIG_KEY_KEEP_BACKUP, true); keepTemporaries = rc.getBoolean(CONFIG_MERGETOOL_SECTION,
keepTemporaries = rc.getBoolean( CONFIG_KEY_KEEP_TEMPORARIES, false);
ConfigConstants.CONFIG_MERGETOOL_SECTION, writeToTemp = rc.getBoolean(CONFIG_MERGETOOL_SECTION,
ConfigConstants.CONFIG_KEY_KEEP_TEMPORARIES, false); CONFIG_KEY_WRITE_TO_TEMP, false);
writeToTemp = rc.getBoolean(ConfigConstants.CONFIG_MERGETOOL_SECTION,
ConfigConstants.CONFIG_KEY_WRITE_TO_TEMP, false);
tools = new HashMap<>(); tools = new HashMap<>();
Set<String> subsections = rc Set<String> subsections = rc.getSubsections(CONFIG_MERGETOOL_SECTION);
.getSubsections(ConfigConstants.CONFIG_MERGETOOL_SECTION);
for (String name : subsections) { for (String name : subsections) {
String cmd = rc.getString(ConfigConstants.CONFIG_MERGETOOL_SECTION, String cmd = rc.getString(CONFIG_MERGETOOL_SECTION, name,
name, ConfigConstants.CONFIG_KEY_CMD); CONFIG_KEY_CMD);
String path = rc.getString(ConfigConstants.CONFIG_MERGETOOL_SECTION, String path = rc.getString(CONFIG_MERGETOOL_SECTION, name,
name, ConfigConstants.CONFIG_KEY_PATH); CONFIG_KEY_PATH);
BooleanTriState trustExitCode = BooleanTriState.FALSE; BooleanTriState trustExitCode = BooleanTriState.FALSE;
String trustStr = rc.getString( String trustStr = rc.getString(CONFIG_MERGETOOL_SECTION, name,
ConfigConstants.CONFIG_MERGETOOL_SECTION, name, CONFIG_KEY_TRUST_EXIT_CODE);
ConfigConstants.CONFIG_KEY_TRUST_EXIT_CODE);
if (trustStr != null) { if (trustStr != null) {
trustExitCode = Boolean.valueOf(trustStr).booleanValue() trustExitCode = Boolean.valueOf(trustStr).booleanValue()
? BooleanTriState.TRUE ? BooleanTriState.TRUE
@ -75,9 +82,8 @@ private MergeToolConfig(Config rc) {
trustExitCode = BooleanTriState.UNSET; trustExitCode = BooleanTriState.UNSET;
} }
if ((cmd != null) || (path != null)) { if ((cmd != null) || (path != null)) {
tools.put(name, tools.put(name, new UserDefinedMergeTool(name, path, cmd,
new UserDefinedMergeTool(name, path, cmd, trustExitCode));
trustExitCode));
} }
} }
} }

View File

@ -9,17 +9,21 @@
*/ */
package org.eclipse.jgit.internal.diffmergetool; package org.eclipse.jgit.internal.diffmergetool;
import java.io.File;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TreeMap; import java.util.TreeMap;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.internal.BooleanTriState; import org.eclipse.jgit.lib.internal.BooleanTriState;
import org.eclipse.jgit.util.FS.ExecutionResult;
/** /**
* Manages merge tools. * Manages merge tools.
*/ */
public class MergeTools { public class MergeTools {
private final MergeToolConfig config; private final MergeToolConfig config;
private final Map<String, ExternalMergeTool> predefinedTools; private final Map<String, ExternalMergeTool> predefinedTools;
@ -33,10 +37,12 @@ public class MergeTools {
public MergeTools(Repository repo) { public MergeTools(Repository repo) {
config = repo.getConfig().get(MergeToolConfig.KEY); config = repo.getConfig().get(MergeToolConfig.KEY);
predefinedTools = setupPredefinedTools(); predefinedTools = setupPredefinedTools();
userDefinedTools = setupUserDefinedTools(); userDefinedTools = setupUserDefinedTools(config, predefinedTools);
} }
/** /**
* @param repo
* the repository
* @param localFile * @param localFile
* the local file element * the local file element
* @param remoteFile * @param remoteFile
@ -49,19 +55,43 @@ public MergeTools(Repository repo) {
* the selected tool name (can be null) * the selected tool name (can be null)
* @param prompt * @param prompt
* the prompt option * the prompt option
* @param trustExitCode
* the "trust exit code" option
* @param gui * @param gui
* the GUI option * the GUI option
* @return the execution result from tool * @return the execution result from tool
* @throws ToolException * @throws ToolException
*/ */
public int merge(String localFile, public ExecutionResult merge(Repository repo, FileElement localFile,
String remoteFile, String baseFile, String mergedFilePath, FileElement remoteFile, FileElement baseFile, String mergedFilePath,
String toolName, BooleanTriState prompt, BooleanTriState gui, String toolName, BooleanTriState prompt, BooleanTriState gui)
BooleanTriState trustExitCode)
throws ToolException { 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 Map<String, ExternalMergeTool> getNotAvailableTools() {
*/ */
public String getDefaultToolName(BooleanTriState gui) { public String getDefaultToolName(BooleanTriState gui) {
return gui != BooleanTriState.UNSET ? "my_gui_tool" //$NON-NLS-1$ return gui != BooleanTriState.UNSET ? "my_gui_tool" //$NON-NLS-1$
: "my_default_toolname"; //$NON-NLS-1$ : config.getDefaultToolName();
} }
/** /**
@ -109,11 +139,58 @@ public boolean isInteractive() {
return config.isPrompt(); return config.isPrompt();
} }
private Map<String, ExternalMergeTool> setupPredefinedTools() { private ExternalMergeTool guessTool(String toolName, BooleanTriState gui)
return new TreeMap<>(); 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 Map<String, ExternalMergeTool> setupUserDefinedTools() { private ExternalMergeTool getTool(final String name) {
return new TreeMap<>(); ExternalMergeTool tool = userDefinedTools.get(name);
if (tool == null) {
tool = predefinedTools.get(name);
}
return tool;
} }
}
private Map<String, ExternalMergeTool> setupPredefinedTools() {
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(
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;
}
}

View File

@ -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);
}
}

View File

@ -21,7 +21,7 @@ public class UserDefinedMergeTool extends UserDefinedDiffTool
/** /**
* the merge tool "trust exit code" option * the merge tool "trust exit code" option
*/ */
private final BooleanTriState trustExitCode; private BooleanTriState trustExitCode;
/** /**
* Creates the merge tool * Creates the merge tool
@ -40,20 +40,30 @@ public UserDefinedMergeTool(String name, String path, String cmd,
super(name, path, cmd); super(name, path, cmd);
this.trustExitCode = trustExitCode; this.trustExitCode = trustExitCode;
} }
/** /**
* @return the "trust exit code" flag * @return the "trust exit code" flag
*/ */
@Override @Override
public boolean isTrustExitCode() {
return trustExitCode == BooleanTriState.TRUE;
}
/**
* @return the "trust exit code" option
*/
public BooleanTriState getTrustExitCode() { public BooleanTriState getTrustExitCode() {
return trustExitCode; return trustExitCode;
} }
/**
* @param trustExitCode
* the new "trust exit code" flag
*/
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();
}
} }

View File

@ -10,6 +10,7 @@
* *
* SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: BSD-3-Clause
*/ */
package org.eclipse.jgit.lib; package org.eclipse.jgit.lib;
/** /**
@ -66,7 +67,7 @@ public final class ConfigConstants {
public static final String CONFIG_KEY_TRUST_EXIT_CODE = "trustExitCode"; 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 * @since 6.1
*/ */