ExternalToolUtils.java
/*
* Copyright (C) 2018-2021, Andre Bossert <andre.bossert@siemens.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.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.
*/
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
* the input "command" string
* @param localFile
* the local file (ours)
* @param remoteFile
* the remote file (theirs)
* @param mergedFile
* the merged file (worktree)
* @param baseFile
* the base file (can be null)
* @return the prepared (with replaced variables) command string
* @throws IOException
*/
public static String prepareCommand(String command, FileElement localFile,
FileElement remoteFile, FileElement mergedFile,
FileElement baseFile) throws IOException {
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);
}
return command;
}
/**
* Prepare environment needed for execution.
*
* @param gitDir
* the .git directory
* @param localFile
* the local file (ours)
* @param remoteFile
* the remote file (theirs)
* @param mergedFile
* the merged file (worktree)
* @param baseFile
* the base file (can be null)
* @return the environment map with variables and values (file paths)
* @throws IOException
*/
public static Map<String, String> prepareEnvironment(File gitDir,
FileElement localFile, FileElement remoteFile,
FileElement mergedFile, FileElement baseFile) throws IOException {
Map<String, String> env = new TreeMap<>();
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);
}
}
}