1 /*
2 * Copyright (C) 2008-2010, Google Inc.
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
4 * and other copyright owners as documented in the project's IP log.
5 *
6 * This program and the accompanying materials are made available
7 * under the terms of the Eclipse Distribution License v1.0 which
8 * accompanies this distribution, is reproduced below, and is
9 * available at http://www.eclipse.org/org/documents/edl-v10.php
10 *
11 * All rights reserved.
12 *
13 * Redistribution and use in source and binary forms, with or
14 * without modification, are permitted provided that the following
15 * conditions are met:
16 *
17 * - Redistributions of source code must retain the above copyright
18 * notice, this list of conditions and the following disclaimer.
19 *
20 * - Redistributions in binary form must reproduce the above
21 * copyright notice, this list of conditions and the following
22 * disclaimer in the documentation and/or other materials provided
23 * with the distribution.
24 *
25 * - Neither the name of the Eclipse Foundation, Inc. nor the
26 * names of its contributors may be used to endorse or promote
27 * products derived from this software without specific prior
28 * written permission.
29 *
30 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
31 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
32 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
33 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
34 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
35 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
36 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
37 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
38 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
39 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
40 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
42 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43 */
44
45 package org.eclipse.jgit.treewalk;
46
47 import static org.eclipse.jgit.lib.Constants.DOT_GIT_ATTRIBUTES;
48 import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
49 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
50 import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
51 import static org.eclipse.jgit.lib.Constants.TYPE_TREE;
52 import static org.eclipse.jgit.lib.Constants.encode;
53
54 import java.io.IOException;
55 import java.io.InputStream;
56 import java.util.Arrays;
57 import java.util.Collections;
58
59 import org.eclipse.jgit.attributes.AttributesNode;
60 import org.eclipse.jgit.attributes.AttributesRule;
61 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
62 import org.eclipse.jgit.errors.MissingObjectException;
63 import org.eclipse.jgit.lib.AnyObjectId;
64 import org.eclipse.jgit.lib.FileMode;
65 import org.eclipse.jgit.lib.MutableObjectId;
66 import org.eclipse.jgit.lib.ObjectId;
67 import org.eclipse.jgit.lib.ObjectReader;
68
69 /**
70 * Parses raw Git trees from the canonical semi-text/semi-binary format.
71 */
72 public class CanonicalTreeParser extends AbstractTreeIterator {
73 private static final byte[] EMPTY = {};
74 private static final byte[] ATTRS = encode(DOT_GIT_ATTRIBUTES);
75
76 private byte[] raw;
77
78 /** First offset within {@link #raw} of the prior entry. */
79 private int prevPtr;
80
81 /** First offset within {@link #raw} of the current entry's data. */
82 private int currPtr;
83
84 /** Offset one past the current entry (first byte of next entry). */
85 private int nextPtr;
86
87 /**
88 * Create a new parser.
89 */
90 public CanonicalTreeParser() {
91 reset(EMPTY);
92 }
93
94 /**
95 * Create a new parser for a tree appearing in a subset of a repository.
96 *
97 * @param prefix
98 * position of this iterator in the repository tree. The value
99 * may be null or the empty array to indicate the prefix is the
100 * root of the repository. A trailing slash ('/') is
101 * automatically appended if the prefix does not end in '/'.
102 * @param reader
103 * reader to load the tree data from.
104 * @param treeId
105 * identity of the tree being parsed; used only in exception
106 * messages if data corruption is found.
107 * @throws MissingObjectException
108 * the object supplied is not available from the repository.
109 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
110 * the object supplied as an argument is not actually a tree and
111 * cannot be parsed as though it were a tree.
112 * @throws java.io.IOException
113 * a loose object or pack file could not be read.
114 */
115 public CanonicalTreeParser(final byte[] prefix, final ObjectReader reader,
116 final AnyObjectId treeId) throws IncorrectObjectTypeException,
117 IOException {
118 super(prefix);
119 reset(reader, treeId);
120 }
121
122 private CanonicalTreeParser(CanonicalTreeParser p) {
123 super(p);
124 }
125
126 /**
127 * Get the parent of this tree parser.
128 *
129 * @return the parent of this tree parser.
130 * @deprecated internal use only
131 */
132 @Deprecated
133 public CanonicalTreeParser getParent() {
134 return (CanonicalTreeParser) parent;
135 }
136
137 /**
138 * Reset this parser to walk through the given tree data.
139 *
140 * @param treeData
141 * the raw tree content.
142 */
143 public void reset(byte[] treeData) {
144 attributesNode = null;
145 raw = treeData;
146 prevPtr = -1;
147 currPtr = 0;
148 if (eof())
149 nextPtr = 0;
150 else
151 parseEntry();
152 }
153
154 /**
155 * Reset this parser to walk through the given tree.
156 *
157 * @param reader
158 * reader to use during repository access.
159 * @param id
160 * identity of the tree being parsed; used only in exception
161 * messages if data corruption is found.
162 * @return the root level parser.
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 CanonicalTreeParser resetRoot(final ObjectReader reader,
172 final AnyObjectId id) throws IncorrectObjectTypeException,
173 IOException {
174 CanonicalTreeParser p = this;
175 while (p.parent != null)
176 p = (CanonicalTreeParser) p.parent;
177 p.reset(reader, id);
178 return p;
179 }
180
181 /**
182 * Get this iterator, or its parent, if the tree is at eof.
183 *
184 * @return this iterator, or its parent, if the tree is at eof.
185 */
186 public CanonicalTreeParser next() {
187 CanonicalTreeParser p = this;
188 for (;;) {
189 if (p.nextPtr == p.raw.length) {
190 // This parser has reached EOF, return to the parent.
191 if (p.parent == null) {
192 p.currPtr = p.nextPtr;
193 return p;
194 }
195 p = (CanonicalTreeParser) p.parent;
196 continue;
197 }
198
199 p.prevPtr = p.currPtr;
200 p.currPtr = p.nextPtr;
201 p.parseEntry();
202 return p;
203 }
204 }
205
206 /**
207 * Reset this parser to walk through the given tree.
208 *
209 * @param reader
210 * reader to use during repository access.
211 * @param id
212 * identity of the tree being parsed; used only in exception
213 * messages if data corruption is found.
214 * @throws MissingObjectException
215 * the object supplied is not available from the repository.
216 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
217 * the object supplied as an argument is not actually a tree and
218 * cannot be parsed as though it were a tree.
219 * @throws java.io.IOException
220 * a loose object or pack file could not be read.
221 */
222 public void reset(ObjectReader reader, AnyObjectId id)
223 throws IncorrectObjectTypeException, IOException {
224 reset(reader.open(id, OBJ_TREE).getCachedBytes());
225 }
226
227 /** {@inheritDoc} */
228 @Override
229 public CanonicalTreeParser createSubtreeIterator(final ObjectReader reader,
230 final MutableObjectId idBuffer)
231 throws IncorrectObjectTypeException, IOException {
232 idBuffer.fromRaw(idBuffer(), idOffset());
233 if (!FileMode.TREE.equals(mode)) {
234 final ObjectId me = idBuffer.toObjectId();
235 throw new IncorrectObjectTypeException(me, TYPE_TREE);
236 }
237 return createSubtreeIterator0(reader, idBuffer);
238 }
239
240 /**
241 * Back door to quickly create a subtree iterator for any subtree.
242 * <p>
243 * Don't use this unless you are ObjectWalk. The method is meant to be
244 * called only once the current entry has been identified as a tree and its
245 * identity has been converted into an ObjectId.
246 *
247 * @param reader
248 * reader to load the tree data from.
249 * @param id
250 * ObjectId of the tree to open.
251 * @return a new parser that walks over the current subtree.
252 * @throws java.io.IOException
253 * a loose object or pack file could not be read.
254 */
255 public final CanonicalTreeParser createSubtreeIterator0(
256 final ObjectReader reader, final AnyObjectId id)
257 throws IOException {
258 final CanonicalTreeParser p = new CanonicalTreeParser(this);
259 p.reset(reader, id);
260 return p;
261 }
262
263 /** {@inheritDoc} */
264 @Override
265 public CanonicalTreeParser createSubtreeIterator(ObjectReader reader)
266 throws IncorrectObjectTypeException, IOException {
267 return createSubtreeIterator(reader, new MutableObjectId());
268 }
269
270 /** {@inheritDoc} */
271 @Override
272 public boolean hasId() {
273 return true;
274 }
275
276 /** {@inheritDoc} */
277 @Override
278 public byte[] idBuffer() {
279 return raw;
280 }
281
282 /** {@inheritDoc} */
283 @Override
284 public int idOffset() {
285 return nextPtr - OBJECT_ID_LENGTH;
286 }
287
288 /** {@inheritDoc} */
289 @Override
290 public void reset() {
291 if (!first())
292 reset(raw);
293 }
294
295 /** {@inheritDoc} */
296 @Override
297 public boolean first() {
298 return currPtr == 0;
299 }
300
301 /** {@inheritDoc} */
302 @Override
303 public boolean eof() {
304 return currPtr == raw.length;
305 }
306
307 /** {@inheritDoc} */
308 @Override
309 public void next(int delta) {
310 if (delta == 1) {
311 // Moving forward one is the most common case.
312 //
313 prevPtr = currPtr;
314 currPtr = nextPtr;
315 if (!eof())
316 parseEntry();
317 return;
318 }
319
320 // Fast skip over records, then parse the last one.
321 //
322 final int end = raw.length;
323 int ptr = nextPtr;
324 while (--delta > 0 && ptr != end) {
325 prevPtr = ptr;
326 while (raw[ptr] != 0)
327 ptr++;
328 ptr += OBJECT_ID_LENGTH + 1;
329 }
330 if (delta != 0)
331 throw new ArrayIndexOutOfBoundsException(delta);
332 currPtr = ptr;
333 if (!eof())
334 parseEntry();
335 }
336
337 /** {@inheritDoc} */
338 @Override
339 public void back(int delta) {
340 if (delta == 1 && 0 <= prevPtr) {
341 // Moving back one is common in NameTreeWalk, as the average tree
342 // won't have D/F type conflicts to study.
343 //
344 currPtr = prevPtr;
345 prevPtr = -1;
346 if (!eof())
347 parseEntry();
348 return;
349 } else if (delta <= 0)
350 throw new ArrayIndexOutOfBoundsException(delta);
351
352 // Fast skip through the records, from the beginning of the tree.
353 // There is no reliable way to read the tree backwards, so we must
354 // parse all over again from the beginning. We hold the last "delta"
355 // positions in a buffer, so we can find the correct position later.
356 //
357 final int[] trace = new int[delta + 1];
358 Arrays.fill(trace, -1);
359 int ptr = 0;
360 while (ptr != currPtr) {
361 System.arraycopy(trace, 1, trace, 0, delta);
362 trace[delta] = ptr;
363 while (raw[ptr] != 0)
364 ptr++;
365 ptr += OBJECT_ID_LENGTH + 1;
366 }
367 if (trace[1] == -1)
368 throw new ArrayIndexOutOfBoundsException(delta);
369 prevPtr = trace[0];
370 currPtr = trace[1];
371 parseEntry();
372 }
373
374 private void parseEntry() {
375 int ptr = currPtr;
376 byte c = raw[ptr++];
377 int tmp = c - '0';
378 for (;;) {
379 c = raw[ptr++];
380 if (' ' == c)
381 break;
382 tmp <<= 3;
383 tmp += c - '0';
384 }
385 mode = tmp;
386
387 tmp = pathOffset;
388 for (;; tmp++) {
389 c = raw[ptr++];
390 if (c == 0) {
391 break;
392 }
393 if (tmp >= path.length) {
394 growPath(tmp);
395 }
396 path[tmp] = c;
397 }
398 pathLen = tmp;
399 nextPtr = ptr + OBJECT_ID_LENGTH;
400 }
401
402 /**
403 * Retrieve the {@link org.eclipse.jgit.attributes.AttributesNode} for the
404 * current entry.
405 *
406 * @param reader
407 * {@link org.eclipse.jgit.lib.ObjectReader} used to parse the
408 * .gitattributes entry.
409 * @return {@link org.eclipse.jgit.attributes.AttributesNode} for the
410 * current entry.
411 * @throws java.io.IOException
412 * @since 4.2
413 */
414 public AttributesNode getEntryAttributesNode(ObjectReader reader)
415 throws IOException {
416 if (attributesNode == null) {
417 attributesNode = findAttributes(reader);
418 }
419 return attributesNode.getRules().isEmpty() ? null : attributesNode;
420 }
421
422 private AttributesNode findAttributes(ObjectReader reader)
423 throws IOException {
424 CanonicalTreeParser itr = new CanonicalTreeParser();
425 itr.reset(raw);
426 if (itr.findFile(ATTRS)) {
427 return loadAttributes(reader, itr.getEntryObjectId());
428 }
429 return noAttributes();
430 }
431
432 private static AttributesNode loadAttributes(ObjectReader reader,
433 AnyObjectId id) throws IOException {
434 AttributesNode r = new AttributesNode();
435 try (InputStream in = reader.open(id, OBJ_BLOB).openStream()) {
436 r.parse(in);
437 }
438 return r.getRules().isEmpty() ? noAttributes() : r;
439 }
440
441 private static AttributesNode noAttributes() {
442 return new AttributesNode(Collections.<AttributesRule> emptyList());
443 }
444 }