1
2
3
4
5
6
7
8
9
10 package org.eclipse.jgit.attributes;
11
12 import java.io.IOException;
13 import java.util.HashMap;
14 import java.util.List;
15 import java.util.ListIterator;
16 import java.util.Map;
17 import java.util.function.Supplier;
18
19 import org.eclipse.jgit.annotations.Nullable;
20 import org.eclipse.jgit.attributes.Attribute.State;
21 import org.eclipse.jgit.dircache.DirCacheIterator;
22 import org.eclipse.jgit.lib.FileMode;
23 import org.eclipse.jgit.treewalk.AbstractTreeIterator;
24 import org.eclipse.jgit.treewalk.CanonicalTreeParser;
25 import org.eclipse.jgit.treewalk.TreeWalk;
26 import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
27 import org.eclipse.jgit.treewalk.WorkingTreeIterator;
28
29
30
31
32
33
34
35
36
37
38
39
40
41 public class AttributesHandler {
42 private static final String MACRO_PREFIX = "[attr]";
43
44 private static final String BINARY_RULE_KEY = "binary";
45
46
47
48
49
50 private static final List<Attribute> BINARY_RULE_ATTRIBUTES = new AttributesRule(
51 MACRO_PREFIX + BINARY_RULE_KEY, "-diff -merge -text")
52 .getAttributes();
53
54 private final TreeWalk treeWalk;
55
56 private final Supplier<CanonicalTreeParser> attributesTree;
57
58 private final AttributesNode globalNode;
59
60 private final AttributesNode infoNode;
61
62 private final Map<String, List<Attribute>> expansions = new HashMap<>();
63
64
65
66
67
68
69
70
71
72
73
74
75 @Deprecated
76 public AttributesHandler(TreeWalk treeWalk) throws IOException {
77 this(treeWalk, () -> treeWalk.getTree(CanonicalTreeParser.class));
78 }
79
80
81
82
83
84
85
86
87
88
89
90
91
92 public AttributesHandler(TreeWalk treeWalk,
93 Supplier<CanonicalTreeParser> attributesTree) throws IOException {
94 this.treeWalk = treeWalk;
95 this.attributesTree = attributesTree;
96 AttributesNodeProvider attributesNodeProvider = treeWalk
97 .getAttributesNodeProvider();
98 this.globalNode = attributesNodeProvider != null
99 ? attributesNodeProvider.getGlobalAttributesNode() : null;
100 this.infoNode = attributesNodeProvider != null
101 ? attributesNodeProvider.getInfoAttributesNode() : null;
102
103 AttributesNode rootNode = attributesNode(treeWalk,
104 rootOf(treeWalk.getTree(WorkingTreeIterator.class)),
105 rootOf(treeWalk.getTree(DirCacheIterator.class)),
106 rootOf(attributesTree.get()));
107
108 expansions.put(BINARY_RULE_KEY, BINARY_RULE_ATTRIBUTES);
109 for (AttributesNode node : new AttributesNode[] { globalNode, rootNode,
110 infoNode }) {
111 if (node == null) {
112 continue;
113 }
114 for (AttributesRule rule : node.getRules()) {
115 if (rule.getPattern().startsWith(MACRO_PREFIX)) {
116 expansions.put(rule.getPattern()
117 .substring(MACRO_PREFIX.length()).trim(),
118 rule.getAttributes());
119 }
120 }
121 }
122 }
123
124
125
126
127
128
129
130
131
132 public Attributes getAttributes() throws IOException {
133 String entryPath = treeWalk.getPathString();
134 boolean isDirectory = (treeWalk.getFileMode() == FileMode.TREE);
135 Attributes attributes = new Attributes();
136
137
138 mergeInfoAttributes(entryPath, isDirectory, attributes);
139
140
141 mergePerDirectoryEntryAttributes(entryPath, entryPath.lastIndexOf('/'),
142 isDirectory,
143 treeWalk.getTree(WorkingTreeIterator.class),
144 treeWalk.getTree(DirCacheIterator.class),
145 attributesTree.get(),
146 attributes);
147
148
149 mergeGlobalAttributes(entryPath, isDirectory, attributes);
150
151
152
153 for (Attribute a : attributes.getAll()) {
154 if (a.getState() == State.UNSPECIFIED)
155 attributes.remove(a.getKey());
156 }
157
158 return attributes;
159 }
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174 private void mergeGlobalAttributes(String entryPath, boolean isDirectory,
175 Attributes result) {
176 mergeAttributes(globalNode, entryPath, isDirectory, result);
177 }
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192 private void mergeInfoAttributes(String entryPath, boolean isDirectory,
193 Attributes result) {
194 mergeAttributes(infoNode, entryPath, isDirectory, result);
195 }
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216 private void mergePerDirectoryEntryAttributes(String entryPath,
217 int nameRoot, boolean isDirectory,
218 @Nullable WorkingTreeIterator workingTreeIterator,
219 @Nullable DirCacheIterator dirCacheIterator,
220 @Nullable CanonicalTreeParser otherTree, Attributes result)
221 throws IOException {
222
223 if (workingTreeIterator != null || dirCacheIterator != null
224 || otherTree != null) {
225 AttributesNode attributesNode = attributesNode(
226 treeWalk, workingTreeIterator, dirCacheIterator, otherTree);
227 if (attributesNode != null) {
228 mergeAttributes(attributesNode,
229 entryPath.substring(nameRoot + 1), isDirectory,
230 result);
231 }
232 mergePerDirectoryEntryAttributes(entryPath,
233 entryPath.lastIndexOf('/', nameRoot - 1), isDirectory,
234 parentOf(workingTreeIterator), parentOf(dirCacheIterator),
235 parentOf(otherTree), result);
236 }
237 }
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254 protected void mergeAttributes(@Nullable AttributesNode node,
255 String entryPath,
256 boolean isDirectory, Attributes result) {
257 if (node == null)
258 return;
259 List<AttributesRule> rules = node.getRules();
260
261
262 ListIterator<AttributesRule> ruleIterator = rules
263 .listIterator(rules.size());
264 while (ruleIterator.hasPrevious()) {
265 AttributesRule rule = ruleIterator.previous();
266 if (rule.isMatch(entryPath, isDirectory)) {
267 ListIterator<Attribute> attributeIte = rule.getAttributes()
268 .listIterator(rule.getAttributes().size());
269
270
271 while (attributeIte.hasPrevious()) {
272 expandMacro(attributeIte.previous(), result);
273 }
274 }
275 }
276 }
277
278
279
280
281
282
283
284
285
286
287 protected void expandMacro(Attribute attr, Attributes result) {
288
289 if (result.containsKey(attr.getKey()))
290 return;
291
292
293 result.put(attr);
294
295 List<Attribute> expansion = expansions.get(attr.getKey());
296 if (expansion == null) {
297 return;
298 }
299 switch (attr.getState()) {
300 case UNSET: {
301 for (Attribute e : expansion) {
302 switch (e.getState()) {
303 case SET:
304 expandMacro(new Attribute(e.getKey(), State.UNSET), result);
305 break;
306 case UNSET:
307 expandMacro(new Attribute(e.getKey(), State.SET), result);
308 break;
309 case UNSPECIFIED:
310 expandMacro(new Attribute(e.getKey(), State.UNSPECIFIED),
311 result);
312 break;
313 case CUSTOM:
314 default:
315 expandMacro(e, result);
316 }
317 }
318 break;
319 }
320 case CUSTOM: {
321 for (Attribute e : expansion) {
322 switch (e.getState()) {
323 case SET:
324 case UNSET:
325 case UNSPECIFIED:
326 expandMacro(e, result);
327 break;
328 case CUSTOM:
329 default:
330 expandMacro(new Attribute(e.getKey(), attr.getValue()),
331 result);
332 }
333 }
334 break;
335 }
336 case UNSPECIFIED: {
337 for (Attribute e : expansion) {
338 expandMacro(new Attribute(e.getKey(), State.UNSPECIFIED),
339 result);
340 }
341 break;
342 }
343 case SET:
344 default:
345 for (Attribute e : expansion) {
346 expandMacro(e, result);
347 }
348 break;
349 }
350 }
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369 private static AttributesNode attributesNode(TreeWalk treeWalk,
370 @Nullable WorkingTreeIterator workingTreeIterator,
371 @Nullable DirCacheIterator dirCacheIterator,
372 @Nullable CanonicalTreeParser otherTree) throws IOException {
373 AttributesNode attributesNode = null;
374 switch (treeWalk.getOperationType()) {
375 case CHECKIN_OP:
376 if (workingTreeIterator != null) {
377 attributesNode = workingTreeIterator.getEntryAttributesNode();
378 }
379 if (attributesNode == null && dirCacheIterator != null) {
380 attributesNode = dirCacheIterator
381 .getEntryAttributesNode(treeWalk.getObjectReader());
382 }
383 if (attributesNode == null && otherTree != null) {
384 attributesNode = otherTree
385 .getEntryAttributesNode(treeWalk.getObjectReader());
386 }
387 break;
388 case CHECKOUT_OP:
389 if (otherTree != null) {
390 attributesNode = otherTree
391 .getEntryAttributesNode(treeWalk.getObjectReader());
392 }
393 if (attributesNode == null && dirCacheIterator != null) {
394 attributesNode = dirCacheIterator
395 .getEntryAttributesNode(treeWalk.getObjectReader());
396 }
397 if (attributesNode == null && workingTreeIterator != null) {
398 attributesNode = workingTreeIterator.getEntryAttributesNode();
399 }
400 break;
401 default:
402 throw new IllegalStateException(
403 "The only supported operation types are:"
404 + OperationType.CHECKIN_OP + ","
405 + OperationType.CHECKOUT_OP);
406 }
407
408 return attributesNode;
409 }
410
411 private static <T extends AbstractTreeIterator> T parentOf(@Nullable T node) {
412 if(node==null) return null;
413 @SuppressWarnings("unchecked")
414 Class<T> type = (Class<T>) node.getClass();
415 AbstractTreeIterator parent = node.parent;
416 if (type.isInstance(parent)) {
417 return type.cast(parent);
418 }
419 return null;
420 }
421
422 private static <T extends AbstractTreeIterator> T rootOf(
423 @Nullable T node) {
424 if(node==null) return null;
425 AbstractTreeIterator t=node;
426 while (t!= null && t.parent != null) {
427 t= t.parent;
428 }
429 @SuppressWarnings("unchecked")
430 Class<T> type = (Class<T>) node.getClass();
431 if (type.isInstance(t)) {
432 return type.cast(t);
433 }
434 return null;
435 }
436
437 }