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