BlockWriter.java

  1. /*
  2.  * Copyright (C) 2017, 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.reftable;

  11. import static java.nio.charset.StandardCharsets.UTF_8;
  12. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN;
  13. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.INDEX_BLOCK_TYPE;
  14. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_BLOCK_TYPE;
  15. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_DATA;
  16. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_NONE;
  17. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.MAX_RESTARTS;
  18. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.OBJ_BLOCK_TYPE;
  19. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.REF_BLOCK_TYPE;
  20. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_1ID;
  21. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_2ID;
  22. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_NONE;
  23. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_SYMREF;
  24. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_TYPE_MASK;
  25. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.reverseUpdateIndex;
  26. import static org.eclipse.jgit.internal.storage.reftable.ReftableOutputStream.computeVarintSize;
  27. import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
  28. import static org.eclipse.jgit.lib.Ref.Storage.NEW;

  29. import java.io.IOException;
  30. import java.util.ArrayList;
  31. import java.util.Arrays;
  32. import java.util.List;

  33. import org.eclipse.jgit.internal.JGitText;
  34. import org.eclipse.jgit.lib.ObjectId;
  35. import org.eclipse.jgit.lib.PersonIdent;
  36. import org.eclipse.jgit.lib.Ref;
  37. import org.eclipse.jgit.util.IntList;
  38. import org.eclipse.jgit.util.LongList;
  39. import org.eclipse.jgit.util.NB;

  40. /** Formats and writes blocks for {@link ReftableWriter}. */
  41. class BlockWriter {
  42.     private final byte blockType;
  43.     private final byte keyType;
  44.     private final List<Entry> entries;
  45.     private final int blockLimitBytes;
  46.     private final int restartInterval;

  47.     private int entriesSumBytes;
  48.     private int restartCnt;

  49.     BlockWriter(byte type, byte kt, int bs, int ri) {
  50.         blockType = type;
  51.         keyType = kt;
  52.         blockLimitBytes = bs;
  53.         restartInterval = ri;
  54.         entries = new ArrayList<>(estimateEntryCount(type, kt, bs));
  55.     }

  56.     private static int estimateEntryCount(byte blockType, byte keyType,
  57.             int blockLimitBytes) {
  58.         double avgBytesPerEntry;
  59.         switch (blockType) {
  60.         case REF_BLOCK_TYPE:
  61.         default:
  62.             avgBytesPerEntry = 35.31;
  63.             break;

  64.         case OBJ_BLOCK_TYPE:
  65.             avgBytesPerEntry = 4.19;
  66.             break;

  67.         case LOG_BLOCK_TYPE:
  68.             avgBytesPerEntry = 101.14;
  69.             break;

  70.         case INDEX_BLOCK_TYPE:
  71.             switch (keyType) {
  72.             case REF_BLOCK_TYPE:
  73.             case LOG_BLOCK_TYPE:
  74.             default:
  75.                 avgBytesPerEntry = 27.44;
  76.                 break;

  77.             case OBJ_BLOCK_TYPE:
  78.                 avgBytesPerEntry = 11.57;
  79.                 break;
  80.             }
  81.         }

  82.         int cnt = (int) (Math.ceil(blockLimitBytes / avgBytesPerEntry));
  83.         return Math.min(cnt, 4096);
  84.     }

  85.     byte blockType() {
  86.         return blockType;
  87.     }

  88.     boolean padBetweenBlocks() {
  89.         return padBetweenBlocks(blockType)
  90.                 || (blockType == INDEX_BLOCK_TYPE && padBetweenBlocks(keyType));
  91.     }

  92.     static boolean padBetweenBlocks(byte type) {
  93.         return type == REF_BLOCK_TYPE || type == OBJ_BLOCK_TYPE;
  94.     }

  95.     byte[] lastKey() {
  96.         return entries.get(entries.size() - 1).key;
  97.     }

  98.     int currentSize() {
  99.         return computeBlockBytes(0, false);
  100.     }

  101.     void mustAdd(Entry entry) throws BlockSizeTooSmallException {
  102.         if (!tryAdd(entry, true)) {
  103.             // Insanely long names need a larger block size.
  104.             throw blockSizeTooSmall(entry);
  105.         }
  106.     }

  107.     boolean tryAdd(Entry entry) {
  108.         if (entry instanceof ObjEntry
  109.                 && computeBlockBytes(entry.sizeBytes(), 1) > blockLimitBytes) {
  110.             // If the ObjEntry has so many ref block pointers that its
  111.             // encoding overflows any block, reconfigure it to tell readers to
  112.             // instead scan all refs for this ObjectId. That significantly
  113.             // shrinks the entry to a very small size, which may now fit into
  114.             // this block.
  115.             ((ObjEntry) entry).markScanRequired();
  116.         }

  117.         if (tryAdd(entry, true)) {
  118.             return true;
  119.         } else if (nextShouldBeRestart()) {
  120.             // It was time for another restart, but the entry doesn't fit
  121.             // with its complete key, as the block is nearly full. Try to
  122.             // force it to fit with prefix compression rather than waste
  123.             // the tail of the block with padding.
  124.             return tryAdd(entry, false);
  125.         }
  126.         return false;
  127.     }

  128.     private boolean tryAdd(Entry entry, boolean tryRestart) {
  129.         byte[] key = entry.key;
  130.         int prefixLen = 0;
  131.         boolean restart = tryRestart && nextShouldBeRestart();
  132.         if (!restart) {
  133.             Entry priorEntry = entries.get(entries.size() - 1);
  134.             byte[] prior = priorEntry.key;
  135.             prefixLen = commonPrefix(prior, prior.length, key);
  136.             if (prefixLen <= 5 /* "refs/" */ && keyType == REF_BLOCK_TYPE) {
  137.                 // Force restart points at transitions between namespaces
  138.                 // such as "refs/heads/" to "refs/tags/".
  139.                 restart = true;
  140.                 prefixLen = 0;
  141.             } else if (prefixLen == 0) {
  142.                 restart = true;
  143.             }
  144.         }

  145.         entry.restart = restart;
  146.         entry.prefixLen = prefixLen;
  147.         int entryBytes = entry.sizeBytes();
  148.         if (computeBlockBytes(entryBytes, restart) > blockLimitBytes) {
  149.             return false;
  150.         }

  151.         entriesSumBytes += entryBytes;
  152.         entries.add(entry);
  153.         if (restart) {
  154.             restartCnt++;
  155.         }
  156.         return true;
  157.     }

  158.     private boolean nextShouldBeRestart() {
  159.         int cnt = entries.size();
  160.         return (cnt == 0 || ((cnt + 1) % restartInterval) == 0)
  161.                 && restartCnt < MAX_RESTARTS;
  162.     }

  163.     private int computeBlockBytes(int entryBytes, boolean restart) {
  164.         return computeBlockBytes(
  165.                 entriesSumBytes + entryBytes,
  166.                 restartCnt + (restart ? 1 : 0));
  167.     }

  168.     private static int computeBlockBytes(int entryBytes, int restartCnt) {
  169.         return 4 // 4-byte block header
  170.                 + entryBytes
  171.                 + restartCnt * 3 // restart_offset
  172.                 + 2; // 2-byte restart_count
  173.     }

  174.     void writeTo(ReftableOutputStream os) throws IOException {
  175.         os.beginBlock(blockType);
  176.         IntList restarts = new IntList(restartCnt);
  177.         for (Entry entry : entries) {
  178.             if (entry.restart) {
  179.                 restarts.add(os.bytesWrittenInBlock());
  180.             }
  181.             entry.writeKey(os);
  182.             entry.writeValue(os);
  183.         }
  184.         if (restarts.size() == 0 || restarts.size() > MAX_RESTARTS) {
  185.             throw new IllegalStateException();
  186.         }
  187.         for (int i = 0; i < restarts.size(); i++) {
  188.             os.writeInt24(restarts.get(i));
  189.         }
  190.         os.writeInt16(restarts.size());
  191.         os.flushBlock();
  192.     }

  193.     private BlockSizeTooSmallException blockSizeTooSmall(Entry entry) {
  194.         // Compute size required to fit this entry by itself.
  195.         int min = FILE_HEADER_LEN + computeBlockBytes(entry.sizeBytes(), 1);
  196.         return new BlockSizeTooSmallException(min);
  197.     }

  198.     static int commonPrefix(byte[] a, int n, byte[] b) {
  199.         int len = Math.min(n, Math.min(a.length, b.length));
  200.         for (int i = 0; i < len; i++) {
  201.             if (a[i] != b[i]) {
  202.                 return i;
  203.             }
  204.         }
  205.         return len;
  206.     }

  207.     static int encodeSuffixAndType(int sfx, int valueType) {
  208.         return (sfx << 3) | valueType;
  209.     }

  210.     static int compare(
  211.             byte[] a, int ai, int aLen,
  212.             byte[] b, int bi, int bLen) {
  213.         int aEnd = ai + aLen;
  214.         int bEnd = bi + bLen;
  215.         while (ai < aEnd && bi < bEnd) {
  216.             int c = (a[ai++] & 0xff) - (b[bi++] & 0xff);
  217.             if (c != 0) {
  218.                 return c;
  219.             }
  220.         }
  221.         return aLen - bLen;
  222.     }

  223.     abstract static class Entry {
  224.         static int compare(Entry ea, Entry eb) {
  225.             byte[] a = ea.key;
  226.             byte[] b = eb.key;
  227.             return BlockWriter.compare(a, 0, a.length, b, 0, b.length);
  228.         }

  229.         final byte[] key;
  230.         int prefixLen;
  231.         boolean restart;

  232.         Entry(byte[] key) {
  233.             this.key = key;
  234.         }

  235.         void writeKey(ReftableOutputStream os) {
  236.             int sfxLen = key.length - prefixLen;
  237.             os.writeVarint(prefixLen);
  238.             os.writeVarint(encodeSuffixAndType(sfxLen, valueType()));
  239.             os.write(key, prefixLen, sfxLen);
  240.         }

  241.         int sizeBytes() {
  242.             int sfxLen = key.length - prefixLen;
  243.             int sfx = encodeSuffixAndType(sfxLen, valueType());
  244.             return computeVarintSize(prefixLen)
  245.                     + computeVarintSize(sfx)
  246.                     + sfxLen
  247.                     + valueSize();
  248.         }

  249.         abstract byte blockType();
  250.         abstract int valueType();
  251.         abstract int valueSize();
  252.         abstract void writeValue(ReftableOutputStream os) throws IOException;
  253.     }

  254.     static class IndexEntry extends Entry {
  255.         private final long blockPosition;

  256.         IndexEntry(byte[] key, long blockPosition) {
  257.             super(key);
  258.             this.blockPosition = blockPosition;
  259.         }

  260.         @Override
  261.         byte blockType() {
  262.             return INDEX_BLOCK_TYPE;
  263.         }

  264.         @Override
  265.         int valueType() {
  266.             return 0;
  267.         }

  268.         @Override
  269.         int valueSize() {
  270.             return computeVarintSize(blockPosition);
  271.         }

  272.         @Override
  273.         void writeValue(ReftableOutputStream os) {
  274.             os.writeVarint(blockPosition);
  275.         }
  276.     }

  277.     static class RefEntry extends Entry {
  278.         final Ref ref;
  279.         final long updateIndexDelta;

  280.         RefEntry(Ref ref, long updateIndexDelta) {
  281.             super(nameUtf8(ref));
  282.             this.ref = ref;
  283.             this.updateIndexDelta = updateIndexDelta;
  284.         }

  285.         @Override
  286.         byte blockType() {
  287.             return REF_BLOCK_TYPE;
  288.         }

  289.         @Override
  290.         int valueType() {
  291.             if (ref.isSymbolic()) {
  292.                 return VALUE_SYMREF;
  293.             } else if (ref.getStorage() == NEW && ref.getObjectId() == null) {
  294.                 return VALUE_NONE;
  295.             } else if (ref.getPeeledObjectId() != null) {
  296.                 return VALUE_2ID;
  297.             } else {
  298.                 return VALUE_1ID;
  299.             }
  300.         }

  301.         @Override
  302.         int valueSize() {
  303.             int n = computeVarintSize(updateIndexDelta);
  304.             switch (valueType()) {
  305.             case VALUE_NONE:
  306.                 return n;
  307.             case VALUE_1ID:
  308.                 return n + OBJECT_ID_LENGTH;
  309.             case VALUE_2ID:
  310.                 return n + 2 * OBJECT_ID_LENGTH;
  311.             case VALUE_SYMREF:
  312.                 if (ref.isSymbolic()) {
  313.                     int nameLen = nameUtf8(ref.getTarget()).length;
  314.                     return n + computeVarintSize(nameLen) + nameLen;
  315.                 }
  316.             }
  317.             throw new IllegalStateException();
  318.         }

  319.         @Override
  320.         void writeValue(ReftableOutputStream os) throws IOException {
  321.             os.writeVarint(updateIndexDelta);
  322.             switch (valueType()) {
  323.             case VALUE_NONE:
  324.                 return;

  325.             case VALUE_1ID: {
  326.                 ObjectId id1 = ref.getObjectId();
  327.                 if (!ref.isPeeled()) {
  328.                     throw new IOException(JGitText.get().peeledRefIsRequired);
  329.                 } else if (id1 == null) {
  330.                     throw new IOException(JGitText.get().invalidId0);
  331.                 }
  332.                 os.writeId(id1);
  333.                 return;
  334.             }

  335.             case VALUE_2ID: {
  336.                 ObjectId id1 = ref.getObjectId();
  337.                 ObjectId id2 = ref.getPeeledObjectId();
  338.                 if (!ref.isPeeled()) {
  339.                     throw new IOException(JGitText.get().peeledRefIsRequired);
  340.                 } else if (id1 == null || id2 == null) {
  341.                     throw new IOException(JGitText.get().invalidId0);
  342.                 }
  343.                 os.writeId(id1);
  344.                 os.writeId(id2);
  345.                 return;
  346.             }

  347.             case VALUE_SYMREF:
  348.                 if (ref.isSymbolic()) {
  349.                     os.writeVarintString(ref.getTarget().getName());
  350.                     return;
  351.                 }
  352.             }
  353.             throw new IllegalStateException();
  354.         }

  355.         private static byte[] nameUtf8(Ref ref) {
  356.             return ref.getName().getBytes(UTF_8);
  357.         }
  358.     }

  359.     static class ObjEntry extends Entry {
  360.         final LongList blockPos;

  361.         ObjEntry(int idLen, ObjectId id, LongList blockPos) {
  362.             super(key(idLen, id));
  363.             this.blockPos = blockPos;
  364.         }

  365.         private static byte[] key(int idLen, ObjectId id) {
  366.             byte[] key = new byte[OBJECT_ID_LENGTH];
  367.             id.copyRawTo(key, 0);
  368.             if (idLen < OBJECT_ID_LENGTH) {
  369.                 return Arrays.copyOf(key, idLen);
  370.             }
  371.             return key;
  372.         }

  373.         void markScanRequired() {
  374.             blockPos.clear();
  375.         }

  376.         @Override
  377.         byte blockType() {
  378.             return OBJ_BLOCK_TYPE;
  379.         }

  380.         @Override
  381.         int valueType() {
  382.             int cnt = blockPos.size();
  383.             return cnt != 0 && cnt <= VALUE_TYPE_MASK ? cnt : 0;
  384.         }

  385.         @Override
  386.         int valueSize() {
  387.             int cnt = blockPos.size();
  388.             if (cnt == 0) {
  389.                 return computeVarintSize(0);
  390.             }

  391.             int n = 0;
  392.             if (cnt > VALUE_TYPE_MASK) {
  393.                 n += computeVarintSize(cnt);
  394.             }
  395.             n += computeVarintSize(blockPos.get(0));
  396.             for (int j = 1; j < cnt; j++) {
  397.                 long prior = blockPos.get(j - 1);
  398.                 long b = blockPos.get(j);
  399.                 n += computeVarintSize(b - prior);
  400.             }
  401.             return n;
  402.         }

  403.         @Override
  404.         void writeValue(ReftableOutputStream os) throws IOException {
  405.             int cnt = blockPos.size();
  406.             if (cnt == 0) {
  407.                 os.writeVarint(0);
  408.                 return;
  409.             }

  410.             if (cnt > VALUE_TYPE_MASK) {
  411.                 os.writeVarint(cnt);
  412.             }
  413.             os.writeVarint(blockPos.get(0));
  414.             for (int j = 1; j < cnt; j++) {
  415.                 long prior = blockPos.get(j - 1);
  416.                 long b = blockPos.get(j);
  417.                 os.writeVarint(b - prior);
  418.             }
  419.         }
  420.     }

  421.     static class DeleteLogEntry extends Entry {
  422.         DeleteLogEntry(String refName, long updateIndex) {
  423.             super(LogEntry.key(refName, updateIndex));
  424.         }

  425.         @Override
  426.         byte blockType() {
  427.             return LOG_BLOCK_TYPE;
  428.         }

  429.         @Override
  430.         int valueType() {
  431.             return LOG_NONE;
  432.         }

  433.         @Override
  434.         int valueSize() {
  435.             return 0;
  436.         }

  437.         @Override
  438.         void writeValue(ReftableOutputStream os) {
  439.             // Nothing in a delete log record.
  440.         }
  441.     }

  442.     static class LogEntry extends Entry {
  443.         final ObjectId oldId;
  444.         final ObjectId newId;
  445.         final long timeSecs;
  446.         final short tz;
  447.         final byte[] name;
  448.         final byte[] email;
  449.         final byte[] msg;

  450.         LogEntry(String refName, long updateIndex, PersonIdent who,
  451.                 ObjectId oldId, ObjectId newId, String message) {
  452.             super(key(refName, updateIndex));

  453.             this.oldId = oldId;
  454.             this.newId = newId;
  455.             this.timeSecs = who.getWhen().getTime() / 1000L;
  456.             this.tz = (short) who.getTimeZoneOffset();
  457.             this.name = who.getName().getBytes(UTF_8);
  458.             this.email = who.getEmailAddress().getBytes(UTF_8);
  459.             this.msg = message.getBytes(UTF_8);
  460.         }

  461.         static byte[] key(String ref, long index) {
  462.             byte[] name = ref.getBytes(UTF_8);
  463.             byte[] key = Arrays.copyOf(name, name.length + 1 + 8);
  464.             NB.encodeInt64(key, key.length - 8, reverseUpdateIndex(index));
  465.             return key;
  466.         }

  467.         @Override
  468.         byte blockType() {
  469.             return LOG_BLOCK_TYPE;
  470.         }

  471.         @Override
  472.         int valueType() {
  473.             return LOG_DATA;
  474.         }

  475.         @Override
  476.         int valueSize() {
  477.             return 2 * OBJECT_ID_LENGTH
  478.                     + computeVarintSize(name.length) + name.length
  479.                     + computeVarintSize(email.length) + email.length
  480.                     + computeVarintSize(timeSecs)
  481.                     + 2 // tz
  482.                     + computeVarintSize(msg.length) + msg.length;
  483.         }

  484.         @Override
  485.         void writeValue(ReftableOutputStream os) {
  486.             os.writeId(oldId);
  487.             os.writeId(newId);
  488.             os.writeVarintString(name);
  489.             os.writeVarintString(email);
  490.             os.writeVarint(timeSecs);
  491.             os.writeInt16(tz);
  492.             os.writeVarintString(msg);
  493.         }
  494.     }
  495. }