Package org.eclipse.jgit.storage.dht

Source Code of org.eclipse.jgit.storage.dht.PackChunk$Delta

/*
* Copyright (C) 2011, Google Inc.
* 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.storage.dht;

import static org.eclipse.jgit.lib.Constants.OBJ_BAD;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
import static org.eclipse.jgit.lib.Constants.OBJ_OFS_DELTA;
import static org.eclipse.jgit.lib.Constants.OBJ_REF_DELTA;
import static org.eclipse.jgit.lib.Constants.OBJ_TAG;
import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
import static org.eclipse.jgit.lib.Constants.newMessageDigest;
import static org.eclipse.jgit.storage.dht.ChunkFormatter.TRAILER_SIZE;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.text.MessageFormat;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;

import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.LargeObjectException;
import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException;
import org.eclipse.jgit.generated.storage.dht.proto.GitStore.ChunkMeta;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.storage.pack.BinaryDelta;
import org.eclipse.jgit.storage.pack.PackOutputStream;
import org.eclipse.jgit.transport.PackParser;

/**
* Chunk of object data, stored under a {@link ChunkKey}.
* <p>
* A chunk typically contains thousands of objects, compressed in the Git native
* pack file format. Its associated {@link ChunkIndex} provides offsets for each
* object's header and compressed data.
* <p>
* Chunks (and their indexes) are opaque binary blobs meant only to be read by
* the Git implementation.
*/
public final class PackChunk {
  /** Constructs a {@link PackChunk} while reading from the DHT. */
  public static class Members {
    private ChunkKey chunkKey;

    private byte[] dataBuf;

    private int dataPtr;

    private int dataLen;

    private byte[] indexBuf;

    private int indexPtr;

    private int indexLen;

    private ChunkMeta meta;

    /** @return the chunk key. Never null. */
    public ChunkKey getChunkKey() {
      return chunkKey;
    }

    /**
     * @param key
     * @return {@code this}
     */
    public Members setChunkKey(ChunkKey key) {
      this.chunkKey = key;
      return this;
    }

    /** @return true if there is chunk data present. */
    public boolean hasChunkData() {
      return dataBuf != null;
    }

    /** @return the chunk data, or null if not available. */
    public byte[] getChunkData() {
      return asArray(dataBuf, dataPtr, dataLen);
    }

    /** @return the chunk data, or null if not available. */
    public ByteBuffer getChunkDataAsByteBuffer() {
      return asByteBuffer(dataBuf, dataPtr, dataLen);
    }

    private static byte[] asArray(byte[] buf, int ptr, int len) {
      if (buf == null)
        return null;
      if (ptr == 0 && buf.length == len)
        return buf;
      byte[] r = new byte[len];
      System.arraycopy(buf, ptr, r, 0, len);
      return r;
    }

    private static ByteBuffer asByteBuffer(byte[] buf, int ptr, int len) {
      return buf != null ? ByteBuffer.wrap(buf, ptr, len) : null;
    }

    /**
     * @param chunkData
     * @return {@code this}
     */
    public Members setChunkData(byte[] chunkData) {
      return setChunkData(chunkData, 0, chunkData.length);
    }

    /**
     * @param chunkData
     * @param ptr
     * @param len
     * @return {@code this}
     */
    public Members setChunkData(byte[] chunkData, int ptr, int len) {
      this.dataBuf = chunkData;
      this.dataPtr = ptr;
      this.dataLen = len;
      return this;
    }

    /** @return true if there is a chunk index present. */
    public boolean hasChunkIndex() {
      return indexBuf != null;
    }

    /** @return the chunk index, or null if not available. */
    public byte[] getChunkIndex() {
      return asArray(indexBuf, indexPtr, indexLen);
    }

    /** @return the chunk index, or null if not available. */
    public ByteBuffer getChunkIndexAsByteBuffer() {
      return asByteBuffer(indexBuf, indexPtr, indexLen);
    }

