View Javadoc
1   /*
2    * Copyright (C) 2008-2009, 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.dircache;
46  
47  import static org.eclipse.jgit.lib.FileMode.TREE;
48  import static org.eclipse.jgit.lib.TreeFormatter.entrySize;
49  
50  import java.io.IOException;
51  import java.io.OutputStream;
52  import java.nio.ByteBuffer;
53  import java.util.Arrays;
54  import java.util.Comparator;
55  
56  import org.eclipse.jgit.errors.UnmergedPathException;
57  import org.eclipse.jgit.lib.Constants;
58  import org.eclipse.jgit.lib.ObjectId;
59  import org.eclipse.jgit.lib.ObjectInserter;
60  import org.eclipse.jgit.lib.TreeFormatter;
61  import org.eclipse.jgit.util.MutableInteger;
62  import org.eclipse.jgit.util.RawParseUtils;
63  
64  /**
65   * Single tree record from the 'TREE' {@link DirCache} extension.
66   * <p>
67   * A valid cache tree record contains the object id of a tree object and the
68   * total number of {@link DirCacheEntry} instances (counted recursively) from
69   * the DirCache contained within the tree. This information facilitates faster
70   * traversal of the index and quicker generation of tree objects prior to
71   * creating a new commit.
72   * <p>
73   * An invalid cache tree record indicates a known subtree whose file entries
74   * have changed in ways that cause the tree to no longer have a known object id.
75   * Invalid cache tree records must be revalidated prior to use.
76   */
77  public class DirCacheTree {
78  	private static final byte[] NO_NAME = {};
79  
80  	private static final DirCacheTree[] NO_CHILDREN = {};
81  
82  	private static final Comparator<DirCacheTree> TREE_CMP = new Comparator<DirCacheTree>() {
83  		public int compare(final DirCacheTree o1, final DirCacheTree o2) {
84  			final byte[] a = o1.encodedName;
85  			final byte[] b = o2.encodedName;
86  			final int aLen = a.length;
87  			final int bLen = b.length;
88  			int cPos;
89  			for (cPos = 0; cPos < aLen && cPos < bLen; cPos++) {
90  				final int cmp = (a[cPos] & 0xff) - (b[cPos] & 0xff);
91  				if (cmp != 0)
92  					return cmp;
93  			}
94  			if (aLen == bLen)
95  				return 0;
96  			if (aLen < bLen)
97  				return '/' - (b[cPos] & 0xff);
98  			return (a[cPos] & 0xff) - '/';
99  		}
100 	};
101 
102 	/** Tree this tree resides in; null if we are the root. */
103 	private DirCacheTree parent;
104 
105 	/** Name of this tree within its parent. */
106 	byte[] encodedName;
107 
108 	/** Number of {@link DirCacheEntry} records that belong to this tree. */
109 	private int entrySpan;
110 
111 	/** Unique SHA-1 of this tree; null if invalid. */
112 	private ObjectId id;
113 
114 	/** Child trees, if any, sorted by {@link #encodedName}. */
115 	private DirCacheTree[] children;
116 
117 	/** Number of valid children in {@link #children}. */
118 	private int childCnt;
119 
120 	DirCacheTree() {
121 		encodedName = NO_NAME;
122 		children = NO_CHILDREN;
123 		childCnt = 0;
124 		entrySpan = -1;
125 	}
126 
127 	private DirCacheTree(final DirCacheTree myParent, final byte[] path,
128 			final int pathOff, final int pathLen) {
129 		parent = myParent;
130 		encodedName = new byte[pathLen];
131 		System.arraycopy(path, pathOff, encodedName, 0, pathLen);
132 		children = NO_CHILDREN;
133 		childCnt = 0;
134 		entrySpan = -1;
135 	}
136 
137 	DirCacheTree(final byte[] in, final MutableInteger off,
138 			final DirCacheTree myParent) {
139 		parent = myParent;
140 
141 		int ptr = RawParseUtils.next(in, off.value, '\0');
142 		final int nameLen = ptr - off.value - 1;
143 		if (nameLen > 0) {
144 			encodedName = new byte[nameLen];
145 			System.arraycopy(in, off.value, encodedName, 0, nameLen);
146 		} else
147 			encodedName = NO_NAME;
148 
149 		entrySpan = RawParseUtils.parseBase10(in, ptr, off);
150 		final int subcnt = RawParseUtils.parseBase10(in, off.value, off);
151 		off.value = RawParseUtils.next(in, off.value, '\n');
152 
153 		if (entrySpan >= 0) {
154 			// Valid trees have a positive entry count and an id of a
155 			// tree object that should exist in the object database.
156 			//
157 			id = ObjectId.fromRaw(in, off.value);
158 			off.value += Constants.OBJECT_ID_LENGTH;
159 		}
160 
161 		if (subcnt > 0) {
162 			boolean alreadySorted = true;
163 			children = new DirCacheTree[subcnt];
164 			for (int i = 0; i < subcnt; i++) {
165 				children[i] = new DirCacheTree(in, off, this);
166 
167 				// C Git's ordering differs from our own; it prefers to
168 				// sort by length first. This sometimes produces a sort
169 				// we do not desire. On the other hand it may have been
170 				// created by us, and be sorted the way we want.
171 				//
172 				if (alreadySorted && i > 0
173 						&& TREE_CMP.compare(children[i - 1], children[i]) > 0)
174 					alreadySorted = false;
175 			}
176 			if (!alreadySorted)
177 				Arrays.sort(children, 0, subcnt, TREE_CMP);
178 		} else {
179 			// Leaf level trees have no children, only (file) entries.
180 			//
181 			children = NO_CHILDREN;
182 		}
183 		childCnt = subcnt;
184 	}
185 
186 	void write(final byte[] tmp, final OutputStream os) throws IOException {
187 		int ptr = tmp.length;
188 		tmp[--ptr] = '\n';
189 		ptr = RawParseUtils.formatBase10(tmp, ptr, childCnt);
190 		tmp[--ptr] = ' ';
191 		ptr = RawParseUtils.formatBase10(tmp, ptr, isValid() ? entrySpan : -1);
192 		tmp[--ptr] = 0;
193 
194 		os.write(encodedName);
195 		os.write(tmp, ptr, tmp.length - ptr);
196 		if (isValid()) {
197 			id.copyRawTo(tmp, 0);
198 			os.write(tmp, 0, Constants.OBJECT_ID_LENGTH);
199 		}
200 		for (int i = 0; i < childCnt; i++)
201 			children[i].write(tmp, os);
202 	}
203 
204 	/**
205 	 * Determine if this cache is currently valid.
206 	 * <p>
207 	 * A valid cache tree knows how many {@link DirCacheEntry} instances from
208 	 * the parent {@link DirCache} reside within this tree (recursively
209 	 * enumerated). It also knows the object id of the tree, as the tree should
210 	 * be readily available from the repository's object database.
211 	 *
212 	 * @return true if this tree is knows key details about itself; false if the
213 	 *         tree needs to be regenerated.
214 	 */
215 	public boolean isValid() {
216 		return id != null;
217 	}
218 
219 	/**
220 	 * Get the number of entries this tree spans within the DirCache.
221 	 * <p>
222 	 * If this tree is not valid (see {@link #isValid()}) this method's return
223 	 * value is always strictly negative (less than 0) but is otherwise an
224 	 * undefined result.
225 	 *
226 	 * @return total number of entries (recursively) contained within this tree.
227 	 */
228 	public int getEntrySpan() {
229 		return entrySpan;
230 	}
231 
232 	/**
233 	 * Get the number of cached subtrees contained within this tree.
234 	 *
235 	 * @return number of child trees available through this tree.
236 	 */
237 	public int getChildCount() {
238 		return childCnt;
239 	}
240 
241 	/**
242 	 * Get the i-th child cache tree.
243 	 *
244 	 * @param i
245 	 *            index of the child to obtain.
246 	 * @return the child tree.
247 	 */
248 	public DirCacheTree getChild(final int i) {
249 		return children[i];
250 	}
251 
252 	/**
253 	 * Get the tree's ObjectId.
254 	 * <p>
255 	 * If {@link #isValid()} returns false this method will return null.
256 	 *
257 	 * @return ObjectId of this tree or null.
258 	 * @since 4.3
259 	 */
260 	public ObjectId getObjectId() {
261 		return id;
262 	}
263 
264 	/**
265 	 * Get the tree's name within its parent.
266 	 * <p>
267 	 * This method is not very efficient and is primarily meant for debugging
268 	 * and final output generation. Applications should try to avoid calling it,
269 	 * and if invoked do so only once per interesting entry, where the name is
270 	 * absolutely required for correct function.
271 	 *
272 	 * @return name of the tree. This does not contain any '/' characters.
273 	 */
274 	public String getNameString() {
275 		final ByteBuffer bb = ByteBuffer.wrap(encodedName);
276 		return Constants.CHARSET.decode(bb).toString();
277 	}
278 
279 	/**
280 	 * Get the tree's path within the repository.
281 	 * <p>
282 	 * This method is not very efficient and is primarily meant for debugging
283 	 * and final output generation. Applications should try to avoid calling it,
284 	 * and if invoked do so only once per interesting entry, where the name is
285 	 * absolutely required for correct function.
286 	 *
287 	 * @return path of the tree, relative to the repository root. If this is not
288 	 *         the root tree the path ends with '/'. The root tree's path string
289 	 *         is the empty string ("").
290 	 */
291 	public String getPathString() {
292 		final StringBuilder r = new StringBuilder();
293 		appendName(r);
294 		return r.toString();
295 	}
296 
297 	/**
298 	 * Write (if necessary) this tree to the object store.
299 	 *
300 	 * @param cache
301 	 *            the complete cache from DirCache.
302 	 * @param cIdx
303 	 *            first position of <code>cache</code> that is a member of this
304 	 *            tree. The path of <code>cache[cacheIdx].path</code> for the
305 	 *            range <code>[0,pathOff-1)</code> matches the complete path of
306 	 *            this tree, from the root of the repository.
307 	 * @param pathOffset
308 	 *            number of bytes of <code>cache[cacheIdx].path</code> that
309 	 *            matches this tree's path. The value at array position
310 	 *            <code>cache[cacheIdx].path[pathOff-1]</code> is always '/' if
311 	 *            <code>pathOff</code> is > 0.
312 	 * @param ow
313 	 *            the writer to use when serializing to the store.
314 	 * @return identity of this tree.
315 	 * @throws UnmergedPathException
316 	 *             one or more paths contain higher-order stages (stage > 0),
317 	 *             which cannot be stored in a tree object.
318 	 * @throws IOException
319 	 *             an unexpected error occurred writing to the object store.
320 	 */
321 	ObjectId writeTree(final DirCacheEntry[] cache, int cIdx,
322 			final int pathOffset, final ObjectInserter ow)
323 			throws UnmergedPathException, IOException {
324 		if (id == null) {
325 			final int endIdx = cIdx + entrySpan;
326 			final TreeFormatter fmt = new TreeFormatter(computeSize(cache,
327 					cIdx, pathOffset, ow));
328 			int childIdx = 0;
329 			int entryIdx = cIdx;
330 
331 			while (entryIdx < endIdx) {
332 				final DirCacheEntry e = cache[entryIdx];
333 				final byte[] ep = e.path;
334 				if (childIdx < childCnt) {
335 					final DirCacheTree st = children[childIdx];
336 					if (st.contains(ep, pathOffset, ep.length)) {
337 						fmt.append(st.encodedName, TREE, st.id);
338 						entryIdx += st.entrySpan;
339 						childIdx++;
340 						continue;
341 					}
342 				}
343 
344 				fmt.append(ep, pathOffset, ep.length - pathOffset, e
345 						.getFileMode(), e.idBuffer(), e.idOffset());
346 				entryIdx++;
347 			}
348 
349 			id = ow.insert(fmt);
350 		}
351 		return id;
352 	}
353 
354 	private int computeSize(final DirCacheEntry[] cache, int cIdx,
355 			final int pathOffset, final ObjectInserter ow)
356 			throws UnmergedPathException, IOException {
357 		final int endIdx = cIdx + entrySpan;
358 		int childIdx = 0;
359 		int entryIdx = cIdx;
360 		int size = 0;
361 
362 		while (entryIdx < endIdx) {
363 			final DirCacheEntry e = cache[entryIdx];
364 			if (e.getStage() != 0)
365 				throw new UnmergedPathException(e);
366 
367 			final byte[] ep = e.path;
368 			if (childIdx < childCnt) {
369 				final DirCacheTree st = children[childIdx];
370 				if (st.contains(ep, pathOffset, ep.length)) {
371 					final int stOffset = pathOffset + st.nameLength() + 1;
372 					st.writeTree(cache, entryIdx, stOffset, ow);
373 
374 					size += entrySize(TREE, st.nameLength());
375 
376 					entryIdx += st.entrySpan;
377 					childIdx++;
378 					continue;
379 				}
380 			}
381 
382 			size += entrySize(e.getFileMode(), ep.length - pathOffset);
383 			entryIdx++;
384 		}
385 
386 		return size;
387 	}
388 
389 	private void appendName(final StringBuilder r) {
390 		if (parent != null) {
391 			parent.appendName(r);
392 			r.append(getNameString());
393 			r.append('/');
394 		} else if (nameLength() > 0) {
395 			r.append(getNameString());
396 			r.append('/');
397 		}
398 	}
399 
400 	final int nameLength() {
401 		return encodedName.length;
402 	}
403 
404 	final boolean contains(final byte[] a, int aOff, final int aLen) {
405 		final byte[] e = encodedName;
406 		final int eLen = e.length;
407 		for (int eOff = 0; eOff < eLen && aOff < aLen; eOff++, aOff++)
408 			if (e[eOff] != a[aOff])
409 				return false;
410 		if (aOff >= aLen)
411 			return false;
412 		return a[aOff] == '/';
413 	}
414 
415 	/**
416 	 * Update (if necessary) this tree's entrySpan.
417 	 *
418 	 * @param cache
419 	 *            the complete cache from DirCache.
420 	 * @param cCnt
421 	 *            number of entries in <code>cache</code> that are valid for
422 	 *            iteration.
423 	 * @param cIdx
424 	 *            first position of <code>cache</code> that is a member of this
425 	 *            tree. The path of <code>cache[cacheIdx].path</code> for the
426 	 *            range <code>[0,pathOff-1)</code> matches the complete path of
427 	 *            this tree, from the root of the repository.
428 	 * @param pathOff
429 	 *            number of bytes of <code>cache[cacheIdx].path</code> that
430 	 *            matches this tree's path. The value at array position
431 	 *            <code>cache[cacheIdx].path[pathOff-1]</code> is always '/' if
432 	 *            <code>pathOff</code> is > 0.
433 	 */
434 	void validate(final DirCacheEntry[] cache, final int cCnt, int cIdx,
435 			final int pathOff) {
436 		if (entrySpan >= 0 && cIdx + entrySpan <= cCnt) {
437 			// If we are valid, our children are also valid.
438 			// We have no need to validate them.
439 			//
440 			return;
441 		}
442 
443 		entrySpan = 0;
444 		if (cCnt == 0) {
445 			// Special case of an empty index, and we are the root tree.
446 			//
447 			return;
448 		}
449 
450 		final byte[] firstPath = cache[cIdx].path;
451 		int stIdx = 0;
452 		while (cIdx < cCnt) {
453 			final byte[] currPath = cache[cIdx].path;
454 			if (pathOff > 0 && !peq(firstPath, currPath, pathOff)) {
455 				// The current entry is no longer in this tree. Our
456 				// span is updated and the remainder goes elsewhere.
457 				//
458 				break;
459 			}
460 
461 			DirCacheTree st = stIdx < childCnt ? children[stIdx] : null;
462 			final int cc = namecmp(currPath, pathOff, st);
463 			if (cc > 0) {
464 				// This subtree is now empty.
465 				//
466 				removeChild(stIdx);
467 				continue;
468 			}
469 
470 			if (cc < 0) {
471 				final int p = slash(currPath, pathOff);
472 				if (p < 0) {
473 					// The entry has no '/' and thus is directly in this
474 					// tree. Count it as one of our own.
475 					//
476 					cIdx++;
477 					entrySpan++;
478 					continue;
479 				}
480 
481 				// Build a new subtree for this entry.
482 				//
483 				st = new DirCacheTree(this, currPath, pathOff, p - pathOff);
484 				insertChild(stIdx, st);
485 			}
486 
487 			// The entry is contained in this subtree.
488 			//
489 			assert(st != null);
490 			st.validate(cache, cCnt, cIdx, pathOff + st.nameLength() + 1);
491 			cIdx += st.entrySpan;
492 			entrySpan += st.entrySpan;
493 			stIdx++;
494 		}
495 
496 		// None of our remaining children can be in this tree
497 		// as the current cache entry is after our own name.
498 		//
499 		while (stIdx < childCnt)
500 			removeChild(childCnt - 1);
501 	}
502 
503 	private void insertChild(final int stIdx, final DirCacheTree st) {
504 		final DirCacheTree[] c = children;
505 		if (childCnt + 1 <= c.length) {
506 			if (stIdx < childCnt)
507 				System.arraycopy(c, stIdx, c, stIdx + 1, childCnt - stIdx);
508 			c[stIdx] = st;
509 			childCnt++;
510 			return;
511 		}
512 
513 		final int n = c.length;
514 		final DirCacheTree[] a = new DirCacheTree[n + 1];
515 		if (stIdx > 0)
516 			System.arraycopy(c, 0, a, 0, stIdx);
517 		a[stIdx] = st;
518 		if (stIdx < n)
519 			System.arraycopy(c, stIdx, a, stIdx + 1, n - stIdx);
520 		children = a;
521 		childCnt++;
522 	}
523 
524 	private void removeChild(final int stIdx) {
525 		final int n = --childCnt;
526 		if (stIdx < n)
527 			System.arraycopy(children, stIdx + 1, children, stIdx, n - stIdx);
528 		children[n] = null;
529 	}
530 
531 	static boolean peq(final byte[] a, final byte[] b, int aLen) {
532 		if (b.length < aLen)
533 			return false;
534 		for (aLen--; aLen >= 0; aLen--)
535 			if (a[aLen] != b[aLen])
536 				return false;
537 		return true;
538 	}
539 
540 	private static int namecmp(final byte[] a, int aPos, final DirCacheTree ct) {
541 		if (ct == null)
542 			return -1;
543 		final byte[] b = ct.encodedName;
544 		final int aLen = a.length;
545 		final int bLen = b.length;
546 		int bPos = 0;
547 		for (; aPos < aLen && bPos < bLen; aPos++, bPos++) {
548 			final int cmp = (a[aPos] & 0xff) - (b[bPos] & 0xff);
549 			if (cmp != 0)
550 				return cmp;
551 		}
552 		if (bPos == bLen)
553 			return a[aPos] == '/' ? 0 : -1;
554 		return aLen - bLen;
555 	}
556 
557 	private static int slash(final byte[] a, int aPos) {
558 		final int aLen = a.length;
559 		for (; aPos < aLen; aPos++)
560 			if (a[aPos] == '/')
561 				return aPos;
562 		return -1;
563 	}
564 
565 	@Override
566 	public String toString() {
567 		return getNameString();
568 	}
569 }