ExternalToolUtils.java

  1. /*
  2.  * Copyright (C) 2018-2021, Andre Bossert <andre.bossert@siemens.com>
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */
  10. package org.eclipse.jgit.internal.diffmergetool;

  11. import java.util.TreeMap;
  12. import java.io.File;
  13. import java.io.IOException;
  14. import java.util.LinkedHashSet;
  15. import java.util.Map;
  16. import java.util.Optional;
  17. import java.util.Set;

  18. import org.eclipse.jgit.attributes.Attributes;
  19. import org.eclipse.jgit.errors.RevisionSyntaxException;
  20. import org.eclipse.jgit.lib.Constants;
  21. import org.eclipse.jgit.lib.Repository;
  22. import org.eclipse.jgit.treewalk.FileTreeIterator;
  23. import org.eclipse.jgit.treewalk.TreeWalk;
  24. import org.eclipse.jgit.treewalk.WorkingTreeIterator;
  25. import org.eclipse.jgit.treewalk.filter.NotIgnoredFilter;
  26. import org.eclipse.jgit.util.FS;

  27. /**
  28.  * Utilities for diff- and merge-tools.
  29.  */
  30. public class ExternalToolUtils {

  31.     /**
  32.      * Key for merge tool git configuration section
  33.      */
  34.     public static final String KEY_MERGE_TOOL = "mergetool"; //$NON-NLS-1$

  35.     /**
  36.      * Key for diff tool git configuration section
  37.      */
  38.     public static final String KEY_DIFF_TOOL = "difftool"; //$NON-NLS-1$

  39.     /**
  40.      * Prepare command for execution.
  41.      *
  42.      * @param command
  43.      *            the input "command" string
  44.      * @param localFile
  45.      *            the local file (ours)
  46.      * @param remoteFile
  47.      *            the remote file (theirs)
  48.      * @param mergedFile
  49.      *            the merged file (worktree)
  50.      * @param baseFile
  51.      *            the base file (can be null)
  52.      * @return the prepared (with replaced variables) command string
  53.      * @throws IOException
  54.      */
  55.     public static String prepareCommand(String command, FileElement localFile,
  56.             FileElement remoteFile, FileElement mergedFile,
  57.             FileElement baseFile) throws IOException {
  58.         if (localFile != null) {
  59.             command = localFile.replaceVariable(command);
  60.         }
  61.         if (remoteFile != null) {
  62.             command = remoteFile.replaceVariable(command);
  63.         }
  64.         if (mergedFile != null) {
  65.             command = mergedFile.replaceVariable(command);
  66.         }
  67.         if (baseFile != null) {
  68.             command = baseFile.replaceVariable(command);
  69.         }
  70.         return command;
  71.     }

  72.     /**
  73.      * Prepare environment needed for execution.
  74.      *
  75.      * @param gitDir
  76.      *            the .git directory
  77.      * @param localFile
  78.      *            the local file (ours)
  79.      * @param remoteFile
  80.      *            the remote file (theirs)
  81.      * @param mergedFile
  82.      *            the merged file (worktree)
  83.      * @param baseFile
  84.      *            the base file (can be null)
  85.      * @return the environment map with variables and values (file paths)
  86.      * @throws IOException
  87.      */
  88.     public static Map<String, String> prepareEnvironment(File gitDir,
  89.             FileElement localFile, FileElement remoteFile,
  90.             FileElement mergedFile, FileElement baseFile) throws IOException {
  91.         Map<String, String> env = new TreeMap<>();
  92.         if (gitDir != null) {
  93.             env.put(Constants.GIT_DIR_KEY, gitDir.getAbsolutePath());
  94.         }
  95.         if (localFile != null) {
  96.             localFile.addToEnv(env);
  97.         }
  98.         if (remoteFile != null) {
  99.             remoteFile.addToEnv(env);
  100.         }
  101.         if (mergedFile != null) {
  102.             mergedFile.addToEnv(env);
  103.         }
  104.         if (baseFile != null) {
  105.             baseFile.addToEnv(env);
  106.         }
  107.         return env;
  108.     }

  109.     /**
  110.      * @param path
  111.      *            the path to be quoted
  112.      * @return quoted path if it contains spaces
  113.      */
  114.     @SuppressWarnings("nls")
  115.     public static String quotePath(String path) {
  116.         // handling of spaces in path
  117.         if (path.contains(" ")) {
  118.             // add quotes before if needed
  119.             if (!path.startsWith("\"")) {
  120.                 path = "\"" + path;
  121.             }
  122.             // add quotes after if needed
  123.             if (!path.endsWith("\"")) {
  124.                 path = path + "\"";
  125.             }
  126.         }
  127.         return path;
  128.     }

  129.     /**
  130.      * @param fs
  131.      *            the file system abstraction
  132.      * @param gitDir
  133.      *            the .git directory
  134.      * @param directory
  135.      *            the working directory
  136.      * @param path
  137.      *            the tool path
  138.      * @return true if tool available and false otherwise
  139.      */
  140.     public static boolean isToolAvailable(FS fs, File gitDir, File directory,
  141.             String path) {
  142.         boolean available = true;
  143.         try {
  144.             CommandExecutor cmdExec = new CommandExecutor(fs, false);
  145.             available = cmdExec.checkExecutable(path, directory,
  146.                     prepareEnvironment(gitDir, null, null, null, null));
  147.         } catch (Exception e) {
  148.             available = false;
  149.         }
  150.         return available;
  151.     }

  152.     /**
  153.      * @param defaultName
  154.      *            the default tool name
  155.      * @param userDefinedNames
  156.      *            the user defined tool names
  157.      * @param preDefinedNames
  158.      *            the pre defined tool names
  159.      * @return the sorted tool names set: first element is default tool name if
  160.      *         valid, then user defined tool names and then pre defined tool
  161.      *         names
  162.      */
  163.     public static Set<String> createSortedToolSet(String defaultName,
  164.             Set<String> userDefinedNames, Set<String> preDefinedNames) {
  165.         Set<String> names = new LinkedHashSet<>();
  166.         if (defaultName != null) {
  167.             // remove defaultName from both sets
  168.             Set<String> namesPredef = new LinkedHashSet<>();
  169.             Set<String> namesUser = new LinkedHashSet<>();
  170.             namesUser.addAll(userDefinedNames);
  171.             namesUser.remove(defaultName);
  172.             namesPredef.addAll(preDefinedNames);
  173.             namesPredef.remove(defaultName);
  174.             // add defaultName as first in set
  175.             names.add(defaultName);
  176.             names.addAll(namesUser);
  177.             names.addAll(namesPredef);
  178.         } else {
  179.             names.addAll(userDefinedNames);
  180.             names.addAll(preDefinedNames);
  181.         }
  182.         return names;
  183.     }

  184.     /**
  185.      * Provides {@link Optional} with the name of an external tool if specified
  186.      * in git configuration for a path.
  187.      *
  188.      * The formed git configuration results from global rules as well as merged
  189.      * rules from info and worktree attributes.
  190.      *
  191.      * Triggers {@link TreeWalk} until specified path found in the tree.
  192.      *
  193.      * @param repository
  194.      *            target repository to traverse into
  195.      * @param path
  196.      *            path to the node in repository to parse git attributes for
  197.      * @param toolKey
  198.      *            config key name for the tool
  199.      * @return attribute value for the given tool key if set
  200.      * @throws ToolException
  201.      */
  202.     public static Optional<String> getExternalToolFromAttributes(
  203.             final Repository repository, final String path,
  204.             final String toolKey) throws ToolException {
  205.         try {
  206.             WorkingTreeIterator treeIterator = new FileTreeIterator(repository);
  207.             try (TreeWalk walk = new TreeWalk(repository)) {
  208.                 walk.addTree(treeIterator);
  209.                 walk.setFilter(new NotIgnoredFilter(0));
  210.                 while (walk.next()) {
  211.                     String treePath = walk.getPathString();
  212.                     if (treePath.equals(path)) {
  213.                         Attributes attrs = walk.getAttributes();
  214.                         if (attrs.containsKey(toolKey)) {
  215.                             return Optional.of(attrs.getValue(toolKey));
  216.                         }
  217.                     }
  218.                     if (walk.isSubtree()) {
  219.                         walk.enterSubtree();
  220.                     }
  221.                 }
  222.                 // no external tool specified
  223.                 return Optional.empty();
  224.             }

  225.         } catch (RevisionSyntaxException | IOException e) {
  226.             throw new ToolException(e);
  227.         }
  228.     }

  229. }