    /**
     * @param chunkIndex
     * @return {@code this}
     */
    public Members setChunkIndex(byte[] chunkIndex) {
      return setChunkIndex(chunkIndex, 0, chunkIndex.length);
    }

    /**
     * @param chunkIndex
     * @param ptr
     * @param len
     * @return {@code this}
     */
    public Members setChunkIndex(byte[] chunkIndex, int ptr, int len) {
      this.indexBuf = chunkIndex;
      this.indexPtr = ptr;
      this.indexLen = len;
      return this;
    }

    /** @return true if there is meta information present. */
    public boolean hasMeta() {
      return meta != null;
    }

    /** @return the inline meta data, or null if not available. */
    public ChunkMeta getMeta() {
      return meta;
    }

    /**
     * @param meta
     * @return {@code this}
     */
    public Members setMeta(ChunkMeta meta) {
      this.meta = meta;
      return this;
    }

    /**
     * @return the PackChunk instance.
     * @throws DhtException
     *             if early validation indicates the chunk data is corrupt
     *             or not recognized by this version of the library.
     */
    public PackChunk build() throws DhtException {
      ChunkIndex i;
      if (indexBuf != null)
        i = ChunkIndex.fromBytes(chunkKey, indexBuf, indexPtr, indexLen);
      else
        i = null;

      return new PackChunk(chunkKey, dataBuf, dataPtr, dataLen, i, meta);
    }
  }

  private static final int INFLATE_STRIDE = 512;

  private final ChunkKey key;

  private final byte[] dataBuf;

  private final int dataPtr;

  private final int dataLen;

  private final ChunkIndex index;

  private final ChunkMeta meta;

  private volatile Boolean valid;

  PackChunk(ChunkKey key, byte[] dataBuf, int dataPtr, int dataLen,
      ChunkIndex index, ChunkMeta meta) {
    this.key = key;
    this.dataBuf = dataBuf;
    this.dataPtr = dataPtr;
    this.dataLen = dataLen;
    this.index = index;
    this.meta = meta;
  }

  /** @return unique name of this chunk in the database. */
  public ChunkKey getChunkKey() {
    return key;
  }

  /** @return index describing the objects stored within this chunk. */
  public ChunkIndex getIndex() {
    return index;
  }

  /** @return inline meta information, or null if no data was necessary. */
  public ChunkMeta getMeta() {
    return meta;
  }

  @Override
  public String toString() {
    return "PackChunk[" + getChunkKey() + "]";
  }

  boolean hasIndex() {
    return index != null;
  }

  boolean isFragment() {
    return meta != null && 0 < meta.getFragmentCount();
  }

  int findOffset(RepositoryKey repo, AnyObjectId objId) {
    if (key.getRepositoryId() == repo.asInt() && index != null)
      return index.findOffset(objId);
    return -1;
  }

  boolean contains(RepositoryKey repo, AnyObjectId objId) {
    return 0 <= findOffset(repo, objId);
  }

  static ObjectLoader read(PackChunk pc, int pos, final DhtReader ctx,
      final int typeHint) throws IOException {
    try {
      return read1(pc, pos, ctx, typeHint, true /* use recentChunks */);
    } catch (DeltaChainCycleException cycleFound) {
      // A cycle can occur if recentChunks cache was used by the reader
      // to satisfy an OBJ_REF_DELTA, but the chunk that was chosen has
      // a reverse delta back onto an object already being read during
      // this invocation. Its not as uncommon as it sounds, as the Git
      // wire protocol can sometimes copy an object the repository already
      // has when dealing with reverts or cherry-picks.
      //
      // Work around the cycle by disabling the recentChunks cache for
      // this resolution only. This will force the DhtReader to re-read
      // OBJECT_INDEX and consider only the oldest chunk for any given
      // object. There cannot be a cycle if the method only walks along
      // the oldest chunks.
      try {
        ctx.getStatistics().deltaChainCycles++;
        return read1(pc, pos, ctx, typeHint, false /* no recentChunks */);
      } catch (DeltaChainCycleException cannotRecover) {
        throw new DhtException(MessageFormat.format(
            DhtText.get().cycleInDeltaChain, pc.getChunkKey(),
            Integer.valueOf(pos)));
      }
    }
  }

