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