398 lines
13 KiB
Java
398 lines
13 KiB
Java
/*
|
|
* 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\")";
|
|
}
|
|
}
|