  @SuppressWarnings("null")
  private static ObjectLoader read1(PackChunk pc, int pos,
      final DhtReader ctx, final int typeHint, final boolean recent)
      throws IOException, DeltaChainCycleException {
    try {
      Delta delta = null;
      byte[] data = null;
      int type = OBJ_BAD;
      boolean cached = false;

      SEARCH: for (;;) {
        final byte[] dataBuf = pc.dataBuf;
        final int dataPtr = pc.dataPtr;
        final int posPtr = dataPtr + pos;
        int c = dataBuf[posPtr] & 0xff;
        int typeCode = (c >> 4) & 7;
        long sz = c & 15;
        int shift = 4;
        int p = 1;
        while ((c & 0x80) != 0) {
          c = dataBuf[posPtr + p++] & 0xff;
          sz += (c & 0x7f) << shift;
          shift += 7;
        }

        switch (typeCode) {
        case OBJ_COMMIT:
        case OBJ_TREE:
        case OBJ_BLOB:
        case OBJ_TAG: {
          if (delta != null) {
            data = inflate(sz, pc, pos + p, ctx);
            type = typeCode;
            break SEARCH;
          }

          if (sz < Integer.MAX_VALUE && !pc.isFragment()) {
            try {
              data = pc.inflateOne(sz, pos + p, ctx);
              return new ObjectLoader.SmallObject(typeCode, data);
            } catch (LargeObjectException tooBig) {
              // Fall through and stream.
            }
          }

          return new LargeNonDeltaObject(typeCode, sz, pc, pos + p, ctx);
        }

        case OBJ_OFS_DELTA: {
          c = dataBuf[posPtr + p++] & 0xff;
          long base = c & 127;
          while ((c & 128) != 0) {
            base += 1;
            c = dataBuf[posPtr + p++] & 0xff;
            base <<= 7;
            base += (c & 127);
          }

          ChunkKey baseChunkKey;
          int basePosInChunk;

          if (base <= pos) {
            // Base occurs in the same chunk, just earlier.
            baseChunkKey = pc.getChunkKey();
            basePosInChunk = pos - (int) base;
          } else {
            // Long offset delta, base occurs in another chunk.
            // Adjust distance to be from our chunk start.
            base = base - pos;

            ChunkMeta.BaseChunk baseChunk;
            baseChunk = ChunkMetaUtil.getBaseChunk(
                pc.key,
                pc.meta,
                base);
            baseChunkKey = ChunkKey.fromString(baseChunk.getChunkKey());
            basePosInChunk = (int) (baseChunk.getRelativeStart() - base);
          }

          delta = new Delta(delta, //
              pc.key, pos, (int) sz, p, //
              baseChunkKey, basePosInChunk);
          if (sz != delta.deltaSize)
            break SEARCH;

          DeltaBaseCache.Entry e = delta.getBase(ctx);
          if (e != null) {
            type = e.type;
            data = e.data;
            cached = true;
            break SEARCH;
          }
          if (baseChunkKey != pc.getChunkKey())
            pc = ctx.getChunk(baseChunkKey);
          pos = basePosInChunk;
          continue SEARCH;
        }

        case OBJ_REF_DELTA: {
          ObjectId id = ObjectId.fromRaw(dataBuf, posPtr + p);
          PackChunk nc = pc;
          int base = pc.index.findOffset(id);
          if (base < 0) {
            DhtReader.ChunkAndOffset n;
            n = ctx.getChunk(id, typeHint, recent);
            nc = n.chunk;
            base = n.offset;
          }
          checkCycle(delta, pc.key, pos);
          delta = new Delta(delta, //
              pc.key, pos, (int) sz, p + 20, //
              nc.getChunkKey(), base);
          if (sz != delta.deltaSize)
            break SEARCH;

          DeltaBaseCache.Entry e = delta.getBase(ctx);
          if (e != null) {
            type = e.type;
            data = e.data;
            cached = true;
            break SEARCH;
          }
          pc = nc;
          pos = base;
          continue SEARCH;
        }

        default:
          throw new DhtException(MessageFormat.format(
              DhtText.get().unsupportedObjectTypeInChunk, //
              Integer.valueOf(typeCode), //
              pc.getChunkKey(), //
              Integer.valueOf(pos)));
        }
      }

      // At this point there is at least one delta to apply to data.
      // (Whole objects with no deltas to apply return early above.)

      do {
        if (!delta.deltaChunk.equals(pc.getChunkKey()))
          pc = ctx.getChunk(delta.deltaChunk);
        pos = delta.deltaPos;

        // Cache only the base immediately before desired object.
        if (cached)
          cached = false;
        else if (delta.next == null)
          delta.putBase(ctx, type, data);

        final byte[] cmds = delta.decompress(pc, ctx);
        final long sz = BinaryDelta.getResultSize(cmds);
        final byte[] result = newResult(sz);
        BinaryDelta.apply(data, cmds, result);
        data = result;
        delta = delta.next;
      } while (delta != null);

      return new ObjectLoader.SmallObject(type, data);

    } catch (DataFormatException dfe) {
      CorruptObjectException coe = new CorruptObjectException(
          MessageFormat.format(DhtText.get().corruptCompressedObject,
              pc.getChunkKey(), Integer.valueOf(pos)));
      coe.initCause(dfe);
      throw coe;
    }
  }

