You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AttributesHandler.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. /*
  2. * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com>
  3. *
  4. * This program and the accompanying materials are made available
  5. * under the terms of the Eclipse Distribution License v1.0 which
  6. * accompanies this distribution, is reproduced below, and is
  7. * available at http://www.eclipse.org/org/documents/edl-v10.php
  8. *
  9. * All rights reserved.
  10. *
  11. * Redistribution and use in source and binary forms, with or
  12. * without modification, are permitted provided that the following
  13. * conditions are met:
  14. *
  15. * - Redistributions of source code must retain the above copyright
  16. * notice, this list of conditions and the following disclaimer.
  17. *
  18. * - Redistributions in binary form must reproduce the above
  19. * copyright notice, this list of conditions and the following
  20. * disclaimer in the documentation and/or other materials provided
  21. * with the distribution.
  22. *
  23. * - Neither the name of the Eclipse Foundation, Inc. nor the
  24. * names of its contributors may be used to endorse or promote
  25. * products derived from this software without specific prior
  26. * written permission.
  27. *
  28. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  29. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  30. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  31. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  32. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  33. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  34. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  35. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  36. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  37. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  38. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  39. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  40. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  41. */
  42. package org.eclipse.jgit.attributes;
  43. import java.io.IOException;
  44. import java.util.HashMap;
  45. import java.util.List;
  46. import java.util.ListIterator;
  47. import java.util.Map;
  48. import org.eclipse.jgit.annotations.Nullable;
  49. import org.eclipse.jgit.attributes.Attribute.State;
  50. import org.eclipse.jgit.dircache.DirCacheIterator;
  51. import org.eclipse.jgit.lib.FileMode;
  52. import org.eclipse.jgit.treewalk.AbstractTreeIterator;
  53. import org.eclipse.jgit.treewalk.CanonicalTreeParser;
  54. import org.eclipse.jgit.treewalk.TreeWalk;
  55. import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
  56. import org.eclipse.jgit.treewalk.WorkingTreeIterator;
  57. /**
  58. * The attributes handler knows how to retrieve, parse and merge attributes from
  59. * the various gitattributes files. Furthermore it collects and expands macro
  60. * expressions. The method {@link #getAttributes()} yields the ready processed
  61. * attributes for the current path represented by the {@link TreeWalk}
  62. * <p>
  63. * The implementation is based on the specifications in
  64. * http://git-scm.com/docs/gitattributes
  65. *
  66. * @since 4.3
  67. */
  68. public class AttributesHandler {
  69. private static final String MACRO_PREFIX = "[attr]"; //$NON-NLS-1$
  70. private static final String BINARY_RULE_KEY = "binary"; //$NON-NLS-1$
  71. /**
  72. * This is the default <b>binary</b> rule that is present in any git folder
  73. * <code>[attr]binary -diff -merge -text</code>
  74. */
  75. private static final List<Attribute> BINARY_RULE_ATTRIBUTES = new AttributesRule(
  76. MACRO_PREFIX + BINARY_RULE_KEY, "-diff -merge -text") //$NON-NLS-1$
  77. .getAttributes();
  78. private final TreeWalk treeWalk;
  79. private final AttributesNode globalNode;
  80. private final AttributesNode infoNode;
  81. private final Map<String, List<Attribute>> expansions = new HashMap<>();
  82. /**
  83. * Create an {@link AttributesHandler} with default rules as well as merged
  84. * rules from global, info and worktree root attributes
  85. *
  86. * @param treeWalk
  87. * @throws IOException
  88. */
  89. public AttributesHandler(TreeWalk treeWalk) throws IOException {
  90. this.treeWalk = treeWalk;
  91. AttributesNodeProvider attributesNodeProvider =treeWalk.getAttributesNodeProvider();
  92. this.globalNode = attributesNodeProvider != null
  93. ? attributesNodeProvider.getGlobalAttributesNode() : null;
  94. this.infoNode = attributesNodeProvider != null
  95. ? attributesNodeProvider.getInfoAttributesNode() : null;
  96. AttributesNode rootNode = attributesNode(treeWalk,
  97. rootOf(
  98. treeWalk.getTree(WorkingTreeIterator.class)),
  99. rootOf(
  100. treeWalk.getTree(DirCacheIterator.class)),
  101. rootOf(treeWalk
  102. .getTree(CanonicalTreeParser.class)));
  103. expansions.put(BINARY_RULE_KEY, BINARY_RULE_ATTRIBUTES);
  104. for (AttributesNode node : new AttributesNode[] { globalNode, rootNode,
  105. infoNode }) {
  106. if (node == null) {
  107. continue;
  108. }
  109. for (AttributesRule rule : node.getRules()) {
  110. if (rule.getPattern().startsWith(MACRO_PREFIX)) {
  111. expansions.put(rule.getPattern()
  112. .substring(MACRO_PREFIX.length()).trim(),
  113. rule.getAttributes());
  114. }
  115. }
  116. }
  117. }
  118. /**
  119. * see {@link TreeWalk#getAttributes()}
  120. *
  121. * @return the {@link Attributes} for the current path represented by the
  122. * {@link TreeWalk}
  123. * @throws IOException
  124. */
  125. public Attributes getAttributes() throws IOException {
  126. String entryPath = treeWalk.getPathString();
  127. boolean isDirectory = (treeWalk.getFileMode() == FileMode.TREE);
  128. Attributes attributes = new Attributes();
  129. // Gets the info attributes
  130. mergeInfoAttributes(entryPath, isDirectory, attributes);
  131. // Gets the attributes located on the current entry path
  132. mergePerDirectoryEntryAttributes(entryPath, isDirectory,
  133. treeWalk.getTree(WorkingTreeIterator.class),
  134. treeWalk.getTree(DirCacheIterator.class),
  135. treeWalk.getTree(CanonicalTreeParser.class),
  136. attributes);
  137. // Gets the attributes located in the global attribute file
  138. mergeGlobalAttributes(entryPath, isDirectory, attributes);
  139. // now after all attributes are collected - in the correct hierarchy
  140. // order - remove all unspecified entries (the ! marker)
  141. for (Attribute a : attributes.getAll()) {
  142. if (a.getState() == State.UNSPECIFIED)
  143. attributes.remove(a.getKey());
  144. }
  145. return attributes;
  146. }
  147. /**
  148. * Merges the matching GLOBAL attributes for an entry path.
  149. *
  150. * @param entryPath
  151. * the path to test. The path must be relative to this attribute
  152. * node's own repository path, and in repository path format
  153. * (uses '/' and not '\').
  154. * @param isDirectory
  155. * true if the target item is a directory.
  156. * @param result
  157. * that will hold the attributes matching this entry path. This
  158. * method will NOT override any existing entry in attributes.
  159. */
  160. private void mergeGlobalAttributes(String entryPath, boolean isDirectory,
  161. Attributes result) {
  162. mergeAttributes(globalNode, entryPath, isDirectory, result);
  163. }
  164. /**
  165. * Merges the matching INFO attributes for an entry path.
  166. *
  167. * @param entryPath
  168. * the path to test. The path must be relative to this attribute
  169. * node's own repository path, and in repository path format
  170. * (uses '/' and not '\').
  171. * @param isDirectory
  172. * true if the target item is a directory.
  173. * @param result
  174. * that will hold the attributes matching this entry path. This
  175. * method will NOT override any existing entry in attributes.
  176. */
  177. private void mergeInfoAttributes(String entryPath, boolean isDirectory,
  178. Attributes result) {
  179. mergeAttributes(infoNode, entryPath, isDirectory, result);
  180. }
  181. /**
  182. * Merges the matching working directory attributes for an entry path.
  183. *
  184. * @param entryPath
  185. * the path to test. The path must be relative to this attribute
  186. * node's own repository path, and in repository path format
  187. * (uses '/' and not '\').
  188. * @param isDirectory
  189. * true if the target item is a directory.
  190. * @param workingTreeIterator
  191. * @param dirCacheIterator
  192. * @param otherTree
  193. * @param result
  194. * that will hold the attributes matching this entry path. This
  195. * method will NOT override any existing entry in attributes.
  196. * @throws IOException
  197. */
  198. private void mergePerDirectoryEntryAttributes(String entryPath,
  199. boolean isDirectory,
  200. @Nullable WorkingTreeIterator workingTreeIterator,
  201. @Nullable DirCacheIterator dirCacheIterator,
  202. @Nullable CanonicalTreeParser otherTree, Attributes result)
  203. throws IOException {
  204. // Prevents infinite recurrence
  205. if (workingTreeIterator != null || dirCacheIterator != null
  206. || otherTree != null) {
  207. AttributesNode attributesNode = attributesNode(
  208. treeWalk, workingTreeIterator, dirCacheIterator, otherTree);
  209. if (attributesNode != null) {
  210. mergeAttributes(attributesNode, entryPath, isDirectory, result);
  211. }
  212. mergePerDirectoryEntryAttributes(entryPath, isDirectory,
  213. parentOf(workingTreeIterator), parentOf(dirCacheIterator),
  214. parentOf(otherTree), result);
  215. }
  216. }
  217. /**
  218. * Merges the matching node attributes for an entry path.
  219. *
  220. * @param node
  221. * the node to scan for matches to entryPath
  222. * @param entryPath
  223. * the path to test. The path must be relative to this attribute
  224. * node's own repository path, and in repository path format
  225. * (uses '/' and not '\').
  226. * @param isDirectory
  227. * true if the target item is a directory.
  228. * @param result
  229. * that will hold the attributes matching this entry path. This
  230. * method will NOT override any existing entry in attributes.
  231. */
  232. protected void mergeAttributes(@Nullable AttributesNode node,
  233. String entryPath,
  234. boolean isDirectory, Attributes result) {
  235. if (node == null)
  236. return;
  237. List<AttributesRule> rules = node.getRules();
  238. // Parse rules in the reverse order that they were read since the last
  239. // entry should be used
  240. ListIterator<AttributesRule> ruleIterator = rules
  241. .listIterator(rules.size());
  242. while (ruleIterator.hasPrevious()) {
  243. AttributesRule rule = ruleIterator.previous();
  244. if (rule.isMatch(entryPath, isDirectory)) {
  245. ListIterator<Attribute> attributeIte = rule.getAttributes()
  246. .listIterator(rule.getAttributes().size());
  247. // Parses the attributes in the reverse order that they were
  248. // read since the last entry should be used
  249. while (attributeIte.hasPrevious()) {
  250. expandMacro(attributeIte.previous(), result);
  251. }
  252. }
  253. }
  254. }
  255. /**
  256. * @param attr
  257. * @param result
  258. * contains the (recursive) expanded and merged macro attributes
  259. * including the attribute iself
  260. */
  261. protected void expandMacro(Attribute attr, Attributes result) {
  262. // loop detection = exists check
  263. if (result.containsKey(attr.getKey()))
  264. return;
  265. // also add macro to result set, same does native git
  266. result.put(attr);
  267. List<Attribute> expansion = expansions.get(attr.getKey());
  268. if (expansion == null) {
  269. return;
  270. }
  271. switch (attr.getState()) {
  272. case UNSET: {
  273. for (Attribute e : expansion) {
  274. switch (e.getState()) {
  275. case SET:
  276. expandMacro(new Attribute(e.getKey(), State.UNSET), result);
  277. break;
  278. case UNSET:
  279. expandMacro(new Attribute(e.getKey(), State.SET), result);
  280. break;
  281. case UNSPECIFIED:
  282. expandMacro(new Attribute(e.getKey(), State.UNSPECIFIED),
  283. result);
  284. break;
  285. case CUSTOM:
  286. default:
  287. expandMacro(e, result);
  288. }
  289. }
  290. break;
  291. }
  292. case CUSTOM: {
  293. for (Attribute e : expansion) {
  294. switch (e.getState()) {
  295. case SET:
  296. case UNSET:
  297. case UNSPECIFIED:
  298. expandMacro(e, result);
  299. break;
  300. case CUSTOM:
  301. default:
  302. expandMacro(new Attribute(e.getKey(), attr.getValue()),
  303. result);
  304. }
  305. }
  306. break;
  307. }
  308. case UNSPECIFIED: {
  309. for (Attribute e : expansion) {
  310. expandMacro(new Attribute(e.getKey(), State.UNSPECIFIED),
  311. result);
  312. }
  313. break;
  314. }
  315. case SET:
  316. default:
  317. for (Attribute e : expansion) {
  318. expandMacro(e, result);
  319. }
  320. break;
  321. }
  322. }
  323. /**
  324. * Get the {@link AttributesNode} for the current entry.
  325. * <p>
  326. * This method implements the fallback mechanism between the index and the
  327. * working tree depending on the operation type
  328. * </p>
  329. *
  330. * @param treeWalk
  331. * @param workingTreeIterator
  332. * @param dirCacheIterator
  333. * @param otherTree
  334. * @return a {@link AttributesNode} of the current entry,
  335. * {@link NullPointerException} otherwise.
  336. * @throws IOException
  337. * It raises an {@link IOException} if a problem appears while
  338. * parsing one on the attributes file.
  339. */
  340. private static AttributesNode attributesNode(TreeWalk treeWalk,
  341. @Nullable WorkingTreeIterator workingTreeIterator,
  342. @Nullable DirCacheIterator dirCacheIterator,
  343. @Nullable CanonicalTreeParser otherTree) throws IOException {
  344. AttributesNode attributesNode = null;
  345. switch (treeWalk.getOperationType()) {
  346. case CHECKIN_OP:
  347. if (workingTreeIterator != null) {
  348. attributesNode = workingTreeIterator.getEntryAttributesNode();
  349. }
  350. if (attributesNode == null && dirCacheIterator != null) {
  351. attributesNode = dirCacheIterator
  352. .getEntryAttributesNode(treeWalk.getObjectReader());
  353. }
  354. if (attributesNode == null && otherTree != null) {
  355. attributesNode = otherTree
  356. .getEntryAttributesNode(treeWalk.getObjectReader());
  357. }
  358. break;
  359. case CHECKOUT_OP:
  360. if (otherTree != null) {
  361. attributesNode = otherTree
  362. .getEntryAttributesNode(treeWalk.getObjectReader());
  363. }
  364. if (attributesNode == null && dirCacheIterator != null) {
  365. attributesNode = dirCacheIterator
  366. .getEntryAttributesNode(treeWalk.getObjectReader());
  367. }
  368. if (attributesNode == null && workingTreeIterator != null) {
  369. attributesNode = workingTreeIterator.getEntryAttributesNode();
  370. }
  371. break;
  372. default:
  373. throw new IllegalStateException(
  374. "The only supported operation types are:" //$NON-NLS-1$
  375. + OperationType.CHECKIN_OP + "," //$NON-NLS-1$
  376. + OperationType.CHECKOUT_OP);
  377. }
  378. return attributesNode;
  379. }
  380. private static <T extends AbstractTreeIterator> T parentOf(@Nullable T node) {
  381. if(node==null) return null;
  382. @SuppressWarnings("unchecked")
  383. Class<T> type = (Class<T>) node.getClass();
  384. AbstractTreeIterator parent = node.parent;
  385. if (type.isInstance(parent)) {
  386. return type.cast(parent);
  387. }
  388. return null;
  389. }
  390. private static <T extends AbstractTreeIterator> T rootOf(
  391. @Nullable T node) {
  392. if(node==null) return null;
  393. AbstractTreeIterator t=node;
  394. while (t!= null && t.parent != null) {
  395. t= t.parent;
  396. }
  397. @SuppressWarnings("unchecked")
  398. Class<T> type = (Class<T>) node.getClass();
  399. if (type.isInstance(t)) {
  400. return type.cast(t);
  401. }
  402. return null;
  403. }
  404. }