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