  private static byte[] inflate(long sz, PackChunk pc, int pos,
      DhtReader reader) throws DataFormatException, DhtException {
    if (pc.isFragment())
      return inflateFragment(sz, pc, pos, reader);
    return pc.inflateOne(sz, pos, reader);
  }

  private byte[] inflateOne(long sz, int pos, DhtReader reader)
      throws DataFormatException {
    // Because the chunk ends in a 4 byte CRC, there is always
    // more data available for input than the inflater needs.
    // This also helps with an optimization in libz where it
    // wants at least 1 extra byte of input beyond the end.

    final byte[] dstbuf = newResult(sz);
    final Inflater inf = reader.inflater();
    final int offset = pos;
    int dstoff = 0;

    int bs = Math.min(dataLen - pos, INFLATE_STRIDE);
    inf.setInput(dataBuf, dataPtr + pos, bs);
    pos += bs;

    while (dstoff < dstbuf.length) {
      int n = inf.inflate(dstbuf, dstoff, dstbuf.length - dstoff);
      if (n == 0) {
        if (inf.needsInput()) {
          bs = Math.min(dataLen - pos, INFLATE_STRIDE);
          inf.setInput(dataBuf, dataPtr + pos, bs);
          pos += bs;
          continue;
        }
        break;
      }
      dstoff += n;
    }

    if (dstoff != sz) {
      throw new DataFormatException(MessageFormat.format(
          DhtText.get().shortCompressedObject,
          getChunkKey(),
          Integer.valueOf(offset)));
    }
    return dstbuf;
  }

