PackBitmapIndexV1.java

  1. /*
  2.  * Copyright (C) 2012, Google Inc. and others
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */

  10. package org.eclipse.jgit.internal.storage.file;

  11. import java.io.DataInput;
  12. import java.io.IOException;
  13. import java.io.InputStream;
  14. import java.text.MessageFormat;
  15. import java.util.ArrayList;
  16. import java.util.Arrays;
  17. import java.util.List;
  18. import java.util.concurrent.ExecutionException;
  19. import java.util.concurrent.ExecutorService;
  20. import java.util.concurrent.Executors;
  21. import java.util.concurrent.Future;
  22. import java.util.concurrent.ThreadFactory;
  23. import java.util.concurrent.atomic.AtomicInteger;

  24. import org.eclipse.jgit.annotations.Nullable;
  25. import org.eclipse.jgit.internal.JGitText;
  26. import org.eclipse.jgit.lib.AnyObjectId;
  27. import org.eclipse.jgit.lib.Constants;
  28. import org.eclipse.jgit.lib.ObjectId;
  29. import org.eclipse.jgit.lib.ObjectIdOwnerMap;
  30. import org.eclipse.jgit.util.IO;
  31. import org.eclipse.jgit.util.NB;

  32. import com.googlecode.javaewah.EWAHCompressedBitmap;

  33. /**
  34.  * Support for the pack bitmap index v1 format.
  35.  *
  36.  * @see PackBitmapIndex
  37.  */
  38. class PackBitmapIndexV1 extends BasePackBitmapIndex {
  39.     static final byte[] MAGIC = { 'B', 'I', 'T', 'M' };
  40.     static final int OPT_FULL = 1;

  41.     private static final int MAX_XOR_OFFSET = 126;

  42.     private static final ExecutorService executor = Executors
  43.             .newCachedThreadPool(new ThreadFactory() {
  44.                 private final ThreadFactory baseFactory = Executors
  45.                         .defaultThreadFactory();

  46.                 private final AtomicInteger threadNumber = new AtomicInteger(0);

  47.                 @Override
  48.                 public Thread newThread(Runnable runnable) {
  49.                     Thread thread = baseFactory.newThread(runnable);
  50.                     thread.setName("JGit-PackBitmapIndexV1-" //$NON-NLS-1$
  51.                             + threadNumber.getAndIncrement());
  52.                     thread.setDaemon(true);
  53.                     return thread;
  54.                 }
  55.             });

  56.     private final PackIndex packIndex;
  57.     private final PackReverseIndex reverseIndex;
  58.     private final EWAHCompressedBitmap commits;
  59.     private final EWAHCompressedBitmap trees;
  60.     private final EWAHCompressedBitmap blobs;
  61.     private final EWAHCompressedBitmap tags;

  62.     private final ObjectIdOwnerMap<StoredBitmap> bitmaps;

  63.     PackBitmapIndexV1(final InputStream fd, PackIndex packIndex,
  64.             PackReverseIndex reverseIndex) throws IOException {
  65.         this(fd, () -> packIndex, () -> reverseIndex, false);
  66.     }

  67.     PackBitmapIndexV1(final InputStream fd,
  68.             SupplierWithIOException<PackIndex> packIndexSupplier,
  69.             SupplierWithIOException<PackReverseIndex> reverseIndexSupplier,
  70.             boolean loadParallelRevIndex)
  71.             throws IOException {
  72.         // An entry is object id, xor offset, flag byte, and a length encoded
  73.         // bitmap. The object id is an int32 of the nth position sorted by name.
  74.         super(new ObjectIdOwnerMap<StoredBitmap>());
  75.         this.bitmaps = getBitmaps();

  76.         // Optionally start loading reverse index in parallel to loading bitmap
  77.         // from storage.
  78.         Future<PackReverseIndex> reverseIndexFuture = null;
  79.         if (loadParallelRevIndex) {
  80.             reverseIndexFuture = executor.submit(reverseIndexSupplier::get);
  81.         }

  82.         final byte[] scratch = new byte[32];
  83.         IO.readFully(fd, scratch, 0, scratch.length);

  84.         // Check the magic bytes
  85.         for (int i = 0; i < MAGIC.length; i++) {
  86.             if (scratch[i] != MAGIC[i]) {
  87.                 byte[] actual = new byte[MAGIC.length];
  88.                 System.arraycopy(scratch, 0, actual, 0, MAGIC.length);
  89.                 throw new IOException(MessageFormat.format(
  90.                         JGitText.get().expectedGot, Arrays.toString(MAGIC),
  91.                         Arrays.toString(actual)));
  92.             }
  93.         }

  94.         // Read the version (2 bytes)
  95.         final int version = NB.decodeUInt16(scratch, 4);
  96.         if (version != 1)
  97.             throw new IOException(MessageFormat.format(
  98.                     JGitText.get().unsupportedPackIndexVersion,
  99.                     Integer.valueOf(version)));

  100.         // Read the options (2 bytes)
  101.         final int opts = NB.decodeUInt16(scratch, 6);
  102.         if ((opts & OPT_FULL) == 0)
  103.             throw new IOException(MessageFormat.format(
  104.                     JGitText.get().expectedGot, Integer.valueOf(OPT_FULL),
  105.                     Integer.valueOf(opts)));

  106.         // Read the number of entries (1 int32)
  107.         long numEntries = NB.decodeUInt32(scratch, 8);
  108.         if (numEntries > Integer.MAX_VALUE)
  109.             throw new IOException(JGitText.get().indexFileIsTooLargeForJgit);

  110.         // Checksum applied on the bottom of the corresponding pack file.
  111.         this.packChecksum = new byte[20];
  112.         System.arraycopy(scratch, 12, packChecksum, 0, packChecksum.length);

  113.         // Read the bitmaps for the Git types
  114.         SimpleDataInput dataInput = new SimpleDataInput(fd);
  115.         this.commits = readBitmap(dataInput);
  116.         this.trees = readBitmap(dataInput);
  117.         this.blobs = readBitmap(dataInput);
  118.         this.tags = readBitmap(dataInput);

  119.         // Read full bitmap from storage first.
  120.         List<IdxPositionBitmap> idxPositionBitmapList = new ArrayList<>();
  121.         // The xor offset is a single byte offset back in the list of entries.
  122.         IdxPositionBitmap[] recentBitmaps = new IdxPositionBitmap[MAX_XOR_OFFSET];
  123.         for (int i = 0; i < (int) numEntries; i++) {
  124.             IO.readFully(fd, scratch, 0, 6);
  125.             int nthObjectId = NB.decodeInt32(scratch, 0);
  126.             int xorOffset = scratch[4];
  127.             int flags = scratch[5];
  128.             EWAHCompressedBitmap bitmap = readBitmap(dataInput);

  129.             if (nthObjectId < 0) {
  130.                 throw new IOException(MessageFormat.format(
  131.                         JGitText.get().invalidId, String.valueOf(nthObjectId)));
  132.             }
  133.             if (xorOffset < 0) {
  134.                 throw new IOException(MessageFormat.format(
  135.                         JGitText.get().invalidId, String.valueOf(xorOffset)));
  136.             }
  137.             if (xorOffset > MAX_XOR_OFFSET) {
  138.                 throw new IOException(MessageFormat.format(
  139.                         JGitText.get().expectedLessThanGot,
  140.                         String.valueOf(MAX_XOR_OFFSET),
  141.                         String.valueOf(xorOffset)));
  142.             }
  143.             if (xorOffset > i) {
  144.                 throw new IOException(MessageFormat.format(
  145.                         JGitText.get().expectedLessThanGot, String.valueOf(i),
  146.                         String.valueOf(xorOffset)));
  147.             }
  148.             IdxPositionBitmap xorIdxPositionBitmap = null;
  149.             if (xorOffset > 0) {
  150.                 int index = (i - xorOffset);
  151.                 xorIdxPositionBitmap = recentBitmaps[index
  152.                         % recentBitmaps.length];
  153.                 if (xorIdxPositionBitmap == null) {
  154.                     throw new IOException(MessageFormat.format(
  155.                             JGitText.get().invalidId,
  156.                             String.valueOf(xorOffset)));
  157.                 }
  158.             }
  159.             IdxPositionBitmap idxPositionBitmap = new IdxPositionBitmap(
  160.                     nthObjectId, xorIdxPositionBitmap, bitmap, flags);
  161.             idxPositionBitmapList.add(idxPositionBitmap);
  162.             recentBitmaps[i % recentBitmaps.length] = idxPositionBitmap;
  163.         }

  164.         this.packIndex = packIndexSupplier.get();
  165.         for (int i = 0; i < idxPositionBitmapList.size(); ++i) {
  166.             IdxPositionBitmap idxPositionBitmap = idxPositionBitmapList.get(i);
  167.             ObjectId objectId = packIndex
  168.                     .getObjectId(idxPositionBitmap.nthObjectId);
  169.             StoredBitmap sb = new StoredBitmap(objectId,
  170.                     idxPositionBitmap.bitmap,
  171.                     idxPositionBitmap.getXorStoredBitmap(),
  172.                     idxPositionBitmap.flags);
  173.             // Save the StoredBitmap for a possible future XorStoredBitmap
  174.             // reference.
  175.             idxPositionBitmap.sb = sb;
  176.             bitmaps.add(sb);
  177.         }

  178.         PackReverseIndex computedReverseIndex;
  179.         if (loadParallelRevIndex && reverseIndexFuture != null) {
  180.             try {
  181.                 computedReverseIndex = reverseIndexFuture.get();
  182.             } catch (InterruptedException | ExecutionException e) {
  183.                 // Fallback to loading reverse index through a supplier.
  184.                 computedReverseIndex = reverseIndexSupplier.get();
  185.             }
  186.         } else {
  187.             computedReverseIndex = reverseIndexSupplier.get();
  188.         }
  189.         this.reverseIndex = computedReverseIndex;
  190.     }

  191.     /** {@inheritDoc} */
  192.     @Override
  193.     public int findPosition(AnyObjectId objectId) {
  194.         long offset = packIndex.findOffset(objectId);
  195.         if (offset == -1)
  196.             return -1;
  197.         return reverseIndex.findPostion(offset);
  198.     }

  199.     /** {@inheritDoc} */
  200.     @Override
  201.     public ObjectId getObject(int position) throws IllegalArgumentException {
  202.         ObjectId objectId = reverseIndex.findObjectByPosition(position);
  203.         if (objectId == null)
  204.             throw new IllegalArgumentException();
  205.         return objectId;
  206.     }

  207.     /** {@inheritDoc} */
  208.     @Override
  209.     public int getObjectCount() {
  210.         return (int) packIndex.getObjectCount();
  211.     }

  212.     /** {@inheritDoc} */
  213.     @Override
  214.     public EWAHCompressedBitmap ofObjectType(
  215.             EWAHCompressedBitmap bitmap, int type) {
  216.         switch (type) {
  217.         case Constants.OBJ_BLOB:
  218.             return blobs.and(bitmap);
  219.         case Constants.OBJ_TREE:
  220.             return trees.and(bitmap);
  221.         case Constants.OBJ_COMMIT:
  222.             return commits.and(bitmap);
  223.         case Constants.OBJ_TAG:
  224.             return tags.and(bitmap);
  225.         }
  226.         throw new IllegalArgumentException();
  227.     }

  228.     /** {@inheritDoc} */
  229.     @Override
  230.     public int getBitmapCount() {
  231.         return bitmaps.size();
  232.     }

  233.     /** {@inheritDoc} */
  234.     @Override
  235.     public boolean equals(Object o) {
  236.         // TODO(cranger): compare the pack checksum?
  237.         if (o instanceof PackBitmapIndexV1)
  238.             return getPackIndex() == ((PackBitmapIndexV1) o).getPackIndex();
  239.         return false;
  240.     }

  241.     /** {@inheritDoc} */
  242.     @Override
  243.     public int hashCode() {
  244.         return getPackIndex().hashCode();
  245.     }

  246.     PackIndex getPackIndex() {
  247.         return packIndex;
  248.     }

  249.     private static EWAHCompressedBitmap readBitmap(DataInput dataInput)
  250.             throws IOException {
  251.         EWAHCompressedBitmap bitmap = new EWAHCompressedBitmap();
  252.         bitmap.deserialize(dataInput);
  253.         return bitmap;
  254.     }

  255.     /**
  256.      * Temporary holder of object position in pack index and other metadata for
  257.      * {@code StoredBitmap}.
  258.      */
  259.     private static final class IdxPositionBitmap {
  260.         int nthObjectId;

  261.         IdxPositionBitmap xorIdxPositionBitmap;

  262.         EWAHCompressedBitmap bitmap;

  263.         int flags;

  264.         StoredBitmap sb;

  265.         IdxPositionBitmap(int nthObjectId,
  266.                 @Nullable IdxPositionBitmap xorIdxPositionBitmap,
  267.                 EWAHCompressedBitmap bitmap, int flags) {
  268.             this.nthObjectId = nthObjectId;
  269.             this.xorIdxPositionBitmap = xorIdxPositionBitmap;
  270.             this.bitmap = bitmap;
  271.             this.flags = flags;
  272.         }

  273.         StoredBitmap getXorStoredBitmap() {
  274.             return xorIdxPositionBitmap == null ? null
  275.                     : xorIdxPositionBitmap.sb;
  276.         }
  277.     }
  278. }