View Javadoc
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 			try {
393 				path[tmp] = c;
394 			} catch (ArrayIndexOutOfBoundsException e) {
395 				growPath(tmp);
396 				path[tmp] = c;
397 			}
398 		}
399 		pathLen = tmp;
400 		nextPtr = ptr + OBJECT_ID_LENGTH;
401 	}
402 
403 	/**
404 	 * Retrieve the {@link org.eclipse.jgit.attributes.AttributesNode} for the
405 	 * current entry.
406 	 *
407 	 * @param reader
408 	 *            {@link org.eclipse.jgit.lib.ObjectReader} used to parse the
409 	 *            .gitattributes entry.
410 	 * @return {@link org.eclipse.jgit.attributes.AttributesNode} for the
411 	 *         current entry.
412 	 * @throws java.io.IOException
413 	 * @since 4.2
414 	 */
415 	public AttributesNode getEntryAttributesNode(ObjectReader reader)
416 			throws IOException {
417 		if (attributesNode == null) {
418 			attributesNode = findAttributes(reader);
419 		}
420 		return attributesNode.getRules().isEmpty() ? null : attributesNode;
421 	}
422 
423 	private AttributesNode findAttributes(ObjectReader reader)
424 			throws IOException {
425 		CanonicalTreeParser itr = new CanonicalTreeParser();
426 		itr.reset(raw);
427 		if (itr.findFile(ATTRS)) {
428 			return loadAttributes(reader, itr.getEntryObjectId());
429 		}
430 		return noAttributes();
431 	}
432 
433 	private static AttributesNode loadAttributes(ObjectReader reader,
434 			AnyObjectId id) throws IOException {
435 		AttributesNode r = new AttributesNode();
436 		try (InputStream in = reader.open(id, OBJ_BLOB).openStream()) {
437 			r.parse(in);
438 		}
439 		return r.getRules().isEmpty() ? noAttributes() : r;
440 	}
441 
442 	private static AttributesNode noAttributes() {
443 		return new AttributesNode(Collections.<AttributesRule> emptyList());
444 	}
445 }