  private static byte[] inflateFragment(long sz, PackChunk pc, final int pos,
      DhtReader reader) throws DataFormatException, DhtException {
    byte[] dstbuf = newResult(sz);
    int dstoff = 0;

    final Inflater inf = reader.inflater();
    final ChunkMeta meta = pc.meta;
    int nextChunk = 1;

    int bs = pc.dataLen - pos - TRAILER_SIZE;
    inf.setInput(pc.dataBuf, pc.dataPtr + pos, bs);

    while (dstoff < dstbuf.length) {
      int n = inf.inflate(dstbuf, dstoff, dstbuf.length - dstoff);
      if (n == 0) {
        if (inf.needsInput()) {
          if (meta.getFragmentCount() <= nextChunk)
            break;
          pc = reader.getChunk(ChunkKey.fromString(
              meta.getFragment(nextChunk++)));
          if (meta.getFragmentCount() == nextChunk)
            bs = pc.dataLen; // Include trailer on last chunk.
          else
            bs = pc.dataLen - TRAILER_SIZE;
          inf.setInput(pc.dataBuf, pc.dataPtr, bs);
          continue;
        }
        break;
      }
      dstoff += n;
    }

    if (dstoff != sz) {
      throw new DataFormatException(MessageFormat.format(
          DhtText.get().shortCompressedObject,
          ChunkKey.fromString(meta.getFragment(0)),
          Integer.valueOf(pos)));
    }
    return dstbuf;
  }

  private static byte[] newResult(long sz) {
    if (Integer.MAX_VALUE < sz)
      throw new LargeObjectException.ExceedsByteArrayLimit();
    try {
      return new byte[(int) sz];
    } catch (OutOfMemoryError noMemory) {
      throw new LargeObjectException.OutOfMemory(noMemory);
    }
  }

  int readObjectTypeAndSize(int ptr, PackParser.ObjectTypeAndSize info) {
    ptr += dataPtr;

    int c = dataBuf[ptr++] & 0xff;
    int typeCode = (c >> 4) & 7;
    long sz = c & 15;
    int shift = 4;
    while ((c & 0x80) != 0) {
      c = dataBuf[ptr++] & 0xff;
      sz += (c & 0x7f) << shift;
      shift += 7;
    }

    switch (typeCode) {
    case OBJ_OFS_DELTA:
      c = dataBuf[ptr++] & 0xff;
      while ((c & 128) != 0)
        c = dataBuf[ptr++] & 0xff;
      break;

    case OBJ_REF_DELTA:
      ptr += 20;
      break;
    }

    info.type = typeCode;
    info.size = sz;
    return ptr - dataPtr;
  }

  int read(int ptr, byte[] dst, int dstPos, int cnt) {
    // Do not allow readers to read the CRC-32 from the tail.
    int n = Math.min(cnt, (dataLen - TRAILER_SIZE) - ptr);
    System.arraycopy(dataBuf, dataPtr + ptr, dst, dstPos, n);
    return n;
  }

  void copyObjectAsIs(PackOutputStream out, DhtObjectToPack obj,
      boolean validate, DhtReader ctx) throws IOException,
      StoredObjectRepresentationNotAvailableException {
    if (validate && !isValid()) {
      StoredObjectRepresentationNotAvailableException gone;

      gone = new StoredObjectRepresentationNotAvailableException(obj);
      gone.initCause(new DhtException(MessageFormat.format(
          DhtText.get().corruptChunk, getChunkKey())));
      throw gone;
    }

    int ptr = dataPtr + obj.offset;
    int c = dataBuf[ptr++] & 0xff;
    int typeCode = (c >> 4) & 7;
    long inflatedSize = c & 15;
    int shift = 4;
    while ((c & 0x80) != 0) {
      c = dataBuf[ptr++] & 0xff;
      inflatedSize += (c & 0x7f) << shift;
      shift += 7;
    }

    switch (typeCode) {
    case OBJ_OFS_DELTA:
      do {
        c = dataBuf[ptr++] & 0xff;
      } while ((c & 128) != 0);
      break;

    case OBJ_REF_DELTA:
      ptr += 20;
      break;
    }

    // If the size is positive, its accurate. If its -1, this is a
    // fragmented object that will need more handling below,
    // so copy all of the chunk, minus the trailer.

    final int maxAvail = (dataLen - TRAILER_SIZE) - (ptr - dataPtr);
    final int copyLen;
    if (0 < obj.size)
      copyLen = Math.min(obj.size, maxAvail);
    else if (-1 == obj.size)
      copyLen = maxAvail;
    else
      throw new DhtException(MessageFormat.format(
          DhtText.get().expectedObjectSizeDuringCopyAsIs, obj));
    out.writeHeader(obj, inflatedSize);
    out.write(dataBuf, ptr, copyLen);

    // If the object was fragmented, send all of the other fragments.
    if (isFragment()) {
      int cnt = meta.getFragmentCount();
      for (int fragId = 1; fragId < cnt; fragId++) {
        PackChunk pc = ctx.getChunk(ChunkKey.fromString(
            meta.getFragment(fragId)));
        pc.copyEntireChunkAsIs(out, obj, validate);
      }
    }
  }

