summaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit
diff options
context:
space:
mode:
authorAndrey Loskutov <loskutov@gmx.de>2022-06-03 15:49:45 +0200
committerAndrey Loskutov <loskutov@gmx.de>2022-06-03 15:51:54 +0200
commit2ef2a3562e9aa5c826ce063a2f1d3d8ffef1f0ce (patch)
treeb0f2988a40474499808220cd7d5fe677d87b6600 /org.eclipse.jgit
parent7b1ade51d24f6efacfddd91fcfed7137c27642d3 (diff)
parentc32694e5ae98e5c1373afcb535a76d37e5d82a29 (diff)
downloadjgit-2ef2a3562e9aa5c826ce063a2f1d3d8ffef1f0ce.tar.gz
jgit-2ef2a3562e9aa5c826ce063a2f1d3d8ffef1f0ce.zip
Merge branch 'master' into stable-6.2
* master: Adapt diff- and merge tool code for PGM and EGit usage Teach JGit to handle external diff/merge tools defined in .gitattributes Change-Id: I3aefc14160caaac859bd3548460dd755ebe42fc5
Diffstat (limited to 'org.eclipse.jgit')
-rw-r--r--org.eclipse.jgit/META-INF/MANIFEST.MF6
-rw-r--r--org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandExecutor.java51
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandLineDiffTool.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java296
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalDiffTool.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalToolUtils.java181
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/FileElement.java183
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/InformNoToolHandler.java28
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeToolConfig.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeTools.java301
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedDiffTool.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedMergeTool.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PromptContinueHandler.java27
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ToolException.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/UserDefinedDiffTool.java19
17 files changed, 907 insertions, 217 deletions
diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF
index 242b3a4c92..fda154f93e 100644
--- a/org.eclipse.jgit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit/META-INF/MANIFEST.MF
@@ -73,7 +73,8 @@ Export-Package: org.eclipse.jgit.annotations;version="6.2.0",
org.eclipse.jgit.internal.diffmergetool;version="6.2.0";
x-friends:="org.eclipse.jgit.test,
org.eclipse.jgit.pgm.test,
- org.eclipse.jgit.pgm",
+ org.eclipse.jgit.pgm,
+ org.eclipse.egit.ui",
org.eclipse.jgit.internal.fsck;version="6.2.0";
x-friends:="org.eclipse.jgit.test",
org.eclipse.jgit.internal.revwalk;version="6.2.0";
@@ -133,7 +134,8 @@ Export-Package: org.eclipse.jgit.annotations;version="6.2.0",
org.eclipse.jgit.util.time",
org.eclipse.jgit.lib.internal;version="6.2.0";
x-friends:="org.eclipse.jgit.test,
- org.eclipse.jgit.pgm",
+ org.eclipse.jgit.pgm,
+ org.eclipse.egit.ui",
org.eclipse.jgit.logging;version="6.2.0",
org.eclipse.jgit.merge;version="6.2.0";
uses:="org.eclipse.jgit.dircache,
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
index 9f264fca34..f0bb6c6c99 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -237,6 +237,9 @@ deleteTagUnexpectedResult=Delete tag returned unexpected result {0}
deletingNotSupported=Deleting {0} not supported.
destinationIsNotAWildcard=Destination is not a wildcard.
detachedHeadDetected=HEAD is detached
+diffToolNotGivenError=No diff tool provided and no defaults configured.
+diffToolNotSpecifiedInGitAttributesError=Diff tool specified in git attributes cannot be found.
+diffToolNullError=Parameter for diff tool cannot be null.
dirCacheDoesNotHaveABackingFile=DirCache does not have a backing file
dirCacheFileIsNotLocked=DirCache {0} not locked
dirCacheIsNotLocked=DirCache is not locked
@@ -457,6 +460,8 @@ mergeStrategyDoesNotSupportHeads=merge strategy {0} does not support {1} heads t
mergeUsingStrategyResultedInDescription=Merge of revisions {0} with base {1} using strategy {2} resulted in: {3}. {4}
mergeRecursiveConflictsWhenMergingCommonAncestors=Multiple common ancestors were found and merging them resulted in a conflict: {0}, {1}
mergeRecursiveTooManyMergeBasesFor = "More than {0} merge bases for:\n a {1}\n b {2} found:\n count {3}"
+mergeToolNotGivenError=No merge tool provided and no defaults configured.
+mergeToolNullError=Parameter for merge tool cannot be null.
messageAndTaggerNotAllowedInUnannotatedTags = Unannotated tags cannot have a message or tagger
minutesAgo={0} minutes ago
mismatchOffset=mismatch offset for object {0}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
index b81e605c13..17e359de49 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -265,6 +265,9 @@ public class JGitText extends TranslationBundle {
/***/ public String deletingNotSupported;
/***/ public String destinationIsNotAWildcard;
/***/ public String detachedHeadDetected;
+ /***/ public String diffToolNotGivenError;
+ /***/ public String diffToolNotSpecifiedInGitAttributesError;
+ /***/ public String diffToolNullError;
/***/ public String dirCacheDoesNotHaveABackingFile;
/***/ public String dirCacheFileIsNotLocked;
/***/ public String dirCacheIsNotLocked;
@@ -485,6 +488,8 @@ public class JGitText extends TranslationBundle {
/***/ public String mergeUsingStrategyResultedInDescription;
/***/ public String mergeRecursiveConflictsWhenMergingCommonAncestors;
/***/ public String mergeRecursiveTooManyMergeBasesFor;
+ /***/ public String mergeToolNotGivenError;
+ /***/ public String mergeToolNullError;
/***/ public String messageAndTaggerNotAllowedInUnannotatedTags;
/***/ public String minutesAgo;
/***/ public String mismatchOffset;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandExecutor.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandExecutor.java
index ad79fe8fc6..668adeab65 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandExecutor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandExecutor.java
@@ -14,13 +14,19 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Map;
+
+import org.eclipse.jgit.errors.NoWorkTreeException;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FS.ExecutionResult;
import org.eclipse.jgit.util.FS_POSIX;
import org.eclipse.jgit.util.FS_Win32;
import org.eclipse.jgit.util.FS_Win32_Cygwin;
+import org.eclipse.jgit.util.StringUtils;
/**
* Runs a command with help of FS.
@@ -91,6 +97,49 @@ public class CommandExecutor {
}
}
+ /**
+ * @param path
+ * the executable path
+ * @param workingDir
+ * the working directory
+ * @param env
+ * the environment
+ * @return the execution result
+ * @throws ToolException
+ * @throws InterruptedException
+ * @throws IOException
+ */
+ public boolean checkExecutable(String path, File workingDir,
+ Map<String, String> env)
+ throws ToolException, IOException, InterruptedException {
+ checkUseMsys2(path);
+ String command = null;
+ if (fs instanceof FS_Win32 && !useMsys2) {
+ Path p = Paths.get(path);
+ // Win32 (and not cygwin or MSYS2) where accepts only command / exe
+ // name as parameter
+ // so check if exists and executable in this case
+ if (p.isAbsolute() && Files.isExecutable(p)) {
+ return true;
+ }
+ // try where command for all other cases
+ command = "where " + ExternalToolUtils.quotePath(path); //$NON-NLS-1$
+ } else {
+ command = "which " + ExternalToolUtils.quotePath(path); //$NON-NLS-1$
+ }
+ boolean available = true;
+ try {
+ ExecutionResult rc = run(command, workingDir, env);
+ if (rc.getRc() != 0) {
+ available = false;
+ }
+ } catch (IOException | InterruptedException | NoWorkTreeException
+ | ToolException e) {
+ // no op: is true to not hide possible tools from user
+ }
+ return available;
+ }
+
private void deleteCommandArray() {
deleteCommandFile();
}
@@ -127,7 +176,7 @@ public class CommandExecutor {
private void checkUseMsys2(String command) {
useMsys2 = false;
String useMsys2Str = System.getProperty("jgit.usemsys2bash"); //$NON-NLS-1$
- if (useMsys2Str != null && !useMsys2Str.isEmpty()) {
+ if (!StringUtils.isEmptyOrNull(useMsys2Str)) {
if (useMsys2Str.equalsIgnoreCase("auto")) { //$NON-NLS-1$
useMsys2 = command.contains(".sh"); //$NON-NLS-1$
} else {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandLineDiffTool.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandLineDiffTool.java
index 509515c37a..00dec32718 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandLineDiffTool.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandLineDiffTool.java
@@ -111,7 +111,7 @@ public enum CommandLineDiffTool {
* See: <a href=
* "http://vimdoc.sourceforge.net/htmldoc/diff.html">http://vimdoc.sourceforge.net/htmldoc/diff.html</a>
*/
- gvimdiff("gviewdiff", "\"$LOCAL\" \"$REMOTE\""),
+ gvimdiff("gvimdiff", "\"$LOCAL\" \"$REMOTE\""),
/**
* See: <a href=
* "http://vimdoc.sourceforge.net/htmldoc/diff.html">http://vimdoc.sourceforge.net/htmldoc/diff.html</a>
@@ -160,7 +160,7 @@ public enum CommandLineDiffTool {
* See: <a href=
* "http://vimdoc.sourceforge.net/htmldoc/diff.html">http://vimdoc.sourceforge.net/htmldoc/diff.html</a>
*/
- vimdiff("viewdiff", gvimdiff),
+ vimdiff("vimdiff", gvimdiff),
/**
* See: <a href=
* "http://vimdoc.sourceforge.net/htmldoc/diff.html">http://vimdoc.sourceforge.net/htmldoc/diff.html</a>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java
index 1dcc523bf8..7cedd82995 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2018-2022, Andre Bossert <andre.bossert@siemens.com>
+ * Copyright (C) 2019, Tim Neumann <tim.neumann@advantest.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
@@ -10,14 +11,22 @@
package org.eclipse.jgit.internal.diffmergetool;
-import java.util.TreeMap;
-import java.util.Collections;
+import java.io.File;
import java.io.IOException;
+import java.util.Collections;
+import java.util.LinkedHashSet;
import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
import java.util.Set;
+import java.util.TreeMap;
+import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.lib.internal.BooleanTriState;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FS.ExecutionResult;
import org.eclipse.jgit.util.StringUtils;
@@ -26,10 +35,16 @@ import org.eclipse.jgit.util.StringUtils;
*/
public class DiffTools {
- private final Repository repo;
+ private final FS fs;
+
+ private final File gitDir;
+
+ private final File workTree;
private final DiffToolConfig config;
+ private final Repository repo;
+
private final Map<String, ExternalDiffTool> predefinedTools;
private final Map<String, ExternalDiffTool> userDefinedTools;
@@ -41,69 +56,220 @@ public class DiffTools {
* the repository
*/
public DiffTools(Repository repo) {
+ this(repo, repo.getConfig());
+ }
+
+ /**
+ * Creates the external merge-tools manager for given configuration.
+ *
+ * @param config
+ * the git configuration
+ */
+ public DiffTools(StoredConfig config) {
+ this(null, config);
+ }
+
+ private DiffTools(Repository repo, StoredConfig config) {
this.repo = repo;
- config = repo.getConfig().get(DiffToolConfig.KEY);
+ this.config = config.get(DiffToolConfig.KEY);
+ this.gitDir = repo == null ? null : repo.getDirectory();
+ this.fs = repo == null ? FS.DETECTED : repo.getFS();
+ this.workTree = repo == null ? null : repo.getWorkTree();
predefinedTools = setupPredefinedTools();
- userDefinedTools = setupUserDefinedTools(config, predefinedTools);
+ userDefinedTools = setupUserDefinedTools(predefinedTools);
}
/**
* Compare two versions of a file.
*
* @param localFile
- * the local file element
+ * The local/left version of the file.
* @param remoteFile
- * the remote file element
- * @param mergedFile
- * the merged file element, it's path equals local or remote
- * element path
+ * The remote/right version of the file.
* @param toolName
- * the selected tool name (can be null)
+ * Optionally the name of the tool to use. If not given the
+ * default tool will be used.
* @param prompt
- * the prompt option
+ * Optionally a flag whether to prompt the user before compare.
+ * If not given the default will be used.
* @param gui
- * the GUI option
+ * A flag whether to prefer a gui tool.
+ * @param trustExitCode
+ * Optionally a flag whether to trust the exit code of the tool.
+ * If not given the default will be used.
+ * @param promptHandler
+ * The handler to use when needing to prompt the user if he wants
+ * to continue.
+ * @param noToolHandler
+ * The handler to use when needing to inform the user, that no
+ * tool is configured.
+ * @return the optioanl result of executing the tool if it was executed
+ * @throws ToolException
+ * when the tool fails
+ */
+ public Optional<ExecutionResult> compare(FileElement localFile,
+ FileElement remoteFile, Optional<String> toolName,
+ BooleanTriState prompt, boolean gui, BooleanTriState trustExitCode,
+ PromptContinueHandler promptHandler,
+ InformNoToolHandler noToolHandler) throws ToolException {
+
+ String toolNameToUse;
+
+ if (toolName == null) {
+ throw new ToolException(JGitText.get().diffToolNullError);
+ }
+
+ if (toolName.isPresent()) {
+ toolNameToUse = toolName.get();
+ } else {
+ toolNameToUse = getDefaultToolName(gui);
+ }
+
+ if (StringUtils.isEmptyOrNull(toolNameToUse)) {
+ throw new ToolException(JGitText.get().diffToolNotGivenError);
+ }
+
+ boolean doPrompt;
+ if (prompt != BooleanTriState.UNSET) {
+ doPrompt = prompt == BooleanTriState.TRUE;
+ } else {
+ doPrompt = isInteractive();
+ }
+
+ if (doPrompt) {
+ if (!promptHandler.prompt(toolNameToUse)) {
+ return Optional.empty();
+ }
+ }
+
+ boolean trust;
+ if (trustExitCode != BooleanTriState.UNSET) {
+ trust = trustExitCode == BooleanTriState.TRUE;
+ } else {
+ trust = config.isTrustExitCode();
+ }
+
+ ExternalDiffTool tool = getTool(toolNameToUse);
+ if (tool == null) {
+ throw new ToolException(
+ "External diff tool is not defined: " + toolNameToUse); //$NON-NLS-1$
+ }
+
+ return Optional.of(
+ compare(localFile, remoteFile, tool, trust));
+ }
+
+ /**
+ * Compare two versions of a file.
+ *
+ * @param localFile
+ * the local file element
+ * @param remoteFile
+ * the remote file element
+ * @param tool
+ * the selected tool
* @param trustExitCode
* the "trust exit code" option
* @return the execution result from tool
* @throws ToolException
*/
public ExecutionResult compare(FileElement localFile,
- FileElement remoteFile, FileElement mergedFile, String toolName,
- BooleanTriState prompt, BooleanTriState gui,
- BooleanTriState trustExitCode) throws ToolException {
+ FileElement remoteFile, ExternalDiffTool tool,
+ boolean trustExitCode) throws ToolException {
try {
+ if (tool == null) {
+ throw new ToolException(JGitText
+ .get().diffToolNotSpecifiedInGitAttributesError);
+ }
// prepare the command (replace the file paths)
- String command = ExternalToolUtils.prepareCommand(
- guessTool(toolName, gui).getCommand(), localFile,
- remoteFile, mergedFile, null);
+ String command = ExternalToolUtils.prepareCommand(tool.getCommand(),
+ localFile, remoteFile, null, null);
// prepare the environment
- Map<String, String> env = ExternalToolUtils.prepareEnvironment(repo,
- localFile, remoteFile, mergedFile, null);
- boolean trust = config.isTrustExitCode();
- if (trustExitCode != BooleanTriState.UNSET) {
- trust = trustExitCode == BooleanTriState.TRUE;
- }
+ Map<String, String> env = ExternalToolUtils.prepareEnvironment(
+ gitDir, localFile, remoteFile, null, null);
// execute the tool
- CommandExecutor cmdExec = new CommandExecutor(repo.getFS(), trust);
- return cmdExec.run(command, repo.getWorkTree(), env);
+ CommandExecutor cmdExec = new CommandExecutor(fs, trustExitCode);
+ return cmdExec.run(command, workTree, env);
} catch (IOException | InterruptedException e) {
throw new ToolException(e);
} finally {
localFile.cleanTemporaries();
remoteFile.cleanTemporaries();
- mergedFile.cleanTemporaries();
}
}
/**
- * @return the tool names
+ * Get user defined tool names.
+ *
+ * @return the user defined tool names
+ */
+ public Set<String> getUserDefinedToolNames() {
+ return userDefinedTools.keySet();
+ }
+
+ /**
+ * Get predefined tool names.
+ *
+ * @return the predefined tool names
+ */
+ public Set<String> getPredefinedToolNames() {
+ return predefinedTools.keySet();
+ }
+
+ /**
+ * Get all tool names.
+ *
+ * @return the all tool names (default or available tool name is the first
+ * in the set)
+ */
+ public Set<String> getAllToolNames() {
+ String defaultName = getDefaultToolName(false);
+ if (defaultName == null) {
+ defaultName = getFirstAvailableTool();
+ }
+ return ExternalToolUtils.createSortedToolSet(defaultName,
+ getUserDefinedToolNames(), getPredefinedToolNames());
+ }
+
+ /**
+ * Provides {@link Optional} with the name of an external diff tool if
+ * specified in git configuration for a path.
+ *
+ * The formed git configuration results from global rules as well as merged
+ * rules from info and worktree attributes.
+ *
+ * Triggers {@link TreeWalk} until specified path found in the tree.
+ *
+ * @param path
+ * path to the node in repository to parse git attributes for
+ * @return name of the difftool if set
+ * @throws ToolException
+ */
+ public Optional<String> getExternalToolFromAttributes(final String path)
+ throws ToolException {
+ return ExternalToolUtils.getExternalToolFromAttributes(repo, path,
+ ExternalToolUtils.KEY_DIFF_TOOL);
+ }
+
+ /**
+ * Checks the availability of the predefined tools in the system.
+ *
+ * @return set of predefined available tools
*/
- public Set<String> getToolNames() {
- return config.getToolNames();
+ public Set<String> getPredefinedAvailableTools() {
+ Map<String, ExternalDiffTool> defTools = getPredefinedTools(true);
+ Set<String> availableTools = new LinkedHashSet<>();
+ for (Entry<String, ExternalDiffTool> elem : defTools.entrySet()) {
+ if (elem.getValue().isAvailable()) {
+ availableTools.add(elem.getKey());
+ }
+ }
+ return availableTools;
}
/**
+ * Get user defined tools map.
+ *
* @return the user defined tools
*/
public Map<String, ExternalDiffTool> getUserDefinedTools() {
@@ -111,48 +277,70 @@ public class DiffTools {
}
/**
- * @return the available predefined tools
+ * Get predefined tools map.
+ *
+ * @param checkAvailability
+ * true: for checking if tools can be executed; ATTENTION: this
+ * check took some time, do not execute often (store the map for
+ * other actions); false: availability is NOT checked:
+ * isAvailable() returns default false is this case!
+ * @return the predefined tools with optionally checked availability (long
+ * running operation)
*/
- public Map<String, ExternalDiffTool> getAvailableTools() {
+ public Map<String, ExternalDiffTool> getPredefinedTools(
+ boolean checkAvailability) {
+ if (checkAvailability) {
+ for (ExternalDiffTool tool : predefinedTools.values()) {
+ PreDefinedDiffTool predefTool = (PreDefinedDiffTool) tool;
+ predefTool.setAvailable(ExternalToolUtils.isToolAvailable(fs,
+ gitDir, workTree, predefTool.getPath()));
+ }
+ }
return Collections.unmodifiableMap(predefinedTools);
}
/**
- * @return the NOT available predefined tools
+ * Get first available tool name.
+ *
+ * @return the name of first available predefined tool or null
*/
- public Map<String, ExternalDiffTool> getNotAvailableTools() {
- return Collections.unmodifiableMap(new TreeMap<>());
+ public String getFirstAvailableTool() {
+ for (ExternalDiffTool tool : predefinedTools.values()) {
+ if (ExternalToolUtils.isToolAvailable(fs, gitDir, workTree,
+ tool.getPath())) {
+ return tool.getName();
+ }
+ }
+ return null;
}
/**
+ * Get default (gui-)tool name.
+ *
* @param gui
* use the diff.guitool setting ?
* @return the default tool name
*/
- public String getDefaultToolName(BooleanTriState gui) {
- return gui != BooleanTriState.UNSET ? "my_gui_tool" //$NON-NLS-1$
- : config.getDefaultToolName();
+ public String getDefaultToolName(boolean gui) {
+ String guiToolName;
+ if (gui) {
+ guiToolName = config.getDefaultGuiToolName();
+ if (guiToolName != null) {
+ return guiToolName;
+ }
+ }
+ return config.getDefaultToolName();
}
/**
+ * Is interactive diff (prompt enabled) ?
+ *
* @return is interactive (config prompt enabled) ?
*/
public boolean isInteractive() {
return config.isPrompt();
}
- private ExternalDiffTool guessTool(String toolName, BooleanTriState gui)
- throws ToolException {
- if (StringUtils.isEmptyOrNull(toolName)) {
- toolName = getDefaultToolName(gui);
- }
- ExternalDiffTool tool = getTool(toolName);
- if (tool == null) {
- throw new ToolException("Unknown diff tool " + toolName); //$NON-NLS-1$
- }
- return tool;
- }
-
private ExternalDiffTool getTool(final String name) {
ExternalDiffTool tool = userDefinedTools.get(name);
if (tool == null) {
@@ -169,10 +357,10 @@ public class DiffTools {
return tools;
}
- private static Map<String, ExternalDiffTool> setupUserDefinedTools(
- DiffToolConfig cfg, Map<String, ExternalDiffTool> predefTools) {
+ private Map<String, ExternalDiffTool> setupUserDefinedTools(
+ Map<String, ExternalDiffTool> predefTools) {
Map<String, ExternalDiffTool> tools = new TreeMap<>();
- Map<String, ExternalDiffTool> userTools = cfg.getTools();
+ Map<String, ExternalDiffTool> userTools = config.getTools();
for (String name : userTools.keySet()) {
ExternalDiffTool userTool = userTools.get(name);
// if difftool.<name>.cmd is defined we have user defined tool
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalDiffTool.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalDiffTool.java
index f2d7e828cb..e01b892a53 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalDiffTool.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalDiffTool.java
@@ -30,4 +30,10 @@ public interface ExternalDiffTool {
*/
String getCommand();
+ /**
+ * @return availability of the tool: true if tool can be executed and false
+ * if not
+ */
+ boolean isAvailable();
+
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalToolUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalToolUtils.java
index 3efb90c490..b2dd846d70 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalToolUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalToolUtils.java
@@ -10,10 +10,22 @@
package org.eclipse.jgit.internal.diffmergetool;
import java.util.TreeMap;
+import java.io.File;
import java.io.IOException;
+import java.util.LinkedHashSet;
import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import org.eclipse.jgit.attributes.Attributes;
+import org.eclipse.jgit.errors.RevisionSyntaxException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.treewalk.FileTreeIterator;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.WorkingTreeIterator;
+import org.eclipse.jgit.treewalk.filter.NotIgnoredFilter;
+import org.eclipse.jgit.util.FS;
/**
* Utilities for diff- and merge-tools.
@@ -21,6 +33,16 @@ import org.eclipse.jgit.lib.Repository;
public class ExternalToolUtils {
/**
+ * Key for merge tool git configuration section
+ */
+ public static final String KEY_MERGE_TOOL = "mergetool"; //$NON-NLS-1$
+
+ /**
+ * Key for diff tool git configuration section
+ */
+ public static final String KEY_DIFF_TOOL = "difftool"; //$NON-NLS-1$
+
+ /**
* Prepare command for execution.
*
* @param command
@@ -39,9 +61,15 @@ public class ExternalToolUtils {
public static String prepareCommand(String command, FileElement localFile,
FileElement remoteFile, FileElement mergedFile,
FileElement baseFile) throws IOException {
- command = localFile.replaceVariable(command);
- command = remoteFile.replaceVariable(command);
- command = mergedFile.replaceVariable(command);
+ if (localFile != null) {
+ command = localFile.replaceVariable(command);
+ }
+ if (remoteFile != null) {
+ command = remoteFile.replaceVariable(command);
+ }
+ if (mergedFile != null) {
+ command = mergedFile.replaceVariable(command);
+ }
if (baseFile != null) {
command = baseFile.replaceVariable(command);
}
@@ -51,8 +79,8 @@ public class ExternalToolUtils {
/**
* Prepare environment needed for execution.
*
- * @param repo
- * the repository
+ * @param gitDir
+ * the .git directory
* @param localFile
* the local file (ours)
* @param remoteFile
@@ -64,18 +92,151 @@ public class ExternalToolUtils {
* @return the environment map with variables and values (file paths)
* @throws IOException
*/
- public static Map<String, String> prepareEnvironment(Repository repo,
+ public static Map<String, String> prepareEnvironment(File gitDir,
FileElement localFile, FileElement remoteFile,
FileElement mergedFile, FileElement baseFile) throws IOException {
Map<String, String> env = new TreeMap<>();
- env.put(Constants.GIT_DIR_KEY, repo.getDirectory().getAbsolutePath());
- localFile.addToEnv(env);
- remoteFile.addToEnv(env);
- mergedFile.addToEnv(env);
+ if (gitDir != null) {
+ env.put(Constants.GIT_DIR_KEY, gitDir.getAbsolutePath());
+ }
+ if (localFile != null) {
+ localFile.addToEnv(env);
+ }
+ if (remoteFile != null) {
+ remoteFile.addToEnv(env);
+ }
+ if (mergedFile != null) {
+ mergedFile.addToEnv(env);
+ }
if (baseFile != null) {
baseFile.addToEnv(env);
}
return env;
}
+ /**
+ * @param path
+ * the path to be quoted
+ * @return quoted path if it contains spaces
+ */
+ @SuppressWarnings("nls")
+ public static String quotePath(String path) {
+ // handling of spaces in path
+ if (path.contains(" ")) {
+ // add quotes before if needed
+ if (!path.startsWith("\"")) {
+ path = "\"" + path;
+ }
+ // add quotes after if needed
+ if (!path.endsWith("\"")) {
+ path = path + "\"";
+ }
+ }
+ return path;
+ }
+
+ /**
+ * @param fs
+ * the file system abstraction
+ * @param gitDir
+ * the .git directory
+ * @param directory
+ * the working directory
+ * @param path
+ * the tool path
+ * @return true if tool available and false otherwise
+ */
+ public static boolean isToolAvailable(FS fs, File gitDir, File directory,
+ String path) {
+ boolean available = true;
+ try {
+ CommandExecutor cmdExec = new CommandExecutor(fs, false);
+ available = cmdExec.checkExecutable(path, directory,
+ prepareEnvironment(gitDir, null, null, null, null));
+ } catch (Exception e) {
+ available = false;
+ }
+ return available;
+ }
+
+ /**
+ * @param defaultName
+ * the default tool name
+ * @param userDefinedNames
+ * the user defined tool names
+ * @param preDefinedNames
+ * the pre defined tool names
+ * @return the sorted tool names set: first element is default tool name if
+ * valid, then user defined tool names and then pre defined tool
+ * names
+ */
+ public static Set<String> createSortedToolSet(String defaultName,
+ Set<String> userDefinedNames, Set<String> preDefinedNames) {
+ Set<String> names = new LinkedHashSet<>();
+ if (defaultName != null) {
+ // remove defaultName from both sets
+ Set<String> namesPredef = new LinkedHashSet<>();
+ Set<String> namesUser = new LinkedHashSet<>();
+ namesUser.addAll(userDefinedNames);
+ namesUser.remove(defaultName);
+ namesPredef.addAll(preDefinedNames);
+ namesPredef.remove(defaultName);
+ // add defaultName as first in set
+ names.add(defaultName);
+ names.addAll(namesUser);
+ names.addAll(namesPredef);
+ } else {
+ names.addAll(userDefinedNames);
+ names.addAll(preDefinedNames);
+ }
+ return names;
+ }
+
+ /**
+ * Provides {@link Optional} with the name of an external tool if specified
+ * in git configuration for a path.
+ *
+ * The formed git configuration results from global rules as well as merged
+ * rules from info and worktree attributes.
+ *
+ * Triggers {@link TreeWalk} until specified path found in the tree.
+ *
+ * @param repository
+ * target repository to traverse into
+ * @param path
+ * path to the node in repository to parse git attributes for
+ * @param toolKey
+ * config key name for the tool
+ * @return attribute value for the given tool key if set
+ * @throws ToolException
+ */
+ public static Optional<String> getExternalToolFromAttributes(
+ final Repository repository, final String path,
+ final String toolKey) throws ToolException {
+ try {
+ WorkingTreeIterator treeIterator = new FileTreeIterator(repository);
+ try (TreeWalk walk = new TreeWalk(repository)) {
+ walk.addTree(treeIterator);
+ walk.setFilter(new NotIgnoredFilter(0));
+ while (walk.next()) {
+ String treePath = walk.getPathString();
+ if (treePath.equals(path)) {
+ Attributes attrs = walk.getAttributes();
+ if (attrs.containsKey(toolKey)) {
+ return Optional.of(attrs.getValue(toolKey));
+ }
+ }
+ if (walk.isSubtree()) {
+ walk.enterSubtree();
+ }
+ }
+ // no external tool specified
+ return Optional.empty();
+ }
+
+ } catch (RevisionSyntaxException | IOException e) {
+ throw new ToolException(e);
+ }
+ }
+
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/FileElement.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/FileElement.java
index 5902c1e1b8..ba8ca54c58 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/FileElement.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/FileElement.java
@@ -57,6 +57,8 @@ public class FileElement {
private final Type type;
+ private final File workDir;
+
private InputStream stream;
private File tempFile;
@@ -70,7 +72,7 @@ public class FileElement {
* the element type
*/
public FileElement(String path, Type type) {
- this(path, type, null, null);
+ this(path, type, null);
}
/**
@@ -80,17 +82,31 @@ public class FileElement {
* the file path
* @param type
* the element type
- * @param tempFile
- * the temporary file to be used (can be null and will be created
- * then)
+ * @param workDir
+ * the working directory of the path (can be null, then current
+ * working dir is used)
+ */
+ public FileElement(String path, Type type, File workDir) {
+ this(path, type, workDir, null);
+ }
+
+ /**
+ * @param path
+ * the file path
+ * @param type
+ * the element type
+ * @param workDir
+ * the working directory of the path (can be null, then current
+ * working dir is used)
* @param stream
- * the object stream to load instead of file
+ * the object stream to load and write on demand, @see getFile(),
+ * to tempFile once (can be null)
*/
- public FileElement(String path, Type type, File tempFile,
+ public FileElement(String path, Type type, File workDir,
InputStream stream) {
this.path = path;
this.type = type;
- this.tempFile = tempFile;
+ this.workDir = workDir;
this.stream = stream;
}
@@ -109,41 +125,39 @@ public class FileElement {
}
/**
- * Return a temporary file within passed directory and fills it with stream
- * if valid.
- *
- * @param directory
- * the directory where the temporary file is created
- * @param midName
- * name added in the middle of generated temporary file name
- * @return the object stream
- * @throws IOException
- */
- public File getFile(File directory, String midName) throws IOException {
- if ((tempFile != null) && (stream == null)) {
- return tempFile;
- }
- tempFile = getTempFile(path, directory, midName);
- return copyFromStream(tempFile, stream);
- }
-
- /**
- * Return a real file from work tree or a temporary file with content if
- * stream is valid or if path is "/dev/null"
+ * Return
+ * <ul>
+ * <li>a temporary file if already created and stream is not valid</li>
+ * <li>OR a real file from work tree: if no temp file was created (@see
+ * createTempFile()) and if no stream was set</li>
+ * <li>OR an empty temporary file if path is "/dev/null"</li>
+ * <li>OR a temporary file with stream content if stream is valid (not
+ * null); stream is closed and invalidated (set to null) after write to temp
+ * file, so stream is used only once during first call!</li>
+ * </ul>
*
* @return the object stream
* @throws IOException
*/
public File getFile() throws IOException {
+ // if we have already temp file and no stream
+ // then just return this temp file (it was filled from outside)
if ((tempFile != null) && (stream == null)) {
return tempFile;
}
- File file = new File(path);
- // if we have a stream or file is missing ("/dev/null") then create
- // temporary file
+ File file = new File(workDir, path);
+ // if we have a stream or file is missing (path is "/dev/null")
+ // then optionally create temporary file and fill it with stream content
if ((stream != null) || isNullPath()) {
- tempFile = getTempFile(file);
- return copyFromStream(tempFile, stream);
+ if (tempFile == null) {
+ tempFile = getTempFile(file, type.name(), null);
+ }
+ if (stream != null) {
+ copyFromStream(tempFile, stream);
+ }
+ // invalidate the stream, because it is used once
+ stream = null;
+ return tempFile;
}
return file;
}
@@ -158,7 +172,7 @@ public class FileElement {
}
/**
- * Create temporary file in given or system temporary directory
+ * Create temporary file in given or system temporary directory.
*
* @param directory
* the directory for the file (can be null); if null system
@@ -168,75 +182,23 @@ public class FileElement {
*/
public File createTempFile(File directory) throws IOException {
if (tempFile == null) {
- File file = new File(path);
- if (directory != null) {
- tempFile = getTempFile(file, directory, type.name());
- } else {
- tempFile = getTempFile(file);
- }
+ tempFile = getTempFile(new File(path), type.name(), directory);
}
return tempFile;
}
- private static File getTempFile(File file) throws IOException {
- return File.createTempFile(".__", "__" + file.getName()); //$NON-NLS-1$ //$NON-NLS-2$
- }
-
- private static File getTempFile(File file, File directory, String midName)
- throws IOException {
- String[] fileNameAndExtension = splitBaseFileNameAndExtension(file);
- return File.createTempFile(
- fileNameAndExtension[0] + "_" + midName + "_", //$NON-NLS-1$ //$NON-NLS-2$
- fileNameAndExtension[1], directory);
- }
-
- private static File getTempFile(String path, File directory, String midName)
- throws IOException {
- return getTempFile(new File(path), directory, midName);
- }
-
/**
* Delete and invalidate temporary file if necessary.
*/
public void cleanTemporaries() {
- if (tempFile != null && tempFile.exists())
- tempFile.delete();
- tempFile = null;
- }
-
- private static File copyFromStream(File file, final InputStream stream)
- throws IOException, FileNotFoundException {
- if (stream != null) {
- try (OutputStream outStream = new FileOutputStream(file)) {
- int read = 0;
- byte[] bytes = new byte[8 * 1024];
- while ((read = stream.read(bytes)) != -1) {
- outStream.write(bytes, 0, read);
- }
- } finally {
- // stream can only be consumed once --> close it
- stream.close();
- }
- }
- return file;
- }
-
- private static String[] splitBaseFileNameAndExtension(File file) {
- String[] result = new String[2];
- result[0] = file.getName();
- result[1] = ""; //$NON-NLS-1$
- int idx = result[0].lastIndexOf("."); //$NON-NLS-1$
- // if "." was found (>-1) and last-index is not first char (>0), then
- // split (same behavior like cgit)
- if (idx > 0) {
- result[1] = result[0].substring(idx, result[0].length());
- result[0] = result[0].substring(0, idx);
+ if (tempFile != null && tempFile.exists()) {
+ tempFile.delete();
}
- return result;
+ tempFile = null;
}
/**
- * Replace variable in input
+ * Replace variable in input.
*
* @param input
* the input string
@@ -258,4 +220,43 @@ public class FileElement {
env.put(type.name(), getFile().getPath());
}
+ private static File getTempFile(final File file, final String midName,
+ final File workingDir) throws IOException {
+ String[] fileNameAndExtension = splitBaseFileNameAndExtension(file);
+ // TODO: avoid long random file name (number generated by
+ // createTempFile)
+ return File.createTempFile(
+ fileNameAndExtension[0] + "_" + midName + "_", //$NON-NLS-1$ //$NON-NLS-2$
+ fileNameAndExtension[1], workingDir);
+ }
+
+ private static void copyFromStream(final File file,
+ final InputStream stream)
+ throws IOException, FileNotFoundException {
+ try (OutputStream outStream = new FileOutputStream(file)) {
+ int read = 0;
+ byte[] bytes = new byte[8 * 1024];
+ while ((read = stream.read(bytes)) != -1) {
+ outStream.write(bytes, 0, read);
+ }
+ } finally {
+ // stream can only be consumed once --> close it and invalidate
+ stream.close();
+ }
+ }
+
+ private static String[] splitBaseFileNameAndExtension(File file) {
+ String[] result = new String[2];
+ result[0] = file.getName();
+ result[1] = ""; //$NON-NLS-1$
+ int idx = result[0].lastIndexOf("."); //$NON-NLS-1$
+ // if "." was found (>-1) and last-index is not first char (>0), then
+ // split (same behavior like cgit)
+ if (idx > 0) {
+ result[1] = result[0].substring(idx, result[0].length());
+ result[0] = result[0].substring(0, idx);
+ }
+ return result;
+ }
+
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/InformNoToolHandler.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/InformNoToolHandler.java
new file mode 100644
index 0000000000..36b290d37d
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/InformNoToolHandler.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2018-2019, Tim Neumann <Tim.Neumann@advantest.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 java.util.List;
+
+/**
+ * A handler for when the diff/merge tool manager wants to inform the user that
+ * no tool has been configured and one of the default tools will be used.
+ */
+public interface InformNoToolHandler {
+ /**
+ * Inform the user, that no tool is configured and that one of the given
+ * tools is used.
+ *
+ * @param toolNames
+ * The tools which are tried
+ */
+ void inform(List<String> toolNames);
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeToolConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeToolConfig.java
index 9be20b75ad..9625d5f101 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeToolConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeToolConfig.java
@@ -31,7 +31,7 @@ import org.eclipse.jgit.lib.Config.SectionParser;
import org.eclipse.jgit.lib.internal.BooleanTriState;
/**
- * Keeps track of difftool related configuration options.
+ * Keeps track of merge tool related configuration options.
*/
public class MergeToolConfig {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeTools.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeTools.java
index 9a2a8304eb..b903201264 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeTools.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeTools.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2018-2022, Andre Bossert <andre.bossert@siemens.com>
+ * Copyright (C) 2019, Tim Neumann <tim.neumann@advantest.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
@@ -15,13 +16,23 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashSet;
import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
+import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.diffmergetool.FileElement.Type;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.lib.internal.BooleanTriState;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.StringUtils;
import org.eclipse.jgit.util.FS.ExecutionResult;
/**
@@ -29,26 +40,135 @@ import org.eclipse.jgit.util.FS.ExecutionResult;
*/
public class MergeTools {
- Repository repo;
+ private final FS fs;
+
+ private final File gitDir;
+
+ private final File workTree;
private final MergeToolConfig config;
+ private final Repository repo;
+
private final Map<String, ExternalMergeTool> predefinedTools;
private final Map<String, ExternalMergeTool> userDefinedTools;
/**
+ * Creates the external merge-tools manager for given repository.
+ *
* @param repo
* the repository
*/
public MergeTools(Repository repo) {
+ this(repo, repo.getConfig());
+ }
+
+ /**
+ * Creates the external diff-tools manager for given configuration.
+ *
+ * @param config
+ * the git configuration
+ */
+ public MergeTools(StoredConfig config) {
+ this(null, config);
+ }
+
+ private MergeTools(Repository repo, StoredConfig config) {
this.repo = repo;
- config = repo.getConfig().get(MergeToolConfig.KEY);
+ this.config = config.get(MergeToolConfig.KEY);
+ this.gitDir = repo == null ? null : repo.getDirectory();
+ this.fs = repo == null ? FS.DETECTED : repo.getFS();
+ this.workTree = repo == null ? null : repo.getWorkTree();
predefinedTools = setupPredefinedTools();
- userDefinedTools = setupUserDefinedTools(config, predefinedTools);
+ userDefinedTools = setupUserDefinedTools(predefinedTools);
}
/**
+ * Merge two versions of a file with optional base file.
+ *
+ * @param localFile
+ * The local/left version of the file.
+ * @param remoteFile
+ * The remote/right version of the file.
+ * @param mergedFile
+ * The file for the result.
+ * @param baseFile
+ * The base version of the file. May be null.
+ * @param tempDir
+ * The tmepDir used for the files. May be null.
+ * @param toolName
+ * Optionally the name of the tool to use. If not given the
+ * default tool will be used.
+ * @param prompt
+ * Optionally a flag whether to prompt the user before compare.
+ * If not given the default will be used.
+ * @param gui
+ * A flag whether to prefer a gui tool.
+ * @param promptHandler
+ * The handler to use when needing to prompt the user if he wants
+ * to continue.
+ * @param noToolHandler
+ * The handler to use when needing to inform the user, that no
+ * tool is configured.
+ * @return the optional result of executing the tool if it was executed
+ * @throws ToolException
+ * when the tool fails
+ */
+ public Optional<ExecutionResult> merge(FileElement localFile,
+ FileElement remoteFile, FileElement mergedFile,
+ FileElement baseFile, File tempDir, Optional<String> toolName,
+ BooleanTriState prompt, boolean gui,
+ PromptContinueHandler promptHandler,
+ InformNoToolHandler noToolHandler) throws ToolException {
+
+ String toolNameToUse;
+
+ if (toolName == null) {
+ throw new ToolException(JGitText.get().diffToolNullError);
+ }
+
+ if (toolName.isPresent()) {
+ toolNameToUse = toolName.get();
+ } else {
+ toolNameToUse = getDefaultToolName(gui);
+
+ if (StringUtils.isEmptyOrNull(toolNameToUse)) {
+ noToolHandler.inform(new ArrayList<>(predefinedTools.keySet()));
+ toolNameToUse = getFirstAvailableTool();
+ }
+ }
+
+ if (StringUtils.isEmptyOrNull(toolNameToUse)) {
+ throw new ToolException(JGitText.get().diffToolNotGivenError);
+ }
+
+ boolean doPrompt;
+ if (prompt != BooleanTriState.UNSET) {
+ doPrompt = prompt == BooleanTriState.TRUE;
+ } else {
+ doPrompt = isInteractive();
+ }
+
+ if (doPrompt) {
+ if (!promptHandler.prompt(toolNameToUse)) {
+ return Optional.empty();
+ }
+ }
+
+ ExternalMergeTool tool = getTool(toolNameToUse);
+ if (tool == null) {
+ throw new ToolException(
+ "External merge tool is not defined: " + toolNameToUse); //$NON-NLS-1$
+ }
+
+ return Optional.of(merge(localFile, remoteFile, mergedFile, baseFile,
+ tempDir, tool));
+ }
+
+ /**
+ * Merge two versions of a file with optional base file.
+ *
* @param localFile
* the local file element
* @param remoteFile
@@ -60,38 +180,31 @@ public class MergeTools {
* @param tempDir
* the temporary directory (needed for backup and auto-remove,
* can be null)
- * @param toolName
- * the selected tool name (can be null)
- * @param prompt
- * the prompt option
- * @param gui
- * the GUI option
+ * @param tool
+ * the selected tool
* @return the execution result from tool
* @throws ToolException
*/
public ExecutionResult merge(FileElement localFile, FileElement remoteFile,
FileElement mergedFile, FileElement baseFile, File tempDir,
- String toolName, BooleanTriState prompt, BooleanTriState gui)
- throws ToolException {
- ExternalMergeTool tool = guessTool(toolName, gui);
+ ExternalMergeTool tool) throws ToolException {
FileElement backup = null;
ExecutionResult result = null;
try {
- File workingDir = repo.getWorkTree();
// create additional backup file (copy worktree file)
- backup = createBackupFile(mergedFile.getPath(),
- tempDir != null ? tempDir : workingDir);
+ backup = createBackupFile(mergedFile,
+ tempDir != null ? tempDir : workTree);
// prepare the command (replace the file paths)
- boolean trust = tool.getTrustExitCode() == BooleanTriState.TRUE;
String command = ExternalToolUtils.prepareCommand(
tool.getCommand(baseFile != null), localFile, remoteFile,
mergedFile, baseFile);
// prepare the environment
- Map<String, String> env = ExternalToolUtils.prepareEnvironment(repo,
- localFile, remoteFile, mergedFile, baseFile);
+ Map<String, String> env = ExternalToolUtils.prepareEnvironment(
+ gitDir, localFile, remoteFile, mergedFile, baseFile);
+ boolean trust = tool.getTrustExitCode() == BooleanTriState.TRUE;
// execute the tool
- CommandExecutor cmdExec = new CommandExecutor(repo.getFS(), trust);
- result = cmdExec.run(command, workingDir, env);
+ CommandExecutor cmdExec = new CommandExecutor(fs, trust);
+ result = cmdExec.run(command, workTree, env);
// keep backup as .orig file
if (backup != null) {
keepBackupFile(mergedFile.getPath(), backup);
@@ -123,19 +236,21 @@ public class MergeTools {
}
}
- private FileElement createBackupFile(String filePath, File parentDir)
+ private FileElement createBackupFile(FileElement from, File toParentDir)
throws IOException {
FileElement backup = null;
- Path path = Paths.get(filePath);
+ Path path = Paths.get(from.getPath());
if (Files.exists(path)) {
- backup = new FileElement(filePath, Type.BACKUP);
- Files.copy(path, backup.createTempFile(parentDir).toPath(),
+ backup = new FileElement(from.getPath(), Type.BACKUP);
+ Files.copy(path, backup.createTempFile(toParentDir).toPath(),
StandardCopyOption.REPLACE_EXISTING);
}
return backup;
}
/**
+ * Create temporary directory.
+ *
* @return the created temporary directory if (mergetol.writeToTemp == true)
* or null if not configured or false.
* @throws IOException
@@ -147,60 +262,138 @@ public class MergeTools {
}
/**
- * @return the tool names
+ * Get user defined tool names.
+ *
+ * @return the user defined tool names
*/
- public Set<String> getToolNames() {
- return config.getToolNames();
+ public Set<String> getUserDefinedToolNames() {
+ return userDefinedTools.keySet();
}
/**
- * @return the user defined tools
+ * @return the predefined tool names
*/
- public Map<String, ExternalMergeTool> getUserDefinedTools() {
- return userDefinedTools;
+ public Set<String> getPredefinedToolNames() {
+ return predefinedTools.keySet();
}
/**
- * @return the available predefined tools
+ * Get all tool names.
+ *
+ * @return the all tool names (default or available tool name is the first
+ * in the set)
*/
- public Map<String, ExternalMergeTool> getAvailableTools() {
- return predefinedTools;
+ public Set<String> getAllToolNames() {
+ String defaultName = getDefaultToolName(false);
+ if (defaultName == null) {
+ defaultName = getFirstAvailableTool();
+ }
+ return ExternalToolUtils.createSortedToolSet(defaultName,
+ getUserDefinedToolNames(), getPredefinedToolNames());
}
/**
- * @return the NOT available predefined tools
+ * Provides {@link Optional} with the name of an external merge tool if
+ * specified in git configuration for a path.
+ *
+ * The formed git configuration results from global rules as well as merged
+ * rules from info and worktree attributes.
+ *
+ * Triggers {@link TreeWalk} until specified path found in the tree.
+ *
+ * @param path
+ * path to the node in repository to parse git attributes for
+ * @return name of the difftool if set
+ * @throws ToolException
*/
- public Map<String, ExternalMergeTool> getNotAvailableTools() {
- return new TreeMap<>();
+ public Optional<String> getExternalToolFromAttributes(final String path)
+ throws ToolException {
+ return ExternalToolUtils.getExternalToolFromAttributes(repo, path,
+ ExternalToolUtils.KEY_MERGE_TOOL);
}
/**
- * @param gui
- * use the diff.guitool setting ?
- * @return the default tool name
+ * Checks the availability of the predefined tools in the system.
+ *
+ * @return set of predefined available tools
*/
- public String getDefaultToolName(BooleanTriState gui) {
- return gui != BooleanTriState.UNSET ? "my_gui_tool" //$NON-NLS-1$
- : config.getDefaultToolName();
+ public Set<String> getPredefinedAvailableTools() {
+ Map<String, ExternalMergeTool> defTools = getPredefinedTools(true);
+ Set<String> availableTools = new LinkedHashSet<>();
+ for (Entry<String, ExternalMergeTool> elem : defTools.entrySet()) {
+ if (elem.getValue().isAvailable()) {
+ availableTools.add(elem.getKey());
+ }
+ }
+ return availableTools;
}
/**
+ * @return the user defined tools
+ */
+ public Map<String, ExternalMergeTool> getUserDefinedTools() {
+ return Collections.unmodifiableMap(userDefinedTools);
+ }
+
+ /**
+ * Get predefined tools map.
+ *
+ * @param checkAvailability
+ * true: for checking if tools can be executed; ATTENTION: this
+ * check took some time, do not execute often (store the map for
+ * other actions); false: availability is NOT checked:
+ * isAvailable() returns default false is this case!
+ * @return the predefined tools with optionally checked availability (long
+ * running operation)
+ */
+ public Map<String, ExternalMergeTool> getPredefinedTools(
+ boolean checkAvailability) {
+ if (checkAvailability) {
+ for (ExternalMergeTool tool : predefinedTools.values()) {
+ PreDefinedMergeTool predefTool = (PreDefinedMergeTool) tool;
+ predefTool.setAvailable(ExternalToolUtils.isToolAvailable(fs,
+ gitDir, workTree, predefTool.getPath()));
+ }
+ }
+ return Collections.unmodifiableMap(predefinedTools);
+ }
+
+ /**
+ * Get first available tool name.
+ *
+ * @return the name of first available predefined tool or null
+ */
+ public String getFirstAvailableTool() {
+ String name = null;
+ for (ExternalMergeTool tool : predefinedTools.values()) {
+ if (ExternalToolUtils.isToolAvailable(fs, gitDir, workTree,
+ tool.getPath())) {
+ name = tool.getName();
+ break;
+ }
+ }
+ return name;
+ }
+
+ /**
+ * Is interactive merge (prompt enabled) ?
+ *
* @return is interactive (config prompt enabled) ?
*/
public boolean isInteractive() {
return config.isPrompt();
}
- private ExternalMergeTool guessTool(String toolName, BooleanTriState gui)
- 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;
+ /**
+ * Get the default (gui-)tool name.
+ *
+ * @param gui
+ * use the diff.guitool setting ?
+ * @return the default tool name
+ */
+ public String getDefaultToolName(boolean gui) {
+ return gui ? config.getDefaultGuiToolName()
+ : config.getDefaultToolName();
}
private ExternalMergeTool getTool(final String name) {
@@ -231,9 +424,9 @@ public class MergeTools {
}
private Map<String, ExternalMergeTool> setupUserDefinedTools(
- MergeToolConfig cfg, Map<String, ExternalMergeTool> predefTools) {
+ Map<String, ExternalMergeTool> predefTools) {
Map<String, ExternalMergeTool> tools = new TreeMap<>();
- Map<String, ExternalMergeTool> userTools = cfg.getTools();
+ Map<String, ExternalMergeTool> userTools = config.getTools();
for (String name : userTools.keySet()) {
ExternalMergeTool userTool = userTools.get(name);
// if mergetool.<name>.cmd is defined we have user defined tool
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedDiffTool.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedDiffTool.java
index 092cb605be..e1169a2d60 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedDiffTool.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedDiffTool.java
@@ -56,7 +56,7 @@ public class PreDefinedDiffTool extends UserDefinedDiffTool {
*/
@Override
public String getCommand() {
- return getPath() + " " + super.getCommand(); //$NON-NLS-1$
+ return ExternalToolUtils.quotePath(getPath()) + " " + super.getCommand(); //$NON-NLS-1$
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedMergeTool.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedMergeTool.java
index 2c64c16667..7b28d32820 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedMergeTool.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedMergeTool.java
@@ -84,7 +84,7 @@ public class PreDefinedMergeTool extends UserDefinedMergeTool {
*/
@Override
public String getCommand(boolean withBase) {
- return getPath() + " " //$NON-NLS-1$
+ return ExternalToolUtils.quotePath(getPath()) + " " //$NON-NLS-1$
+ (withBase ? super.getCommand() : parametersWithoutBase);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PromptContinueHandler.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PromptContinueHandler.java
new file mode 100644
index 0000000000..6ad33df2a0
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PromptContinueHandler.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2018-2019, Tim Neumann <Tim.Neumann@advantest.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;
+
+/**
+ * A handler for when the diff/merge tool manager wants to prompt the user
+ * whether to continue
+ */
+public interface PromptContinueHandler {
+ /**
+ * Prompt the user whether to continue with the next file by opening a given
+ * tool.
+ *
+ * @param toolName
+ * The name of the tool to open
+ * @return Whether the user wants to continue
+ */
+ boolean prompt(String toolName);
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ToolException.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ToolException.java
index 27f7d12e66..7cc5bb50d9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ToolException.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ToolException.java
@@ -110,6 +110,9 @@ public class ToolException extends Exception {
* @return the result Stderr
*/
public String getResultStderr() {
+ if (result == null) {
+ return ""; //$NON-NLS-1$
+ }
try {
return new String(result.getStderr().toByteArray());
} catch (Exception e) {
@@ -122,6 +125,9 @@ public class ToolException extends Exception {
* @return the result Stdout
*/
public String getResultStdout() {
+ if (result == null) {
+ return ""; //$NON-NLS-1$
+ }
try {
return new String(result.getStdout().toByteArray());
} catch (Exception e) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/UserDefinedDiffTool.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/UserDefinedDiffTool.java
index 012296eb35..eb72d01cdb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/UserDefinedDiffTool.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/UserDefinedDiffTool.java
@@ -15,6 +15,8 @@ package org.eclipse.jgit.internal.diffmergetool;
*/
public class UserDefinedDiffTool implements ExternalDiffTool {
+ private boolean available;
+
/**
* the diff tool name
*/
@@ -99,6 +101,23 @@ public class UserDefinedDiffTool implements ExternalDiffTool {
}
/**
+ * @return availability of the tool: true if tool can be executed and false
+ * if not
+ */
+ @Override
+ public boolean isAvailable() {
+ return available;
+ }
+
+ /**
+ * @param available
+ * true if tool can be found and false if not
+ */
+ public void setAvailable(boolean available) {
+ this.available = available;
+ }
+
+ /**
* Overrides the path for the given tool. Equivalent to setting
* {@code difftool.<tool>.path}.
*