123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560 |
- /*
- * Copyright (C) 2008-2009, Google Inc.
- * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following
- * disclaimer in the documentation and/or other materials provided
- * with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- * names of its contributors may be used to endorse or promote
- * products derived from this software without specific prior
- * written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
- package org.eclipse.jgit.dircache;
-
- import static org.eclipse.jgit.lib.FileMode.TREE;
- import static org.eclipse.jgit.lib.TreeFormatter.entrySize;
-
- import java.io.IOException;
- import java.io.OutputStream;
- import java.nio.ByteBuffer;
- import java.util.Arrays;
- import java.util.Comparator;
-
- import org.eclipse.jgit.errors.UnmergedPathException;
- import org.eclipse.jgit.lib.Constants;
- import org.eclipse.jgit.lib.ObjectId;
- import org.eclipse.jgit.lib.ObjectInserter;
- import org.eclipse.jgit.lib.TreeFormatter;
- import org.eclipse.jgit.util.MutableInteger;
- import org.eclipse.jgit.util.RawParseUtils;
-
- /**
- * Single tree record from the 'TREE' {@link DirCache} extension.
- * <p>
- * A valid cache tree record contains the object id of a tree object and the
- * total number of {@link DirCacheEntry} instances (counted recursively) from
- * the DirCache contained within the tree. This information facilitates faster
- * traversal of the index and quicker generation of tree objects prior to
- * creating a new commit.
- * <p>
- * An invalid cache tree record indicates a known subtree whose file entries
- * have changed in ways that cause the tree to no longer have a known object id.
- * Invalid cache tree records must be revalidated prior to use.
- */
- public class DirCacheTree {
- private static final byte[] NO_NAME = {};
-
- private static final DirCacheTree[] NO_CHILDREN = {};
-
- private static final Comparator<DirCacheTree> TREE_CMP = new Comparator<DirCacheTree>() {
- public int compare(final DirCacheTree o1, final DirCacheTree o2) {
- final byte[] a = o1.encodedName;
- final byte[] b = o2.encodedName;
- final int aLen = a.length;
- final int bLen = b.length;
- int cPos;
- for (cPos = 0; cPos < aLen && cPos < bLen; cPos++) {
- final int cmp = (a[cPos] & 0xff) - (b[cPos] & 0xff);
- if (cmp != 0)
- return cmp;
- }
- if (aLen == bLen)
- return 0;
- if (aLen < bLen)
- return '/' - (b[cPos] & 0xff);
- return (a[cPos] & 0xff) - '/';
- }
- };
-
- /** Tree this tree resides in; null if we are the root. */
- private DirCacheTree parent;
-
- /** Name of this tree within its parent. */
- private byte[] encodedName;
-
- /** Number of {@link DirCacheEntry} records that belong to this tree. */
- private int entrySpan;
-
- /** Unique SHA-1 of this tree; null if invalid. */
- private ObjectId id;
-
- /** Child trees, if any, sorted by {@link #encodedName}. */
- private DirCacheTree[] children;
-
- /** Number of valid children in {@link #children}. */
- private int childCnt;
-
- DirCacheTree() {
- encodedName = NO_NAME;
- children = NO_CHILDREN;
- childCnt = 0;
- entrySpan = -1;
- }
-
- private DirCacheTree(final DirCacheTree myParent, final byte[] path,
- final int pathOff, final int pathLen) {
- parent = myParent;
- encodedName = new byte[pathLen];
- System.arraycopy(path, pathOff, encodedName, 0, pathLen);
- children = NO_CHILDREN;
- childCnt = 0;
- entrySpan = -1;
- }
-
- DirCacheTree(final byte[] in, final MutableInteger off,
- final DirCacheTree myParent) {
- parent = myParent;
-
- int ptr = RawParseUtils.next(in, off.value, '\0');
- final int nameLen = ptr - off.value - 1;
- if (nameLen > 0) {
- encodedName = new byte[nameLen];
- System.arraycopy(in, off.value, encodedName, 0, nameLen);
- } else
- encodedName = NO_NAME;
-
- entrySpan = RawParseUtils.parseBase10(in, ptr, off);
- final int subcnt = RawParseUtils.parseBase10(in, off.value, off);
- off.value = RawParseUtils.next(in, off.value, '\n');
-
- if (entrySpan >= 0) {
- // Valid trees have a positive entry count and an id of a
- // tree object that should exist in the object database.
- //
- id = ObjectId.fromRaw(in, off.value);
- off.value += Constants.OBJECT_ID_LENGTH;
- }
-
- if (subcnt > 0) {
- boolean alreadySorted = true;
- children = new DirCacheTree[subcnt];
- for (int i = 0; i < subcnt; i++) {
- children[i] = new DirCacheTree(in, off, this);
-
- // C Git's ordering differs from our own; it prefers to
- // sort by length first. This sometimes produces a sort
- // we do not desire. On the other hand it may have been
- // created by us, and be sorted the way we want.
- //
- if (alreadySorted && i > 0
- && TREE_CMP.compare(children[i - 1], children[i]) > 0)
- alreadySorted = false;
- }
- if (!alreadySorted)
- Arrays.sort(children, 0, subcnt, TREE_CMP);
- } else {
- // Leaf level trees have no children, only (file) entries.
- //
- children = NO_CHILDREN;
- }
- childCnt = subcnt;
- }
-
- void write(final byte[] tmp, final OutputStream os) throws IOException {
- int ptr = tmp.length;
- tmp[--ptr] = '\n';
- ptr = RawParseUtils.formatBase10(tmp, ptr, childCnt);
- tmp[--ptr] = ' ';
- ptr = RawParseUtils.formatBase10(tmp, ptr, isValid() ? entrySpan : -1);
- tmp[--ptr] = 0;
-
- os.write(encodedName);
- os.write(tmp, ptr, tmp.length - ptr);
- if (isValid()) {
- id.copyRawTo(tmp, 0);
- os.write(tmp, 0, Constants.OBJECT_ID_LENGTH);
- }
- for (int i = 0; i < childCnt; i++)
- children[i].write(tmp, os);
- }
-
- /**
- * Determine if this cache is currently valid.
- * <p>
- * A valid cache tree knows how many {@link DirCacheEntry} instances from
- * the parent {@link DirCache} reside within this tree (recursively
- * enumerated). It also knows the object id of the tree, as the tree should
- * be readily available from the repository's object database.
- *
- * @return true if this tree is knows key details about itself; false if the
- * tree needs to be regenerated.
- */
- public boolean isValid() {
- return id != null;
- }
-
- /**
- * Get the number of entries this tree spans within the DirCache.
- * <p>
- * If this tree is not valid (see {@link #isValid()}) this method's return
- * value is always strictly negative (less than 0) but is otherwise an
- * undefined result.
- *
- * @return total number of entries (recursively) contained within this tree.
- */
- public int getEntrySpan() {
- return entrySpan;
- }
-
- /**
- * Get the number of cached subtrees contained within this tree.
- *
- * @return number of child trees available through this tree.
- */
- public int getChildCount() {
- return childCnt;
- }
-
- /**
- * Get the i-th child cache tree.
- *
- * @param i
- * index of the child to obtain.
- * @return the child tree.
- */
- public DirCacheTree getChild(final int i) {
- return children[i];
- }
-
- ObjectId getObjectId() {
- return id;
- }
-
- /**
- * Get the tree's name within its parent.
- * <p>
- * This method is not very efficient and is primarily meant for debugging
- * and final output generation. Applications should try to avoid calling it,
- * and if invoked do so only once per interesting entry, where the name is
- * absolutely required for correct function.
- *
- * @return name of the tree. This does not contain any '/' characters.
- */
- public String getNameString() {
- final ByteBuffer bb = ByteBuffer.wrap(encodedName);
- return Constants.CHARSET.decode(bb).toString();
- }
-
- /**
- * Get the tree's path within the repository.
- * <p>
- * This method is not very efficient and is primarily meant for debugging
- * and final output generation. Applications should try to avoid calling it,
- * and if invoked do so only once per interesting entry, where the name is
- * absolutely required for correct function.
- *
- * @return path of the tree, relative to the repository root. If this is not
- * the root tree the path ends with '/'. The root tree's path string
- * is the empty string ("").
- */
- public String getPathString() {
- final StringBuilder r = new StringBuilder();
- appendName(r);
- return r.toString();
- }
-
- /**
- * Write (if necessary) this tree to the object store.
- *
- * @param cache
- * the complete cache from DirCache.
- * @param cIdx
- * first position of <code>cache</code> that is a member of this
- * tree. The path of <code>cache[cacheIdx].path</code> for the
- * range <code>[0,pathOff-1)</code> matches the complete path of
- * this tree, from the root of the repository.
- * @param pathOffset
- * number of bytes of <code>cache[cacheIdx].path</code> that
- * matches this tree's path. The value at array position
- * <code>cache[cacheIdx].path[pathOff-1]</code> is always '/' if
- * <code>pathOff</code> is > 0.
- * @param ow
- * the writer to use when serializing to the store.
- * @return identity of this tree.
- * @throws UnmergedPathException
- * one or more paths contain higher-order stages (stage > 0),
- * which cannot be stored in a tree object.
- * @throws IOException
- * an unexpected error occurred writing to the object store.
- */
- ObjectId writeTree(final DirCacheEntry[] cache, int cIdx,
- final int pathOffset, final ObjectInserter ow)
- throws UnmergedPathException, IOException {
- if (id == null) {
- final int endIdx = cIdx + entrySpan;
- final TreeFormatter fmt = new TreeFormatter(computeSize(cache,
- cIdx, pathOffset, ow));
- int childIdx = 0;
- int entryIdx = cIdx;
-
- while (entryIdx < endIdx) {
- final DirCacheEntry e = cache[entryIdx];
- final byte[] ep = e.path;
- if (childIdx < childCnt) {
- final DirCacheTree st = children[childIdx];
- if (st.contains(ep, pathOffset, ep.length)) {
- fmt.append(st.encodedName, TREE, st.id);
- entryIdx += st.entrySpan;
- childIdx++;
- continue;
- }
- }
-
- fmt.append(ep, pathOffset, ep.length - pathOffset, e
- .getFileMode(), e.idBuffer(), e.idOffset());
- entryIdx++;
- }
-
- id = ow.insert(fmt);
- }
- return id;
- }
-
- private int computeSize(final DirCacheEntry[] cache, int cIdx,
- final int pathOffset, final ObjectInserter ow)
- throws UnmergedPathException, IOException {
- final int endIdx = cIdx + entrySpan;
- int childIdx = 0;
- int entryIdx = cIdx;
- int size = 0;
-
- while (entryIdx < endIdx) {
- final DirCacheEntry e = cache[entryIdx];
- if (e.getStage() != 0)
- throw new UnmergedPathException(e);
-
- final byte[] ep = e.path;
- if (childIdx < childCnt) {
- final DirCacheTree st = children[childIdx];
- if (st.contains(ep, pathOffset, ep.length)) {
- final int stOffset = pathOffset + st.nameLength() + 1;
- st.writeTree(cache, entryIdx, stOffset, ow);
-
- size += entrySize(TREE, st.nameLength());
-
- entryIdx += st.entrySpan;
- childIdx++;
- continue;
- }
- }
-
- size += entrySize(e.getFileMode(), ep.length - pathOffset);
- entryIdx++;
- }
-
- return size;
- }
-
- private void appendName(final StringBuilder r) {
- if (parent != null) {
- parent.appendName(r);
- r.append(getNameString());
- r.append('/');
- } else if (nameLength() > 0) {
- r.append(getNameString());
- r.append('/');
- }
- }
-
- final int nameLength() {
- return encodedName.length;
- }
-
- final boolean contains(final byte[] a, int aOff, final int aLen) {
- final byte[] e = encodedName;
- final int eLen = e.length;
- for (int eOff = 0; eOff < eLen && aOff < aLen; eOff++, aOff++)
- if (e[eOff] != a[aOff])
- return false;
- if (aOff == aLen)
- return false;
- return a[aOff] == '/';
- }
-
- /**
- * Update (if necessary) this tree's entrySpan.
- *
- * @param cache
- * the complete cache from DirCache.
- * @param cCnt
- * number of entries in <code>cache</code> that are valid for
- * iteration.
- * @param cIdx
- * first position of <code>cache</code> that is a member of this
- * tree. The path of <code>cache[cacheIdx].path</code> for the
- * range <code>[0,pathOff-1)</code> matches the complete path of
- * this tree, from the root of the repository.
- * @param pathOff
- * number of bytes of <code>cache[cacheIdx].path</code> that
- * matches this tree's path. The value at array position
- * <code>cache[cacheIdx].path[pathOff-1]</code> is always '/' if
- * <code>pathOff</code> is > 0.
- */
- void validate(final DirCacheEntry[] cache, final int cCnt, int cIdx,
- final int pathOff) {
- if (entrySpan >= 0 && cIdx + entrySpan <= cCnt) {
- // If we are valid, our children are also valid.
- // We have no need to validate them.
- //
- return;
- }
-
- entrySpan = 0;
- if (cCnt == 0) {
- // Special case of an empty index, and we are the root tree.
- //
- return;
- }
-
- final byte[] firstPath = cache[cIdx].path;
- int stIdx = 0;
- while (cIdx < cCnt) {
- final byte[] currPath = cache[cIdx].path;
- if (pathOff > 0 && !peq(firstPath, currPath, pathOff)) {
- // The current entry is no longer in this tree. Our
- // span is updated and the remainder goes elsewhere.
- //
- break;
- }
-
- DirCacheTree st = stIdx < childCnt ? children[stIdx] : null;
- final int cc = namecmp(currPath, pathOff, st);
- if (cc > 0) {
- // This subtree is now empty.
- //
- removeChild(stIdx);
- continue;
- }
-
- if (cc < 0) {
- final int p = slash(currPath, pathOff);
- if (p < 0) {
- // The entry has no '/' and thus is directly in this
- // tree. Count it as one of our own.
- //
- cIdx++;
- entrySpan++;
- continue;
- }
-
- // Build a new subtree for this entry.
- //
- st = new DirCacheTree(this, currPath, pathOff, p - pathOff);
- insertChild(stIdx, st);
- }
-
- // The entry is contained in this subtree.
- //
- st.validate(cache, cCnt, cIdx, pathOff + st.nameLength() + 1);
- cIdx += st.entrySpan;
- entrySpan += st.entrySpan;
- stIdx++;
- }
-
- // None of our remaining children can be in this tree
- // as the current cache entry is after our own name.
- //
- while (stIdx < childCnt)
- removeChild(childCnt - 1);
- }
-
- private void insertChild(final int stIdx, final DirCacheTree st) {
- final DirCacheTree[] c = children;
- if (childCnt + 1 <= c.length) {
- if (stIdx < childCnt)
- System.arraycopy(c, stIdx, c, stIdx + 1, childCnt - stIdx);
- c[stIdx] = st;
- childCnt++;
- return;
- }
-
- final int n = c.length;
- final DirCacheTree[] a = new DirCacheTree[n + 1];
- if (stIdx > 0)
- System.arraycopy(c, 0, a, 0, stIdx);
- a[stIdx] = st;
- if (stIdx < n)
- System.arraycopy(c, stIdx, a, stIdx + 1, n - stIdx);
- children = a;
- childCnt++;
- }
-
- private void removeChild(final int stIdx) {
- final int n = --childCnt;
- if (stIdx < n)
- System.arraycopy(children, stIdx + 1, children, stIdx, n - stIdx);
- children[n] = null;
- }
-
- static boolean peq(final byte[] a, final byte[] b, int aLen) {
- if (b.length < aLen)
- return false;
- for (aLen--; aLen >= 0; aLen--)
- if (a[aLen] != b[aLen])
- return false;
- return true;
- }
-
- private static int namecmp(final byte[] a, int aPos, final DirCacheTree ct) {
- if (ct == null)
- return -1;
- final byte[] b = ct.encodedName;
- final int aLen = a.length;
- final int bLen = b.length;
- int bPos = 0;
- for (; aPos < aLen && bPos < bLen; aPos++, bPos++) {
- final int cmp = (a[aPos] & 0xff) - (b[bPos] & 0xff);
- if (cmp != 0)
- return cmp;
- }
- if (bPos == bLen)
- return a[aPos] == '/' ? 0 : -1;
- return aLen - bLen;
- }
-
- private static int slash(final byte[] a, int aPos) {
- final int aLen = a.length;
- for (; aPos < aLen; aPos++)
- if (a[aPos] == '/')
- return aPos;
- return -1;
- }
-
- @Override
- public String toString() {
- return getNameString();
- }
- }
|