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  /** Parses raw Git trees from the canonical semi-text/semi-binary format. */
70  public class CanonicalTreeParser extends AbstractTreeIterator {
71  	private static final byte[] EMPTY = {};
72  	private static final byte[] ATTRS = encode(DOT_GIT_ATTRIBUTES);
73  
74  	private byte[] raw;
75  
76  	/** First offset within {@link #raw} of the prior entry. */
77  	private int prevPtr;
78  
79  	/** First offset within {@link #raw} of the current entry's data. */
80  	private int currPtr;
81  
82  	/** Offset one past the current entry (first byte of next entry). */
83  	private int nextPtr;
84  
85  	/** Create a new parser. */
86  	public CanonicalTreeParser() {
87  		reset(EMPTY);
88  	}
89  
90  	/**
91  	 * Create a new parser for a tree appearing in a subset of a repository.
92  	 *
93  	 * @param prefix
94  	 *            position of this iterator in the repository tree. The value
95  	 *            may be null or the empty array to indicate the prefix is the
96  	 *            root of the repository. A trailing slash ('/') is
97  	 *            automatically appended if the prefix does not end in '/'.
98  	 * @param reader
99  	 *            reader to load the tree data from.
100 	 * @param treeId
101 	 *            identity of the tree being parsed; used only in exception
102 	 *            messages if data corruption is found.
103 	 * @throws MissingObjectException
104 	 *             the object supplied is not available from the repository.
105 	 * @throws IncorrectObjectTypeException
106 	 *             the object supplied as an argument is not actually a tree and
107 	 *             cannot be parsed as though it were a tree.
108 	 * @throws IOException
109 	 *             a loose object or pack file could not be read.
110 	 */
111 	public CanonicalTreeParser(final byte[] prefix, final ObjectReader reader,
112 			final AnyObjectId treeId) throws IncorrectObjectTypeException,
113 			IOException {
114 		super(prefix);
115 		reset(reader, treeId);
116 	}
117 
118 	private CanonicalTreeParser(final CanonicalTreeParser p) {
119 		super(p);
120 	}
121 
122 	/**
123 	 * @return the parent of this tree parser
124 	 * @deprecated internal use only
125 	 */
126 	@Deprecated
127 	public CanonicalTreeParser getParent() {
128 		return (CanonicalTreeParser) parent;
129 	}
130 
131 	/**
132 	 * Reset this parser to walk through the given tree data.
133 	 *
134 	 * @param treeData
135 	 *            the raw tree content.
136 	 */
137 	public void reset(final byte[] treeData) {
138 		attributesNode = null;
139 		raw = treeData;
140 		prevPtr = -1;
141 		currPtr = 0;
142 		if (eof())
143 			nextPtr = 0;
144 		else
145 			parseEntry();
146 	}
147 
148 	/**
149 	 * Reset this parser to walk through the given tree.
150 	 *
151 	 * @param reader
152 	 *            reader to use during repository access.
153 	 * @param id
154 	 *            identity of the tree being parsed; used only in exception
155 	 *            messages if data corruption is found.
156 	 * @return the root level parser.
157 	 * @throws MissingObjectException
158 	 *             the object supplied is not available from the repository.
159 	 * @throws IncorrectObjectTypeException
160 	 *             the object supplied as an argument is not actually a tree and
161 	 *             cannot be parsed as though it were a tree.
162 	 * @throws IOException
163 	 *             a loose object or pack file could not be read.
164 	 */
165 	public CanonicalTreeParser resetRoot(final ObjectReader reader,
166 			final AnyObjectId id) throws IncorrectObjectTypeException,
167 			IOException {
168 		CanonicalTreeParser p = this;
169 		while (p.parent != null)
170 			p = (CanonicalTreeParser) p.parent;
171 		p.reset(reader, id);
172 		return p;
173 	}
174 
175 	/** @return this iterator, or its parent, if the tree is at eof. */
176 	public CanonicalTreeParser next() {
177 		CanonicalTreeParser p = this;
178 		for (;;) {
179 			if (p.nextPtr == p.raw.length) {
180 				// This parser has reached EOF, return to the parent.
181 				if (p.parent == null) {
182 					p.currPtr = p.nextPtr;
183 					return p;
184 				}
185 				p = (CanonicalTreeParser) p.parent;
186 				continue;
187 			}
188 
189 			p.prevPtr = p.currPtr;
190 			p.currPtr = p.nextPtr;
191 			p.parseEntry();
192 			return p;
193 		}
194 	}
195 
196 	/**
197 	 * Reset this parser to walk through the given tree.
198 	 *
199 	 * @param reader
200 	 *            reader to use during repository access.
201 	 * @param id
202 	 *            identity of the tree being parsed; used only in exception
203 	 *            messages if data corruption is found.
204 	 * @throws MissingObjectException
205 	 *             the object supplied is not available from the repository.
206 	 * @throws IncorrectObjectTypeException
207 	 *             the object supplied as an argument is not actually a tree and
208 	 *             cannot be parsed as though it were a tree.
209 	 * @throws IOException
210 	 *             a loose object or pack file could not be read.
211 	 */
212 	public void reset(final ObjectReader reader, final AnyObjectId id)
213 			throws IncorrectObjectTypeException, IOException {
214 		reset(reader.open(id, OBJ_TREE).getCachedBytes());
215 	}
216 
217 	@Override
218 	public CanonicalTreeParser createSubtreeIterator(final ObjectReader reader,
219 			final MutableObjectId idBuffer)
220 			throws IncorrectObjectTypeException, IOException {
221 		idBuffer.fromRaw(idBuffer(), idOffset());
222 		if (!FileMode.TREE.equals(mode)) {
223 			final ObjectId me = idBuffer.toObjectId();
224 			throw new IncorrectObjectTypeException(me, TYPE_TREE);
225 		}
226 		return createSubtreeIterator0(reader, idBuffer);
227 	}
228 
229 	/**
230 	 * Back door to quickly create a subtree iterator for any subtree.
231 	 * <p>
232 	 * Don't use this unless you are ObjectWalk. The method is meant to be
233 	 * called only once the current entry has been identified as a tree and its
234 	 * identity has been converted into an ObjectId.
235 	 *
236 	 * @param reader
237 	 *            reader to load the tree data from.
238 	 * @param id
239 	 *            ObjectId of the tree to open.
240 	 * @return a new parser that walks over the current subtree.
241 	 * @throws IOException
242 	 *             a loose object or pack file could not be read.
243 	 */
244 	public final CanonicalTreeParser createSubtreeIterator0(
245 			final ObjectReader reader, final AnyObjectId id)
246 			throws IOException {
247 		final CanonicalTreeParser p = new CanonicalTreeParser(this);
248 		p.reset(reader, id);
249 		return p;
250 	}
251 
252 	@Override
253 	public CanonicalTreeParser createSubtreeIterator(final ObjectReader reader)
254 			throws IncorrectObjectTypeException, IOException {
255 		return createSubtreeIterator(reader, new MutableObjectId());
256 	}
257 
258 	@Override
259 	public boolean hasId() {
260 		return true;
261 	}
262 
263 	@Override
264 	public byte[] idBuffer() {
265 		return raw;
266 	}
267 
268 	@Override
269 	public int idOffset() {
270 		return nextPtr - OBJECT_ID_LENGTH;
271 	}
272 
273 	@Override
274 	public void reset() {
275 		if (!first())
276 			reset(raw);
277 	}
278 
279 	@Override
280 	public boolean first() {
281 		return currPtr == 0;
282 	}
283 
284 	@Override
285 	public boolean eof() {
286 		return currPtr == raw.length;
287 	}
288 
289 	@Override
290 	public void next(int delta) {
291 		if (delta == 1) {
292 			// Moving forward one is the most common case.
293 			//
294 			prevPtr = currPtr;
295 			currPtr = nextPtr;
296 			if (!eof())
297 				parseEntry();
298 			return;
299 		}
300 
301 		// Fast skip over records, then parse the last one.
302 		//
303 		final int end = raw.length;
304 		int ptr = nextPtr;
305 		while (--delta > 0 && ptr != end) {
306 			prevPtr = ptr;
307 			while (raw[ptr] != 0)
308 				ptr++;
309 			ptr += OBJECT_ID_LENGTH + 1;
310 		}
311 		if (delta != 0)
312 			throw new ArrayIndexOutOfBoundsException(delta);
313 		currPtr = ptr;
314 		if (!eof())
315 			parseEntry();
316 	}
317 
318 	@Override
319 	public void back(int delta) {
320 		if (delta == 1 && 0 <= prevPtr) {
321 			// Moving back one is common in NameTreeWalk, as the average tree
322 			// won't have D/F type conflicts to study.
323 			//
324 			currPtr = prevPtr;
325 			prevPtr = -1;
326 			if (!eof())
327 				parseEntry();
328 			return;
329 		} else if (delta <= 0)
330 			throw new ArrayIndexOutOfBoundsException(delta);
331 
332 		// Fast skip through the records, from the beginning of the tree.
333 		// There is no reliable way to read the tree backwards, so we must
334 		// parse all over again from the beginning. We hold the last "delta"
335 		// positions in a buffer, so we can find the correct position later.
336 		//
337 		final int[] trace = new int[delta + 1];
338 		Arrays.fill(trace, -1);
339 		int ptr = 0;
340 		while (ptr != currPtr) {
341 			System.arraycopy(trace, 1, trace, 0, delta);
342 			trace[delta] = ptr;
343 			while (raw[ptr] != 0)
344 				ptr++;
345 			ptr += OBJECT_ID_LENGTH + 1;
346 		}
347 		if (trace[1] == -1)
348 			throw new ArrayIndexOutOfBoundsException(delta);
349 		prevPtr = trace[0];
350 		currPtr = trace[1];
351 		parseEntry();
352 	}
353 
354 	private void parseEntry() {
355 		int ptr = currPtr;
356 		byte c = raw[ptr++];
357 		int tmp = c - '0';
358 		for (;;) {
359 			c = raw[ptr++];
360 			if (' ' == c)
361 				break;
362 			tmp <<= 3;
363 			tmp += c - '0';
364 		}
365 		mode = tmp;
366 
367 		tmp = pathOffset;
368 		for (;; tmp++) {
369 			c = raw[ptr++];
370 			if (c == 0)
371 				break;
372 			try {
373 				path[tmp] = c;
374 			} catch (ArrayIndexOutOfBoundsException e) {
375 				growPath(tmp);
376 				path[tmp] = c;
377 			}
378 		}
379 		pathLen = tmp;
380 		nextPtr = ptr + OBJECT_ID_LENGTH;
381 	}
382 
383 	/**
384 	 * Retrieve the {@link AttributesNode} for the current entry.
385 	 *
386 	 * @param reader
387 	 *            {@link ObjectReader} used to parse the .gitattributes entry.
388 	 * @return {@link AttributesNode} for the current entry.
389 	 * @throws IOException
390 	 * @since 4.2
391 	 */
392 	public AttributesNode getEntryAttributesNode(ObjectReader reader)
393 			throws IOException {
394 		if (attributesNode == null) {
395 			attributesNode = findAttributes(reader);
396 		}
397 		return attributesNode.getRules().isEmpty() ? null : attributesNode;
398 	}
399 
400 	private AttributesNode findAttributes(ObjectReader reader)
401 			throws IOException {
402 		CanonicalTreeParser itr = new CanonicalTreeParser();
403 		itr.reset(raw);
404 		if (itr.findFile(ATTRS)) {
405 			return loadAttributes(reader, itr.getEntryObjectId());
406 		}
407 		return noAttributes();
408 	}
409 
410 	private static AttributesNode loadAttributes(ObjectReader reader,
411 			AnyObjectId id) throws IOException {
412 		AttributesNode r = new AttributesNode();
413 		try (InputStream in = reader.open(id, OBJ_BLOB).openStream()) {
414 			r.parse(in);
415 		}
416 		return r.getRules().isEmpty() ? noAttributes() : r;
417 	}
418 
419 	private static AttributesNode noAttributes() {
420 		return new AttributesNode(Collections.<AttributesRule> emptyList());
421 	}
422 }