1
2
3
4
5
6
7
8
9
10
11 package org.eclipse.jgit.internal.diffmergetool;
12
13 import java.io.File;
14 import java.io.IOException;
15 import java.nio.file.Files;
16 import java.nio.file.Path;
17 import java.nio.file.Paths;
18 import java.nio.file.StandardCopyOption;
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.LinkedHashSet;
22 import java.util.Map;
23 import java.util.Map.Entry;
24 import java.util.Optional;
25 import java.util.Set;
26 import java.util.TreeMap;
27
28 import org.eclipse.jgit.internal.JGitText;
29 import org.eclipse.jgit.internal.diffmergetool.FileElement.Type;
30 import org.eclipse.jgit.lib.Repository;
31 import org.eclipse.jgit.lib.StoredConfig;
32 import org.eclipse.jgit.lib.internal.BooleanTriState;
33 import org.eclipse.jgit.treewalk.TreeWalk;
34 import org.eclipse.jgit.util.FS;
35 import org.eclipse.jgit.util.StringUtils;
36 import org.eclipse.jgit.util.FS.ExecutionResult;
37
38
39
40
41 public class MergeTools {
42
43 private final FS fs;
44
45 private final File gitDir;
46
47 private final File workTree;
48
49 private final MergeToolConfig config;
50
51 private final Repository repo;
52
53 private final Map<String, ExternalMergeTool> predefinedTools;
54
55 private final Map<String, ExternalMergeTool> userDefinedTools;
56
57
58
59
60
61
62
63 public MergeTools(Repository repo) {
64 this(repo, repo.getConfig());
65 }
66
67
68
69
70
71
72
73 public MergeTools(StoredConfig config) {
74 this(null, config);
75 }
76
77 private MergeTools(Repository repo, StoredConfig config) {
78 this.repo = repo;
79 this.config = config.get(MergeToolConfig.KEY);
80 this.gitDir = repo == null ? null : repo.getDirectory();
81 this.fs = repo == null ? FS.DETECTED : repo.getFS();
82 this.workTree = repo == null ? null : repo.getWorkTree();
83 predefinedTools = setupPredefinedTools();
84 userDefinedTools = setupUserDefinedTools(predefinedTools);
85 }
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118 public Optional<ExecutionResult> merge(FileElement localFile,
119 FileElement remoteFile, FileElement mergedFile,
120 FileElement baseFile, File tempDir, Optional<String> toolName,
121 BooleanTriState prompt, boolean gui,
122 PromptContinueHandler promptHandler,
123 InformNoToolHandler noToolHandler) throws ToolException {
124
125 String toolNameToUse;
126
127 if (toolName == null) {
128 throw new ToolException(JGitText.get().diffToolNullError);
129 }
130
131 if (toolName.isPresent()) {
132 toolNameToUse = toolName.get();
133 } else {
134 toolNameToUse = getDefaultToolName(gui);
135
136 if (StringUtils.isEmptyOrNull(toolNameToUse)) {
137 noToolHandler.inform(new ArrayList<>(predefinedTools.keySet()));
138 toolNameToUse = getFirstAvailableTool();
139 }
140 }
141
142 if (StringUtils.isEmptyOrNull(toolNameToUse)) {
143 throw new ToolException(JGitText.get().diffToolNotGivenError);
144 }
145
146 boolean doPrompt;
147 if (prompt != BooleanTriState.UNSET) {
148 doPrompt = prompt == BooleanTriState.TRUE;
149 } else {
150 doPrompt = isInteractive();
151 }
152
153 if (doPrompt) {
154 if (!promptHandler.prompt(toolNameToUse)) {
155 return Optional.empty();
156 }
157 }
158
159 ExternalMergeTool tool = getTool(toolNameToUse);
160 if (tool == null) {
161 throw new ToolException(
162 "External merge tool is not defined: " + toolNameToUse);
163 }
164
165 return Optional.of(merge(localFile, remoteFile, mergedFile, baseFile,
166 tempDir, tool));
167 }
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188 public ExecutionResult merge(FileElement localFile, FileElement remoteFile,
189 FileElement mergedFile, FileElement baseFile, File tempDir,
190 ExternalMergeTool tool) throws ToolException {
191 FileElement backup = null;
192 ExecutionResult result = null;
193 try {
194
195 backup = createBackupFile(mergedFile,
196 tempDir != null ? tempDir : workTree);
197
198 String command = ExternalToolUtils.prepareCommand(
199 tool.getCommand(baseFile != null), localFile, remoteFile,
200 mergedFile, baseFile);
201
202 Map<String, String> env = ExternalToolUtils.prepareEnvironment(
203 gitDir, localFile, remoteFile, mergedFile, baseFile);
204 boolean trust = tool.getTrustExitCode() == BooleanTriState.TRUE;
205
206 CommandExecutor cmdExec = new CommandExecutor(fs, trust);
207 result = cmdExec.run(command, workTree, env);
208
209 if (backup != null) {
210 keepBackupFile(mergedFile.getPath(), backup);
211 }
212 return result;
213 } catch (IOException | InterruptedException e) {
214 throw new ToolException(e);
215 } finally {
216
217
218 if (backup != null) {
219 backup.cleanTemporaries();
220 }
221
222
223 if (!((result == null) && config.isKeepTemporaries())) {
224
225 localFile.cleanTemporaries();
226 remoteFile.cleanTemporaries();
227 if (baseFile != null) {
228 baseFile.cleanTemporaries();
229 }
230
231 if (config.isWriteToTemp() && (tempDir != null)
232 && tempDir.exists()) {
233 tempDir.delete();
234 }
235 }
236 }
237 }
238
239 private FileElement createBackupFile(FileElement from, File toParentDir)
240 throws IOException {
241 FileElement backup = null;
242 Path path = Paths.get(from.getPath());
243 if (Files.exists(path)) {
244 backup = new FileElement(from.getPath(), Type.BACKUP);
245 Files.copy(path, backup.createTempFile(toParentDir).toPath(),
246 StandardCopyOption.REPLACE_EXISTING);
247 }
248 return backup;
249 }
250
251
252
253
254
255
256
257
258 public File createTempDirectory() throws IOException {
259 return config.isWriteToTemp()
260 ? Files.createTempDirectory("jgit-mergetool-").toFile()
261 : null;
262 }
263
264
265
266
267
268
269 public Set<String> getUserDefinedToolNames() {
270 return userDefinedTools.keySet();
271 }
272
273
274
275
276 public Set<String> getPredefinedToolNames() {
277 return predefinedTools.keySet();
278 }
279
280
281
282
283
284
285
286 public Set<String> getAllToolNames() {
287 String defaultName = getDefaultToolName(false);
288 if (defaultName == null) {
289 defaultName = getFirstAvailableTool();
290 }
291 return ExternalToolUtils.createSortedToolSet(defaultName,
292 getUserDefinedToolNames(), getPredefinedToolNames());
293 }
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309 public Optional<String> getExternalToolFromAttributes(final String path)
310 throws ToolException {
311 return ExternalToolUtils.getExternalToolFromAttributes(repo, path,
312 ExternalToolUtils.KEY_MERGE_TOOL);
313 }
314
315
316
317
318
319
320 public Set<String> getPredefinedAvailableTools() {
321 Map<String, ExternalMergeTool> defTools = getPredefinedTools(true);
322 Set<String> availableTools = new LinkedHashSet<>();
323 for (Entry<String, ExternalMergeTool> elem : defTools.entrySet()) {
324 if (elem.getValue().isAvailable()) {
325 availableTools.add(elem.getKey());
326 }
327 }
328 return availableTools;
329 }
330
331
332
333
334 public Map<String, ExternalMergeTool> getUserDefinedTools() {
335 return Collections.unmodifiableMap(userDefinedTools);
336 }
337
338
339
340
341
342
343
344
345
346
347
348
349 public Map<String, ExternalMergeTool> getPredefinedTools(
350 boolean checkAvailability) {
351 if (checkAvailability) {
352 for (ExternalMergeTool tool : predefinedTools.values()) {
353 PreDefinedMergeTool predefTool = (PreDefinedMergeTool) tool;
354 predefTool.setAvailable(ExternalToolUtils.isToolAvailable(fs,
355 gitDir, workTree, predefTool.getPath()));
356 }
357 }
358 return Collections.unmodifiableMap(predefinedTools);
359 }
360
361
362
363
364
365
366 public String getFirstAvailableTool() {
367 String name = null;
368 for (ExternalMergeTool tool : predefinedTools.values()) {
369 if (ExternalToolUtils.isToolAvailable(fs, gitDir, workTree,
370 tool.getPath())) {
371 name = tool.getName();
372 break;
373 }
374 }
375 return name;
376 }
377
378
379
380
381
382
383 public boolean isInteractive() {
384 return config.isPrompt();
385 }
386
387
388
389
390
391
392
393
394 public String getDefaultToolName(boolean gui) {
395 return gui ? config.getDefaultGuiToolName()
396 : config.getDefaultToolName();
397 }
398
399 private ExternalMergeTool getTool(final String name) {
400 ExternalMergeTool tool = userDefinedTools.get(name);
401 if (tool == null) {
402 tool = predefinedTools.get(name);
403 }
404 return tool;
405 }
406
407 private void keepBackupFile(String mergedFilePath, FileElement backup)
408 throws IOException {
409 if (config.isKeepBackup()) {
410 Path backupPath = backup.getFile().toPath();
411 Files.move(backupPath,
412 backupPath.resolveSibling(
413 Paths.get(mergedFilePath).getFileName() + ".orig"),
414 StandardCopyOption.REPLACE_EXISTING);
415 }
416 }
417
418 private Map<String, ExternalMergeTool> setupPredefinedTools() {
419 Map<String, ExternalMergeTool> tools = new TreeMap<>();
420 for (CommandLineMergeTool tool : CommandLineMergeTool.values()) {
421 tools.put(tool.name(), new PreDefinedMergeTool(tool));
422 }
423 return tools;
424 }
425
426 private Map<String, ExternalMergeTool> setupUserDefinedTools(
427 Map<String, ExternalMergeTool> predefTools) {
428 Map<String, ExternalMergeTool> tools = new TreeMap<>();
429 Map<String, ExternalMergeTool> userTools = config.getTools();
430 for (String name : userTools.keySet()) {
431 ExternalMergeTool userTool = userTools.get(name);
432
433 if (userTool.getCommand() != null) {
434 tools.put(name, userTool);
435 } else if (userTool.getPath() != null) {
436
437
438 PreDefinedMergeTool predefTool = (PreDefinedMergeTool) predefTools
439 .get(name);
440 if (predefTool != null) {
441 predefTool.setPath(userTool.getPath());
442 if (userTool.getTrustExitCode() != BooleanTriState.UNSET) {
443 predefTool
444 .setTrustExitCode(userTool.getTrustExitCode());
445 }
446 }
447 }
448 }
449 return tools;
450 }
451
452 }