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.

CanonicalTreeParser.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. /*
  2. * Copyright (C) 2008-2010, Google Inc.
  3. * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
  4. *
  5. * This program and the accompanying materials are made available under the
  6. * terms of the Eclipse Distribution License v. 1.0 which is available at
  7. * https://www.eclipse.org/org/documents/edl-v10.php.
  8. *
  9. * SPDX-License-Identifier: BSD-3-Clause
  10. */
  11. package org.eclipse.jgit.treewalk;
  12. import static org.eclipse.jgit.lib.Constants.DOT_GIT_ATTRIBUTES;
  13. import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
  14. import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
  15. import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
  16. import static org.eclipse.jgit.lib.Constants.TYPE_TREE;
  17. import static org.eclipse.jgit.lib.Constants.encode;
  18. import java.io.IOException;
  19. import java.io.InputStream;
  20. import java.util.Arrays;
  21. import java.util.Collections;
  22. import org.eclipse.jgit.attributes.AttributesNode;
  23. import org.eclipse.jgit.attributes.AttributesRule;
  24. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  25. import org.eclipse.jgit.errors.MissingObjectException;
  26. import org.eclipse.jgit.lib.AnyObjectId;
  27. import org.eclipse.jgit.lib.FileMode;
  28. import org.eclipse.jgit.lib.MutableObjectId;
  29. import org.eclipse.jgit.lib.ObjectId;
  30. import org.eclipse.jgit.lib.ObjectReader;
  31. /**
  32. * Parses raw Git trees from the canonical semi-text/semi-binary format.
  33. */
  34. public class CanonicalTreeParser extends AbstractTreeIterator {
  35. private static final byte[] EMPTY = {};
  36. private static final byte[] ATTRS = encode(DOT_GIT_ATTRIBUTES);
  37. private byte[] raw;
  38. /** First offset within {@link #raw} of the prior entry. */
  39. private int prevPtr;
  40. /** First offset within {@link #raw} of the current entry's data. */
  41. private int currPtr;
  42. /** Offset one past the current entry (first byte of next entry). */
  43. private int nextPtr;
  44. /**
  45. * Create a new parser.
  46. */
  47. public CanonicalTreeParser() {
  48. reset(EMPTY);
  49. }
  50. /**
  51. * Create a new parser for a tree appearing in a subset of a repository.
  52. *
  53. * @param prefix
  54. * position of this iterator in the repository tree. The value
  55. * may be null or the empty array to indicate the prefix is the
  56. * root of the repository. A trailing slash ('/') is
  57. * automatically appended if the prefix does not end in '/'.
  58. * @param reader
  59. * reader to load the tree data from.
  60. * @param treeId
  61. * identity of the tree being parsed; used only in exception
  62. * messages if data corruption is found.
  63. * @throws MissingObjectException
  64. * the object supplied is not available from the repository.
  65. * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
  66. * the object supplied as an argument is not actually a tree and
  67. * cannot be parsed as though it were a tree.
  68. * @throws java.io.IOException
  69. * a loose object or pack file could not be read.
  70. */
  71. public CanonicalTreeParser(final byte[] prefix, final ObjectReader reader,
  72. final AnyObjectId treeId) throws IncorrectObjectTypeException,
  73. IOException {
  74. super(prefix);
  75. reset(reader, treeId);
  76. }
  77. private CanonicalTreeParser(CanonicalTreeParser p) {
  78. super(p);
  79. }
  80. /**
  81. * Get the parent of this tree parser.
  82. *
  83. * @return the parent of this tree parser.
  84. * @deprecated internal use only
  85. */
  86. @Deprecated
  87. public CanonicalTreeParser getParent() {
  88. return (CanonicalTreeParser) parent;
  89. }
  90. /**
  91. * Reset this parser to walk through the given tree data.
  92. *
  93. * @param treeData
  94. * the raw tree content.
  95. */
  96. public void reset(byte[] treeData) {
  97. attributesNode = null;
  98. raw = treeData;
  99. prevPtr = -1;
  100. currPtr = 0;
  101. if (eof())
  102. nextPtr = 0;
  103. else
  104. parseEntry();
  105. }
  106. /**
  107. * Reset this parser to walk through the given tree.
  108. *
  109. * @param reader
  110. * reader to use during repository access.
  111. * @param id
  112. * identity of the tree being parsed; used only in exception
  113. * messages if data corruption is found.
  114. * @return the root level parser.
  115. * @throws MissingObjectException
  116. * the object supplied is not available from the repository.
  117. * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
  118. * the object supplied as an argument is not actually a tree and
  119. * cannot be parsed as though it were a tree.
  120. * @throws java.io.IOException
  121. * a loose object or pack file could not be read.
  122. */
  123. public CanonicalTreeParser resetRoot(final ObjectReader reader,
  124. final AnyObjectId id) throws IncorrectObjectTypeException,
  125. IOException {
  126. CanonicalTreeParser p = this;
  127. while (p.parent != null)
  128. p = (CanonicalTreeParser) p.parent;
  129. p.reset(reader, id);
  130. return p;
  131. }
  132. /**
  133. * Get this iterator, or its parent, if the tree is at eof.
  134. *
  135. * @return this iterator, or its parent, if the tree is at eof.
  136. */
  137. public CanonicalTreeParser next() {
  138. CanonicalTreeParser p = this;
  139. for (;;) {
  140. if (p.nextPtr == p.raw.length) {
  141. // This parser has reached EOF, return to the parent.
  142. if (p.parent == null) {
  143. p.currPtr = p.nextPtr;
  144. return p;
  145. }
  146. p = (CanonicalTreeParser) p.parent;
  147. continue;
  148. }
  149. p.prevPtr = p.currPtr;
  150. p.currPtr = p.nextPtr;
  151. p.parseEntry();
  152. return p;
  153. }
  154. }
  155. /**
  156. * Reset this parser to walk through the given tree.
  157. *
  158. * @param reader
  159. * reader to use during repository access.
  160. * @param id
  161. * identity of the tree being parsed; used only in exception
  162. * messages if data corruption is found.
  163. * @throws MissingObjectException
  164. * the object supplied is not available from the repository.
  165. * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
  166. * the object supplied as an argument is not actually a tree and
  167. * cannot be parsed as though it were a tree.
  168. * @throws java.io.IOException
  169. * a loose object or pack file could not be read.
  170. */
  171. public void reset(ObjectReader reader, AnyObjectId id)
  172. throws IncorrectObjectTypeException, IOException {
  173. reset(reader.open(id, OBJ_TREE).getCachedBytes());
  174. }
  175. /** {@inheritDoc} */
  176. @Override
  177. public CanonicalTreeParser createSubtreeIterator(final ObjectReader reader,
  178. final MutableObjectId idBuffer)
  179. throws IncorrectObjectTypeException, IOException {
  180. idBuffer.fromRaw(idBuffer(), idOffset());
  181. if (!FileMode.TREE.equals(mode)) {
  182. final ObjectId me = idBuffer.toObjectId();
  183. throw new IncorrectObjectTypeException(me, TYPE_TREE);
  184. }
  185. return createSubtreeIterator0(reader, idBuffer);
  186. }
  187. /**
  188. * Back door to quickly create a subtree iterator for any subtree.
  189. * <p>
  190. * Don't use this unless you are ObjectWalk. The method is meant to be
  191. * called only once the current entry has been identified as a tree and its
  192. * identity has been converted into an ObjectId.
  193. *
  194. * @param reader
  195. * reader to load the tree data from.
  196. * @param id
  197. * ObjectId of the tree to open.
  198. * @return a new parser that walks over the current subtree.
  199. * @throws java.io.IOException
  200. * a loose object or pack file could not be read.
  201. */
  202. public final CanonicalTreeParser createSubtreeIterator0(
  203. final ObjectReader reader, final AnyObjectId id)
  204. throws IOException {
  205. final CanonicalTreeParser p = new CanonicalTreeParser(this);
  206. p.reset(reader, id);
  207. return p;
  208. }
  209. /** {@inheritDoc} */
  210. @Override
  211. public CanonicalTreeParser createSubtreeIterator(ObjectReader reader)
  212. throws IncorrectObjectTypeException, IOException {
  213. return createSubtreeIterator(reader, new MutableObjectId());
  214. }
  215. /** {@inheritDoc} */
  216. @Override
  217. public boolean hasId() {
  218. return true;
  219. }
  220. /** {@inheritDoc} */
  221. @Override
  222. public byte[] idBuffer() {
  223. return raw;
  224. }
  225. /** {@inheritDoc} */
  226. @Override
  227. public int idOffset() {
  228. return nextPtr - OBJECT_ID_LENGTH;
  229. }
  230. /** {@inheritDoc} */
  231. @Override
  232. public void reset() {
  233. if (!first())
  234. reset(raw);
  235. }
  236. /** {@inheritDoc} */
  237. @Override
  238. public boolean first() {
  239. return currPtr == 0;
  240. }
  241. /** {@inheritDoc} */
  242. @Override
  243. public boolean eof() {
  244. return currPtr == raw.length;
  245. }
  246. /** {@inheritDoc} */
  247. @Override
  248. public void next(int delta) {
  249. if (delta == 1) {
  250. // Moving forward one is the most common case.
  251. //
  252. prevPtr = currPtr;
  253. currPtr = nextPtr;
  254. if (!eof())
  255. parseEntry();
  256. return;
  257. }
  258. // Fast skip over records, then parse the last one.
  259. //
  260. final int end = raw.length;
  261. int ptr = nextPtr;
  262. while (--delta > 0 && ptr != end) {
  263. prevPtr = ptr;
  264. while (raw[ptr] != 0)
  265. ptr++;
  266. ptr += OBJECT_ID_LENGTH + 1;
  267. }
  268. if (delta != 0)
  269. throw new ArrayIndexOutOfBoundsException(delta);
  270. currPtr = ptr;
  271. if (!eof())
  272. parseEntry();
  273. }
  274. /** {@inheritDoc} */
  275. @Override
  276. public void back(int delta) {
  277. if (delta == 1 && 0 <= prevPtr) {
  278. // Moving back one is common in NameTreeWalk, as the average tree
  279. // won't have D/F type conflicts to study.
  280. //
  281. currPtr = prevPtr;
  282. prevPtr = -1;
  283. if (!eof())
  284. parseEntry();
  285. return;
  286. } else if (delta <= 0)
  287. throw new ArrayIndexOutOfBoundsException(delta);
  288. // Fast skip through the records, from the beginning of the tree.
  289. // There is no reliable way to read the tree backwards, so we must
  290. // parse all over again from the beginning. We hold the last "delta"
  291. // positions in a buffer, so we can find the correct position later.
  292. //
  293. final int[] trace = new int[delta + 1];
  294. Arrays.fill(trace, -1);
  295. int ptr = 0;
  296. while (ptr != currPtr) {
  297. System.arraycopy(trace, 1, trace, 0, delta);
  298. trace[delta] = ptr;
  299. while (raw[ptr] != 0)
  300. ptr++;
  301. ptr += OBJECT_ID_LENGTH + 1;
  302. }
  303. if (trace[1] == -1)
  304. throw new ArrayIndexOutOfBoundsException(delta);
  305. prevPtr = trace[0];
  306. currPtr = trace[1];
  307. parseEntry();
  308. }
  309. private void parseEntry() {
  310. int ptr = currPtr;
  311. byte c = raw[ptr++];
  312. int tmp = c - '0';
  313. for (;;) {
  314. c = raw[ptr++];
  315. if (' ' == c)
  316. break;
  317. tmp <<= 3;
  318. tmp += c - '0';
  319. }
  320. mode = tmp;
  321. tmp = pathOffset;
  322. for (;; tmp++) {
  323. c = raw[ptr++];
  324. if (c == 0) {
  325. break;
  326. }
  327. if (tmp >= path.length) {
  328. growPath(tmp);
  329. }
  330. path[tmp] = c;
  331. }
  332. pathLen = tmp;
  333. nextPtr = ptr + OBJECT_ID_LENGTH;
  334. }
  335. /**
  336. * Retrieve the {@link org.eclipse.jgit.attributes.AttributesNode} for the
  337. * current entry.
  338. *
  339. * @param reader
  340. * {@link org.eclipse.jgit.lib.ObjectReader} used to parse the
  341. * .gitattributes entry.
  342. * @return {@link org.eclipse.jgit.attributes.AttributesNode} for the
  343. * current entry.
  344. * @throws java.io.IOException
  345. * @since 4.2
  346. */
  347. public AttributesNode getEntryAttributesNode(ObjectReader reader)
  348. throws IOException {
  349. if (attributesNode == null) {
  350. attributesNode = findAttributes(reader);
  351. }
  352. return attributesNode.getRules().isEmpty() ? null : attributesNode;
  353. }
  354. private AttributesNode findAttributes(ObjectReader reader)
  355. throws IOException {
  356. CanonicalTreeParser itr = new CanonicalTreeParser();
  357. itr.reset(raw);
  358. if (itr.findFile(ATTRS)) {
  359. return loadAttributes(reader, itr.getEntryObjectId());
  360. }
  361. return noAttributes();
  362. }
  363. private static AttributesNode loadAttributes(ObjectReader reader,
  364. AnyObjectId id) throws IOException {
  365. AttributesNode r = new AttributesNode();
  366. try (InputStream in = reader.open(id, OBJ_BLOB).openStream()) {
  367. r.parse(in);
  368. }
  369. return r.getRules().isEmpty() ? noAttributes() : r;
  370. }
  371. private static AttributesNode noAttributes() {
  372. return new AttributesNode(Collections.<AttributesRule> emptyList());
  373. }
  374. }