View Javadoc
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  
12  import java.util.TreeMap;
13  import java.io.File;
14  import java.io.IOException;
15  import java.util.LinkedHashSet;
16  import java.util.Map;
17  import java.util.Optional;
18  import java.util.Set;
19  
20  import org.eclipse.jgit.attributes.Attributes;
21  import org.eclipse.jgit.errors.RevisionSyntaxException;
22  import org.eclipse.jgit.lib.Constants;
23  import org.eclipse.jgit.lib.Repository;
24  import org.eclipse.jgit.treewalk.FileTreeIterator;
25  import org.eclipse.jgit.treewalk.TreeWalk;
26  import org.eclipse.jgit.treewalk.WorkingTreeIterator;
27  import org.eclipse.jgit.treewalk.filter.NotIgnoredFilter;
28  import org.eclipse.jgit.util.FS;
29  
30  /**
31   * Utilities for diff- and merge-tools.
32   */
33  public class ExternalToolUtils {
34  
35  	/**
36  	 * Key for merge tool git configuration section
37  	 */
38  	public static final String KEY_MERGE_TOOL = "mergetool"; //$NON-NLS-1$
39  
40  	/**
41  	 * Key for diff tool git configuration section
42  	 */
43  	public static final String KEY_DIFF_TOOL = "difftool"; //$NON-NLS-1$
44  
45  	/**
46  	 * Prepare command for execution.
47  	 *
48  	 * @param command
49  	 *            the input "command" string
50  	 * @param localFile
51  	 *            the local file (ours)
52  	 * @param remoteFile
53  	 *            the remote file (theirs)
54  	 * @param mergedFile
55  	 *            the merged file (worktree)
56  	 * @param baseFile
57  	 *            the base file (can be null)
58  	 * @return the prepared (with replaced variables) command string
59  	 * @throws IOException
60  	 */
61  	public static String prepareCommand(String command, FileElement localFile,
62  			FileElement remoteFile, FileElement mergedFile,
63  			FileElement baseFile) throws IOException {
64  		if (localFile != null) {
65  			command = localFile.replaceVariable(command);
66  		}
67  		if (remoteFile != null) {
68  			command = remoteFile.replaceVariable(command);
69  		}
70  		if (mergedFile != null) {
71  			command = mergedFile.replaceVariable(command);
72  		}
73  		if (baseFile != null) {
74  			command = baseFile.replaceVariable(command);
75  		}
76  		return command;
77  	}
78  
79  	/**
80  	 * Prepare environment needed for execution.
81  	 *
82  	 * @param gitDir
83  	 *            the .git directory
84  	 * @param localFile
85  	 *            the local file (ours)
86  	 * @param remoteFile
87  	 *            the remote file (theirs)
88  	 * @param mergedFile
89  	 *            the merged file (worktree)
90  	 * @param baseFile
91  	 *            the base file (can be null)
92  	 * @return the environment map with variables and values (file paths)
93  	 * @throws IOException
94  	 */
95  	public static Map<String, String> prepareEnvironment(File gitDir,
96  			FileElement localFile, FileElement remoteFile,
97  			FileElement mergedFile, FileElement baseFile) throws IOException {
98  		Map<String, String> env = new TreeMap<>();
99  		if (gitDir != null) {
100 			env.put(Constants.GIT_DIR_KEY, gitDir.getAbsolutePath());
101 		}
102 		if (localFile != null) {
103 			localFile.addToEnv(env);
104 		}
105 		if (remoteFile != null) {
106 			remoteFile.addToEnv(env);
107 		}
108 		if (mergedFile != null) {
109 			mergedFile.addToEnv(env);
110 		}
111 		if (baseFile != null) {
112 			baseFile.addToEnv(env);
113 		}
114 		return env;
115 	}
116 
117 	/**
118 	 * @param path
119 	 *            the path to be quoted
120 	 * @return quoted path if it contains spaces
121 	 */
122 	@SuppressWarnings("nls")
123 	public static String quotePath(String path) {
124 		// handling of spaces in path
125 		if (path.contains(" ")) {
126 			// add quotes before if needed
127 			if (!path.startsWith("\"")) {
128 				path = "\"" + path;
129 			}
130 			// add quotes after if needed
131 			if (!path.endsWith("\"")) {
132 				path = path + "\"";
133 			}
134 		}
135 		return path;
136 	}
137 
138 	/**
139 	 * @param fs
140 	 *            the file system abstraction
141 	 * @param gitDir
142 	 *            the .git directory
143 	 * @param directory
144 	 *            the working directory
145 	 * @param path
146 	 *            the tool path
147 	 * @return true if tool available and false otherwise
148 	 */
149 	public static boolean isToolAvailable(FS fs, File gitDir, File directory,
150 			String path) {
151 		boolean available = true;
152 		try {
153 			CommandExecutor cmdExec = new CommandExecutor(fs, false);
154 			available = cmdExec.checkExecutable(path, directory,
155 					prepareEnvironment(gitDir, null, null, null, null));
156 		} catch (Exception e) {
157 			available = false;
158 		}
159 		return available;
160 	}
161 
162 	/**
163 	 * @param defaultName
164 	 *            the default tool name
165 	 * @param userDefinedNames
166 	 *            the user defined tool names
167 	 * @param preDefinedNames
168 	 *            the pre defined tool names
169 	 * @return the sorted tool names set: first element is default tool name if
170 	 *         valid, then user defined tool names and then pre defined tool
171 	 *         names
172 	 */
173 	public static Set<String> createSortedToolSet(String defaultName,
174 			Set<String> userDefinedNames, Set<String> preDefinedNames) {
175 		Set<String> names = new LinkedHashSet<>();
176 		if (defaultName != null) {
177 			// remove defaultName from both sets
178 			Set<String> namesPredef = new LinkedHashSet<>();
179 			Set<String> namesUser = new LinkedHashSet<>();
180 			namesUser.addAll(userDefinedNames);
181 			namesUser.remove(defaultName);
182 			namesPredef.addAll(preDefinedNames);
183 			namesPredef.remove(defaultName);
184 			// add defaultName as first in set
185 			names.add(defaultName);
186 			names.addAll(namesUser);
187 			names.addAll(namesPredef);
188 		} else {
189 			names.addAll(userDefinedNames);
190 			names.addAll(preDefinedNames);
191 		}
192 		return names;
193 	}
194 
195 	/**
196 	 * Provides {@link Optional} with the name of an external tool if specified
197 	 * in git configuration for a path.
198 	 *
199 	 * The formed git configuration results from global rules as well as merged
200 	 * rules from info and worktree attributes.
201 	 *
202 	 * Triggers {@link TreeWalk} until specified path found in the tree.
203 	 *
204 	 * @param repository
205 	 *            target repository to traverse into
206 	 * @param path
207 	 *            path to the node in repository to parse git attributes for
208 	 * @param toolKey
209 	 *            config key name for the tool
210 	 * @return attribute value for the given tool key if set
211 	 * @throws ToolException
212 	 */
213 	public static Optional<String> getExternalToolFromAttributes(
214 			final Repository repository, final String path,
215 			final String toolKey) throws ToolException {
216 		try {
217 			WorkingTreeIterator treeIterator = new FileTreeIterator(repository);
218 			try (TreeWalk walk = new TreeWalk(repository)) {
219 				walk.addTree(treeIterator);
220 				walk.setFilter(new NotIgnoredFilter(0));
221 				while (walk.next()) {
222 					String treePath = walk.getPathString();
223 					if (treePath.equals(path)) {
224 						Attributes attrs = walk.getAttributes();
225 						if (attrs.containsKey(toolKey)) {
226 							return Optional.of(attrs.getValue(toolKey));
227 						}
228 					}
229 					if (walk.isSubtree()) {
230 						walk.enterSubtree();
231 					}
232 				}
233 				// no external tool specified
234 				return Optional.empty();
235 			}
236 
237 		} catch (RevisionSyntaxException | IOException e) {
238 			throw new ToolException(e);
239 		}
240 	}
241 
242 }