blob: 54c4f26099d7078b83a26a1d6e086b17474e4c39 [file] [log] [blame]
/*
* 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 static org.junit.Assert.fail;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.eclipse.jgit.internal.diffmergetool.ExternalMergeTool;
import org.eclipse.jgit.internal.diffmergetool.MergeTools;
import org.eclipse.jgit.lib.StoredConfig;
import org.junit.Before;
import org.junit.Test;
/**
* Testing the {@code mergetool} command.
*/
public class MergeToolTest extends ToolTestCase {
private static final String MERGE_TOOL = CONFIG_MERGETOOL_SECTION;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
configureEchoTool(TOOL_NAME);
}
@Test
public void testUndefinedTool() throws Exception {
String toolName = "undefined";
String[] conflictingFilenames = createMergeConflict();
List<String> expectedErrors = new ArrayList<>();
for (String conflictingFilename : conflictingFilenames) {
expectedErrors.add("External merge tool is not defined: " + toolName);
expectedErrors.add("merge of " + conflictingFilename + " failed");
}
runAndCaptureUsingInitRaw(expectedErrors, MERGE_TOOL,
"--no-prompt", "--tool", toolName);
}
@Test(expected = Die.class)
public void testUserToolWithCommandNotFoundError() throws Exception {
String toolName = "customTool";
int errorReturnCode = 127; // command not found
String command = "exit " + errorReturnCode;
StoredConfig config = db.getConfig();
config.setString(CONFIG_MERGETOOL_SECTION, toolName, CONFIG_KEY_CMD,
command);
createMergeConflict();
runAndCaptureUsingInitRaw(MERGE_TOOL, "--no-prompt", "--tool",
toolName);
fail("Expected exception to be thrown due to external tool exiting with error code: "
+ errorReturnCode);
}
@Test
public void testEmptyToolName() throws Exception {
assumeLinuxPlatform();
String emptyToolName = "";
StoredConfig config = db.getConfig();
// the default merge tool is configured without a subsection
String subsection = null;
config.setString(CONFIG_MERGE_SECTION, subsection, CONFIG_KEY_TOOL,
emptyToolName);
createMergeConflict();
String araxisErrorLine = "compare: unrecognized option `-wait' @ error/compare.c/CompareImageCommand/1123.";
String[] expectedErrorOutput = { araxisErrorLine, araxisErrorLine, };
runAndCaptureUsingInitRaw(Arrays.asList(expectedErrorOutput),
MERGE_TOOL, "--no-prompt");
}
@Test
public void testAbortMerge() throws Exception {
String[] inputLines = {
"y", // start tool for merge resolution
"n", // don't accept merge tool result
"n", // don't continue resolution
};
String[] conflictingFilenames = createMergeConflict();
int abortIndex = 1;
String[] expectedOutput = getExpectedAbortMergeOutput(
conflictingFilenames,
abortIndex);
String option = "--tool";
InputStream inputStream = createInputStream(inputLines);
assertArrayOfLinesEquals("Incorrect output for option: " + option,
expectedOutput, runAndCaptureUsingInitRaw(inputStream,
MERGE_TOOL, "--prompt", option, TOOL_NAME));
}
@Test
public void testAbortLaunch() throws Exception {
String[] inputLines = {
"n", // abort merge tool launch
};
String[] conflictingFilenames = createMergeConflict();
String[] expectedOutput = getExpectedAbortLaunchOutput(
conflictingFilenames);
String option = "--tool";
InputStream inputStream = createInputStream(inputLines);
assertArrayOfLinesEquals("Incorrect output for option: " + option,
expectedOutput, runAndCaptureUsingInitRaw(inputStream,
MERGE_TOOL, "--prompt", option, TOOL_NAME));
}
@Test
public void testMergeConflict() throws Exception {
String[] inputLines = {
"y", // start tool for merge resolution
"y", // accept merge result as successful
"y", // start tool for merge resolution
"y", // accept merge result as successful
};
String[] conflictingFilenames = createMergeConflict();
String[] expectedOutput = getExpectedMergeConflictOutput(
conflictingFilenames);
String option = "--tool";
InputStream inputStream = createInputStream(inputLines);
assertArrayOfLinesEquals("Incorrect output for option: " + option,
expectedOutput, runAndCaptureUsingInitRaw(inputStream,
MERGE_TOOL, "--prompt", option, TOOL_NAME));
}
@Test
public void testDeletedConflict() throws Exception {
String[] inputLines = {
"d", // choose delete option to resolve conflict
"m", // choose merge option to resolve conflict
};
String[] conflictingFilenames = createDeletedConflict();
String[] expectedOutput = getExpectedDeletedConflictOutput(
conflictingFilenames);
String option = "--tool";
InputStream inputStream = createInputStream(inputLines);
assertArrayOfLinesEquals("Incorrect output for option: " + option,
expectedOutput, runAndCaptureUsingInitRaw(inputStream,
MERGE_TOOL, "--prompt", option, TOOL_NAME));
}
@Test
public void testNoConflict() throws Exception {
createStagedChanges();
String[] expectedOutput = { "No files need merging" };
String[] options = { "--tool", "-t", };
for (String option : options) {
assertArrayOfLinesEquals("Incorrect output for option: " + option,
expectedOutput,
runAndCaptureUsingInitRaw(MERGE_TOOL, option, TOOL_NAME));
}
}
@Test
public void testMergeConflictNoPrompt() throws Exception {
String[] conflictingFilenames = createMergeConflict();
String[] expectedOutput = getExpectedMergeConflictOutputNoPrompt(
conflictingFilenames);
String option = "--tool";
assertArrayOfLinesEquals("Incorrect output for option: " + option,
expectedOutput,
runAndCaptureUsingInitRaw(MERGE_TOOL, option, TOOL_NAME));
}
@Test
public void testMergeConflictNoGuiNoPrompt() throws Exception {
String[] conflictingFilenames = createMergeConflict();
String[] expectedOutput = getExpectedMergeConflictOutputNoPrompt(
conflictingFilenames);
String option = "--tool";
assertArrayOfLinesEquals("Incorrect output for option: " + option,
expectedOutput, runAndCaptureUsingInitRaw(MERGE_TOOL,
"--no-gui", "--no-prompt", option, TOOL_NAME));
}
@Test
public void testToolHelp() throws Exception {
List<String> expectedOutput = new ArrayList<>();
MergeTools diffTools = new MergeTools(db);
Map<String, ExternalMergeTool> predefinedTools = diffTools
.getPredefinedTools(true);
List<ExternalMergeTool> availableTools = new ArrayList<>();
List<ExternalMergeTool> notAvailableTools = new ArrayList<>();
for (ExternalMergeTool tool : predefinedTools.values()) {
if (tool.isAvailable()) {
availableTools.add(tool);
} else {
notAvailableTools.add(tool);
}
}
expectedOutput.add(
"'git mergetool --tool=<tool>' may be set to one of the following:");
for (ExternalMergeTool tool : availableTools) {
String toolName = tool.getName();
expectedOutput.add(toolName);
}
String customToolHelpLine = TOOL_NAME + "." + CONFIG_KEY_CMD + " "
+ getEchoCommand();
expectedOutput.add("user-defined:");
expectedOutput.add(customToolHelpLine);
expectedOutput.add(
"The following tools are valid, but not currently available:");
for (ExternalMergeTool tool : notAvailableTools) {
String toolName = tool.getName();
expectedOutput.add(toolName);
}
String[] userDefinedToolsHelp = {
"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[] getExpectedMergeConflictOutputNoPrompt(
String[] conflictFilenames) {
List<String> expected = new ArrayList<>();
expected.add("Merging:");
for (String conflictFilename : conflictFilenames) {
expected.add(conflictFilename);
}
for (String conflictFilename : conflictFilenames) {
expected.add("Normal merge conflict for '" + conflictFilename
+ "':");
expected.add("{local}: modified file");
expected.add("{remote}: modified file");
Path filePath = getFullPath(conflictFilename);
expected.add(filePath.toString());
expected.add(conflictFilename + " seems unchanged.");
}
return expected.toArray(new String[0]);
}
private static String[] getExpectedAbortLaunchOutput(
String[] conflictFilenames) {
List<String> expected = new ArrayList<>();
expected.add("Merging:");
for (String conflictFilename : conflictFilenames) {
expected.add(conflictFilename);
}
if (conflictFilenames.length > 1) {
String conflictFilename = conflictFilenames[0];
expected.add(
"Normal merge conflict for '" + conflictFilename + "':");
expected.add("{local}: modified file");
expected.add("{remote}: modified file");
expected.add("Hit return to start merge resolution tool ("
+ TOOL_NAME + "):");
}
return expected.toArray(new String[0]);
}
private String[] getExpectedAbortMergeOutput(
String[] conflictFilenames, int abortIndex) {
List<String> expected = new ArrayList<>();
expected.add("Merging:");
for (String conflictFilename : conflictFilenames) {
expected.add(conflictFilename);
}
for (int i = 0; i < conflictFilenames.length; ++i) {
if (i == abortIndex) {
break;
}
String conflictFilename = conflictFilenames[i];
expected.add(
"Normal merge conflict for '" + conflictFilename + "':");
expected.add("{local}: modified file");
expected.add("{remote}: modified file");
Path fullPath = getFullPath(conflictFilename);
expected.add("Hit return to start merge resolution tool ("
+ TOOL_NAME + "): " + fullPath);
expected.add(conflictFilename + " seems unchanged.");
expected.add("Was the merge successful [y/n]?");
if (i < conflictFilenames.length - 1) {
expected.add(
"\tContinue merging other unresolved paths [y/n]?");
}
}
return expected.toArray(new String[0]);
}
private String[] getExpectedMergeConflictOutput(
String[] conflictFilenames) {
List<String> expected = new ArrayList<>();
expected.add("Merging:");
for (String conflictFilename : conflictFilenames) {
expected.add(conflictFilename);
}
for (int i = 0; i < conflictFilenames.length; ++i) {
String conflictFilename = conflictFilenames[i];
expected.add("Normal merge conflict for '" + conflictFilename
+ "':");
expected.add("{local}: modified file");
expected.add("{remote}: modified file");
Path filePath = getFullPath(conflictFilename);
expected.add("Hit return to start merge resolution tool ("
+ TOOL_NAME + "): " + filePath);
expected.add(conflictFilename + " seems unchanged.");
expected.add("Was the merge successful [y/n]?");
if (i < conflictFilenames.length - 1) {
// expected.add(
// "\tContinue merging other unresolved paths [y/n]?");
}
}
return expected.toArray(new String[0]);
}
private static String[] getExpectedDeletedConflictOutput(
String[] conflictFilenames) {
List<String> expected = new ArrayList<>();
expected.add("Merging:");
for (String mergeConflictFilename : conflictFilenames) {
expected.add(mergeConflictFilename);
}
for (int i = 0; i < conflictFilenames.length; ++i) {
String conflictFilename = conflictFilenames[i];
expected.add(conflictFilename + " seems unchanged.");
expected.add("{local}: deleted");
expected.add("{remote}: modified file");
expected.add("Use (m)odified or (d)eleted file, or (a)bort?");
}
return expected.toArray(new String[0]);
}
private static String getEchoCommand() {
/*
* use 'MERGED' placeholder, as both 'LOCAL' and 'REMOTE' will be
* replaced with full paths to a temporary file during some of the tests
*/
return "(echo \"$MERGED\")";
}
}