  void copyEntireChunkAsIs(PackOutputStream out, DhtObjectToPack obj,
      boolean validate) throws IOException {
    if (validate && !isValid()) {
      if (obj != null)
        throw new CorruptObjectException(obj, MessageFormat.format(
            DhtText.get().corruptChunk, getChunkKey()));
      else
        throw new DhtException(MessageFormat.format(
            DhtText.get().corruptChunk, getChunkKey()));
    }

    // Do not copy the trailer onto the output stream.
    out.write(dataBuf, dataPtr, dataLen - TRAILER_SIZE);
  }

  @SuppressWarnings("boxing")
  private boolean isValid() {
    Boolean v = valid;
    if (v == null) {
      MessageDigest m = newMessageDigest();
      m.update(dataBuf, dataPtr, dataLen);
      v = key.getChunkHash().compareTo(m.digest(), 0) == 0;
      valid = v;
    }
    return v.booleanValue();
  }

  /** @return the complete size of this chunk, in memory. */
  int getTotalSize() {
    // Assume the index is part of the buffer, and report its total size..
    if (dataPtr != 0 || dataLen != dataBuf.length)
      return dataBuf.length;

    int sz = dataLen;
    if (index != null)
      sz += index.getIndexSize();
    return sz;
  }

  private static class Delta {
    /** Child that applies onto this object. */
    final Delta next;

    /** The chunk the delta is stored in. */
    final ChunkKey deltaChunk;

    /** Offset of the delta object. */
    final int deltaPos;

    /** Size of the inflated delta stream. */
    final int deltaSize;

    /** Total size of the delta's pack entry header (including base). */
    final int hdrLen;

    /** The chunk the base is stored in. */
    final ChunkKey baseChunk;

    /** Offset of the base object. */
    final int basePos;

    Delta(Delta next, ChunkKey dc, int ofs, int sz, int hdrLen,
        ChunkKey bc, int bp) {
      this.next = next;
      this.deltaChunk = dc;
      this.deltaPos = ofs;
      this.deltaSize = sz;
      this.hdrLen = hdrLen;
      this.baseChunk = bc;
      this.basePos = bp;
    }

    byte[] decompress(PackChunk chunk, DhtReader reader)
        throws DataFormatException, DhtException {
      return inflate(deltaSize, chunk, deltaPos + hdrLen, reader);
    }

    DeltaBaseCache.Entry getBase(DhtReader ctx) {
      return ctx.getDeltaBaseCache().get(baseChunk, basePos);
    }

    void putBase(DhtReader ctx, int type, byte[] data) {
      ctx.getDeltaBaseCache().put(baseChunk, basePos, type, data);
    }
  }

  private static void checkCycle(Delta delta, ChunkKey key, int ofs)
      throws DeltaChainCycleException {
    for (; delta != null; delta = delta.next) {
      if (delta.deltaPos == ofs && delta.deltaChunk.equals(key))
        throw DeltaChainCycleException.INSTANCE;
    }
  }

  private static class DeltaChainCycleException extends Exception {
    private static final long serialVersionUID = 1L;

    static final DeltaChainCycleException INSTANCE = new DeltaChainCycleException();
  }
}
TOP

Related Classes of org.eclipse.jgit.storage.dht.PackChunk$Delta

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.