1
2
3
4
5
6
7
8
9
10 package org.eclipse.jgit.internal.diffmergetool;
11
12 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_CMD;
13 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_GUITOOL;
14 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PATH;
15 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PROMPT;
16 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_TOOL;
17 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_TRUST_EXIT_CODE;
18 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_MERGETOOL_SECTION;
19 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_MERGE_SECTION;
20 import static org.junit.Assert.assertEquals;
21 import static org.junit.Assert.assertFalse;
22 import static org.junit.Assert.assertNotNull;
23 import static org.junit.Assert.assertNull;
24 import static org.junit.Assert.assertTrue;
25 import static org.junit.Assert.fail;
26 import static org.junit.Assume.assumeTrue;
27
28 import java.io.IOException;
29 import java.nio.file.Files;
30 import java.util.Arrays;
31 import java.util.Collections;
32 import java.util.LinkedHashSet;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Optional;
36 import java.util.Set;
37
38 import org.eclipse.jgit.lib.internal.BooleanTriState;
39 import org.eclipse.jgit.storage.file.FileBasedConfig;
40 import org.eclipse.jgit.util.FS.ExecutionResult;
41 import org.junit.Test;
42
43
44
45
46 public class ExternalMergeToolTest extends ExternalToolTestCase {
47
48 @Test(expected = ToolException.class)
49 public void testUserToolWithError() throws Exception {
50 String toolName = "customTool";
51
52 int errorReturnCode = 1;
53 String command = "exit " + errorReturnCode;
54
55 FileBasedConfig config = db.getConfig();
56 config.setString(CONFIG_MERGETOOL_SECTION, toolName, CONFIG_KEY_CMD,
57 command);
58 config.setString(CONFIG_MERGETOOL_SECTION, toolName,
59 CONFIG_KEY_TRUST_EXIT_CODE, String.valueOf(Boolean.TRUE));
60
61 invokeMerge(toolName);
62
63 fail("Expected exception to be thrown due to external tool exiting with error code: "
64 + errorReturnCode);
65 }
66
67 @Test(expected = ToolException.class)
68 public void testUserToolWithCommandNotFoundError() throws Exception {
69 String toolName = "customTool";
70
71 int errorReturnCode = 127;
72 String command = "exit " + errorReturnCode;
73
74 FileBasedConfig config = db.getConfig();
75 config.setString(CONFIG_MERGETOOL_SECTION, toolName, CONFIG_KEY_CMD,
76 command);
77
78 invokeMerge(toolName);
79
80 fail("Expected exception to be thrown due to external tool exiting with error code: "
81 + errorReturnCode);
82 }
83
84 @Test
85 public void testKdiff3() throws Exception {
86 assumePosixPlatform();
87
88 CommandLineMergeTool autoMergingTool = CommandLineMergeTool.kdiff3;
89 assumeMergeToolIsAvailable(autoMergingTool);
90
91 CommandLineMergeTool tool = autoMergingTool;
92 PreDefinedMergeTool externalTool = new PreDefinedMergeTool(tool.name(),
93 tool.getPath(), tool.getParameters(true),
94 tool.getParameters(false),
95 tool.isExitCodeTrustable() ? BooleanTriState.TRUE
96 : BooleanTriState.FALSE);
97
98 MergeTools manager = new MergeTools(db);
99 ExecutionResult result = manager.merge(local, remote, merged, null,
100 null, externalTool);
101 assertEquals("Expected merge tool to succeed", 0, result.getRc());
102
103 List<String> actualLines = Files.readAllLines(mergedFile.toPath());
104 String actualMergeResult = String.join(System.lineSeparator(),
105 actualLines);
106 String expectedMergeResult = DEFAULT_CONTENT;
107 assertEquals(
108 "Failed to merge equal local and remote versions with pre-defined tool: "
109 + tool.getPath(),
110 expectedMergeResult, actualMergeResult);
111 }
112
113 @Test
114 public void testUserDefinedTool() throws Exception {
115 String customToolName = "customTool";
116 String command = getEchoCommand();
117
118 FileBasedConfig config = db.getConfig();
119 config.setString(CONFIG_MERGETOOL_SECTION, customToolName,
120 CONFIG_KEY_CMD, command);
121
122 MergeTools manager = new MergeTools(db);
123 Map<String, ExternalMergeTool> tools = manager.getUserDefinedTools();
124 ExternalMergeTool externalTool = tools.get(customToolName);
125 manager.merge(local, remote, merged, base, null, externalTool);
126
127 assertEchoCommandHasCorrectOutput();
128 }
129
130 @Test
131 public void testUserDefinedToolWithPrompt() throws Exception {
132 String customToolName = "customTool";
133 String command = getEchoCommand();
134
135 FileBasedConfig config = db.getConfig();
136 config.setString(CONFIG_MERGETOOL_SECTION, customToolName,
137 CONFIG_KEY_CMD, command);
138
139 MergeTools manager = new MergeTools(db);
140
141 PromptHandler promptHandler = PromptHandler.acceptPrompt();
142 MissingToolHandler noToolHandler = new MissingToolHandler();
143
144 manager.merge(local, remote, merged, base, null,
145 Optional.of(customToolName), BooleanTriState.TRUE, false,
146 promptHandler, noToolHandler);
147
148 assertEchoCommandHasCorrectOutput();
149
150 List<String> actualToolPrompts = promptHandler.toolPrompts;
151 List<String> expectedToolPrompts = Arrays.asList("customTool");
152 assertEquals("Expected a user prompt for custom tool call",
153 expectedToolPrompts, actualToolPrompts);
154
155 assertEquals("Expected to no informing about missing tools",
156 Collections.EMPTY_LIST, noToolHandler.missingTools);
157 }
158
159 @Test
160 public void testUserDefinedToolWithCancelledPrompt() throws Exception {
161 MergeTools manager = new MergeTools(db);
162
163 PromptHandler promptHandler = PromptHandler.cancelPrompt();
164 MissingToolHandler noToolHandler = new MissingToolHandler();
165
166 Optional<ExecutionResult> result = manager.merge(local, remote, merged,
167 base, null, Optional.empty(), BooleanTriState.TRUE, false,
168 promptHandler, noToolHandler);
169 assertFalse("Expected no result if user cancels the operation",
170 result.isPresent());
171 }
172
173 @Test
174 public void testAllTools() {
175 FileBasedConfig config = db.getConfig();
176 String customToolName = "customTool";
177 config.setString(CONFIG_MERGETOOL_SECTION, customToolName,
178 CONFIG_KEY_CMD, "echo");
179
180 MergeTools manager = new MergeTools(db);
181 Set<String> actualToolNames = manager.getAllToolNames();
182 Set<String> expectedToolNames = new LinkedHashSet<>();
183 expectedToolNames.add(customToolName);
184 CommandLineMergeTool[] defaultTools = CommandLineMergeTool.values();
185 for (CommandLineMergeTool defaultTool : defaultTools) {
186 String toolName = defaultTool.name();
187 expectedToolNames.add(toolName);
188 }
189 assertEquals("Incorrect set of external merge tools", expectedToolNames,
190 actualToolNames);
191 }
192
193 @Test
194 public void testOverridePredefinedToolPath() {
195 String toolName = CommandLineMergeTool.guiffy.name();
196 String customToolPath = "/usr/bin/echo";
197
198 FileBasedConfig config = db.getConfig();
199 config.setString(CONFIG_MERGETOOL_SECTION, toolName, CONFIG_KEY_CMD,
200 "echo");
201 config.setString(CONFIG_MERGETOOL_SECTION, toolName, CONFIG_KEY_PATH,
202 customToolPath);
203
204 MergeTools manager = new MergeTools(db);
205 Map<String, ExternalMergeTool> tools = manager.getUserDefinedTools();
206 ExternalMergeTool mergeTool = tools.get(toolName);
207 assertNotNull("Expected tool \"" + toolName + "\" to be user defined",
208 mergeTool);
209
210 String toolPath = mergeTool.getPath();
211 assertEquals("Expected external merge tool to have an overriden path",
212 customToolPath, toolPath);
213 }
214
215 @Test
216 public void testUserDefinedTools() {
217 FileBasedConfig config = db.getConfig();
218 String customToolname = "customTool";
219 config.setString(CONFIG_MERGETOOL_SECTION, customToolname,
220 CONFIG_KEY_CMD, "echo");
221 config.setString(CONFIG_MERGETOOL_SECTION, customToolname,
222 CONFIG_KEY_PATH, "/usr/bin/echo");
223 config.setString(CONFIG_MERGETOOL_SECTION, customToolname,
224 CONFIG_KEY_PROMPT, String.valueOf(false));
225 config.setString(CONFIG_MERGETOOL_SECTION, customToolname,
226 CONFIG_KEY_GUITOOL, String.valueOf(false));
227 config.setString(CONFIG_MERGETOOL_SECTION, customToolname,
228 CONFIG_KEY_TRUST_EXIT_CODE, String.valueOf(false));
229 MergeTools manager = new MergeTools(db);
230 Set<String> actualToolNames = manager.getUserDefinedTools().keySet();
231 Set<String> expectedToolNames = new LinkedHashSet<>();
232 expectedToolNames.add(customToolname);
233 assertEquals("Incorrect set of external merge tools", expectedToolNames,
234 actualToolNames);
235 }
236
237 @Test
238 public void testCompare() throws ToolException {
239 String toolName = "customTool";
240
241 FileBasedConfig config = db.getConfig();
242
243 String subsection = null;
244 config.setString(CONFIG_MERGE_SECTION, subsection, CONFIG_KEY_TOOL,
245 toolName);
246
247 String command = getEchoCommand();
248
249 config.setString(CONFIG_MERGETOOL_SECTION, toolName, CONFIG_KEY_CMD,
250 command);
251
252 Optional<ExecutionResult> result = invokeMerge(toolName);
253 assertTrue("Expected external merge tool result to be available",
254 result.isPresent());
255 int expectedCompareResult = 0;
256 assertEquals("Incorrect compare result for external merge tool",
257 expectedCompareResult, result.get().getRc());
258 }
259
260 @Test
261 public void testDefaultTool() throws Exception {
262 String toolName = "customTool";
263 String guiToolName = "customGuiTool";
264
265 FileBasedConfig config = db.getConfig();
266
267 String subsection = null;
268 config.setString(CONFIG_MERGE_SECTION, subsection, CONFIG_KEY_TOOL,
269 toolName);
270
271 MergeTools manager = new MergeTools(db);
272 boolean gui = false;
273 String defaultToolName = manager.getDefaultToolName(gui);
274 assertEquals(
275 "Expected configured mergetool to be the default external merge tool",
276 toolName, defaultToolName);
277
278 gui = true;
279 String defaultGuiToolName = manager.getDefaultToolName(gui);
280 assertNull("Expected default mergetool to not be set",
281 defaultGuiToolName);
282
283 config.setString(CONFIG_MERGE_SECTION, subsection, CONFIG_KEY_GUITOOL,
284 guiToolName);
285 manager = new MergeTools(db);
286 defaultGuiToolName = manager.getDefaultToolName(gui);
287 assertEquals(
288 "Expected configured mergetool to be the default external merge guitool",
289 guiToolName, defaultGuiToolName);
290 }
291
292 @Test
293 public void testOverridePreDefinedToolPath() {
294 String newToolPath = "/tmp/path/";
295
296 CommandLineMergeTool[] defaultTools = CommandLineMergeTool.values();
297 assertTrue("Expected to find pre-defined external merge tools",
298 defaultTools.length > 0);
299
300 CommandLineMergeTool overridenTool = defaultTools[0];
301 String overridenToolName = overridenTool.name();
302 String overridenToolPath = newToolPath + overridenToolName;
303 FileBasedConfig config = db.getConfig();
304 config.setString(CONFIG_MERGETOOL_SECTION, overridenToolName,
305 CONFIG_KEY_PATH, overridenToolPath);
306
307 MergeTools manager = new MergeTools(db);
308 Map<String, ExternalMergeTool> availableTools = manager
309 .getPredefinedTools(true);
310 ExternalMergeTool externalMergeTool = availableTools
311 .get(overridenToolName);
312 String actualMergeToolPath = externalMergeTool.getPath();
313 assertEquals(
314 "Expected pre-defined external merge tool to have overriden path",
315 overridenToolPath, actualMergeToolPath);
316 boolean withBase = true;
317 String expectedMergeToolCommand = overridenToolPath + " "
318 + overridenTool.getParameters(withBase);
319 String actualMergeToolCommand = externalMergeTool.getCommand();
320 assertEquals(
321 "Expected pre-defined external merge tool to have overriden command",
322 expectedMergeToolCommand, actualMergeToolCommand);
323 }
324
325 @Test(expected = ToolException.class)
326 public void testUndefinedTool() throws Exception {
327 String toolName = "undefined";
328 invokeMerge(toolName);
329 fail("Expected exception to be thrown due to not defined external merge tool");
330 }
331
332 @Test
333 public void testDefaultToolExecutionWithPrompt() throws Exception {
334 FileBasedConfig config = db.getConfig();
335
336 String subsection = null;
337 config.setString("merge", subsection, "tool", "customTool");
338
339 String command = getEchoCommand();
340
341 config.setString("mergetool", "customTool", "cmd", command);
342
343 MergeTools manager = new MergeTools(db);
344
345 PromptHandler promptHandler = PromptHandler.acceptPrompt();
346 MissingToolHandler noToolHandler = new MissingToolHandler();
347
348 manager.merge(local, remote, merged, base, null, Optional.empty(),
349 BooleanTriState.TRUE, false, promptHandler, noToolHandler);
350
351 assertEchoCommandHasCorrectOutput();
352 }
353
354 @Test
355 public void testNoDefaultToolName() {
356 MergeTools manager = new MergeTools(db);
357 boolean gui = false;
358 String defaultToolName = manager.getDefaultToolName(gui);
359 assertNull("Expected no default tool when none is configured",
360 defaultToolName);
361
362 gui = true;
363 defaultToolName = manager.getDefaultToolName(gui);
364 assertNull("Expected no default tool when none is configured",
365 defaultToolName);
366 }
367
368 @Test(expected = ToolException.class)
369 public void testNullTool() throws Exception {
370 MergeTools manager = new MergeTools(db);
371
372 PromptHandler promptHandler = null;
373 MissingToolHandler noToolHandler = null;
374
375 Optional<String> tool = null;
376
377 manager.merge(local, remote, merged, base, null, tool,
378 BooleanTriState.TRUE, false, promptHandler, noToolHandler);
379 }
380
381 @Test(expected = ToolException.class)
382 public void testNullToolWithPrompt() throws Exception {
383 MergeTools manager = new MergeTools(db);
384
385 PromptHandler promptHandler = PromptHandler.cancelPrompt();
386 MissingToolHandler noToolHandler = new MissingToolHandler();
387
388 Optional<String> tool = null;
389
390 manager.merge(local, remote, merged, base, null, tool,
391 BooleanTriState.TRUE, false, promptHandler, noToolHandler);
392 }
393
394 private Optional<ExecutionResult> invokeMerge(String toolName)
395 throws ToolException {
396 BooleanTriState prompt = BooleanTriState.UNSET;
397 boolean gui = false;
398
399 MergeTools manager = new MergeTools(db);
400
401 PromptHandler promptHandler = PromptHandler.acceptPrompt();
402 MissingToolHandler noToolHandler = new MissingToolHandler();
403
404 Optional<ExecutionResult> result = manager.merge(local, remote, merged,
405 base, null, Optional.of(toolName), prompt, gui, promptHandler,
406 noToolHandler);
407 return result;
408 }
409
410 private void assumeMergeToolIsAvailable(
411 CommandLineMergeTool autoMergingTool) {
412 boolean isAvailable = ExternalToolUtils.isToolAvailable(db.getFS(),
413 db.getDirectory(), db.getWorkTree(), autoMergingTool.getPath());
414 assumeTrue("Assuming external tool is available: "
415 + autoMergingTool.name(), isAvailable);
416 }
417
418 private String getEchoCommand() {
419 return "(echo $LOCAL $REMOTE $MERGED $BASE) > "
420 + commandResult.getAbsolutePath();
421 }
422
423 private void assertEchoCommandHasCorrectOutput() throws IOException {
424 List<String> actualLines = Files.readAllLines(commandResult.toPath());
425 String actualContent = String.join(System.lineSeparator(), actualLines);
426 actualLines = Arrays.asList(actualContent.split(" "));
427 List<String> expectedLines = Arrays.asList(localFile.getAbsolutePath(),
428 remoteFile.getAbsolutePath(), mergedFile.getAbsolutePath(),
429 baseFile.getAbsolutePath());
430 assertEquals("Dummy test tool called with unexpected arguments",
431 expectedLines, actualLines);
432 }
433 }