aboutsummaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalToolUtils.java
blob: e5947102ebab6345fa661026d7864d7408aba9ce (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
/*
 * 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
	 *             if an IO error occurred
	 */
	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
	 *             if an IO error occurred
	 */
	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;
	}

	/**
	 * Quote path
	 *
	 * @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;
	}

	/**
	 * Whether tool is available
	 *
	 * @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;
	}

	/**
	 * Create sorted tool set
	 *
	 * @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
	 *             if the tool failed
	 */
	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);
		}
	}

}