You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ReftableWriter.java 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813
  1. /*
  2. * Copyright (C) 2017, Google Inc.
  3. * and other copyright owners as documented in the project's IP log.
  4. *
  5. * This program and the accompanying materials are made available
  6. * under the terms of the Eclipse Distribution License v1.0 which
  7. * accompanies this distribution, is reproduced below, and is
  8. * available at http://www.eclipse.org/org/documents/edl-v10.php
  9. *
  10. * All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or
  13. * without modification, are permitted provided that the following
  14. * conditions are met:
  15. *
  16. * - Redistributions of source code must retain the above copyright
  17. * notice, this list of conditions and the following disclaimer.
  18. *
  19. * - Redistributions in binary form must reproduce the above
  20. * copyright notice, this list of conditions and the following
  21. * disclaimer in the documentation and/or other materials provided
  22. * with the distribution.
  23. *
  24. * - Neither the name of the Eclipse Foundation, Inc. nor the
  25. * names of its contributors may be used to endorse or promote
  26. * products derived from this software without specific prior
  27. * written permission.
  28. *
  29. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  30. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  31. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  32. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  33. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  34. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  35. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  36. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  37. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  38. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  39. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  40. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  41. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  42. */
  43. package org.eclipse.jgit.internal.storage.reftable;
  44. import static java.lang.Math.log;
  45. import static org.eclipse.jgit.internal.storage.reftable.BlockWriter.padBetweenBlocks;
  46. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_FOOTER_LEN;
  47. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN;
  48. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_MAGIC;
  49. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.INDEX_BLOCK_TYPE;
  50. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_BLOCK_TYPE;
  51. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.MAX_BLOCK_SIZE;
  52. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.MAX_RESTARTS;
  53. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.OBJ_BLOCK_TYPE;
  54. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.REF_BLOCK_TYPE;
  55. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VERSION_1;
  56. import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
  57. import java.io.IOException;
  58. import java.io.OutputStream;
  59. import java.util.ArrayList;
  60. import java.util.Collection;
  61. import java.util.Collections;
  62. import java.util.HashSet;
  63. import java.util.Iterator;
  64. import java.util.List;
  65. import java.util.Set;
  66. import java.util.zip.CRC32;
  67. import org.eclipse.jgit.annotations.Nullable;
  68. import org.eclipse.jgit.internal.storage.reftable.BlockWriter.DeleteLogEntry;
  69. import org.eclipse.jgit.internal.storage.reftable.BlockWriter.Entry;
  70. import org.eclipse.jgit.internal.storage.reftable.BlockWriter.IndexEntry;
  71. import org.eclipse.jgit.internal.storage.reftable.BlockWriter.LogEntry;
  72. import org.eclipse.jgit.internal.storage.reftable.BlockWriter.ObjEntry;
  73. import org.eclipse.jgit.internal.storage.reftable.BlockWriter.RefEntry;
  74. import org.eclipse.jgit.lib.AbbreviatedObjectId;
  75. import org.eclipse.jgit.lib.AnyObjectId;
  76. import org.eclipse.jgit.lib.ObjectId;
  77. import org.eclipse.jgit.lib.ObjectIdOwnerMap;
  78. import org.eclipse.jgit.lib.ObjectIdSubclassMap;
  79. import org.eclipse.jgit.lib.PersonIdent;
  80. import org.eclipse.jgit.lib.Ref;
  81. import org.eclipse.jgit.util.LongList;
  82. import org.eclipse.jgit.util.NB;
  83. /**
  84. * Writes a reftable formatted file.
  85. * <p>
  86. * A reftable can be written in a streaming fashion, provided the caller sorts
  87. * all references. A {@link ReftableWriter} is single-use, and not thread-safe.
  88. */
  89. public class ReftableWriter {
  90. private ReftableConfig config;
  91. private int refBlockSize;
  92. private int logBlockSize;
  93. private int restartInterval;
  94. private int maxIndexLevels;
  95. private boolean alignBlocks;
  96. private boolean indexObjects;
  97. private long minUpdateIndex;
  98. private long maxUpdateIndex;
  99. private ReftableOutputStream out;
  100. private ObjectIdSubclassMap<RefList> obj2ref;
  101. private BlockWriter cur;
  102. private Section refs;
  103. private Section objs;
  104. private Section logs;
  105. private int objIdLen;
  106. private Stats stats;
  107. /** Initialize a writer with a default configuration. */
  108. public ReftableWriter() {
  109. this(new ReftableConfig());
  110. }
  111. /**
  112. * Initialize a writer with a specific configuration.
  113. *
  114. * @param cfg
  115. * configuration for the writer.
  116. */
  117. public ReftableWriter(ReftableConfig cfg) {
  118. config = cfg;
  119. }
  120. /**
  121. * @param cfg
  122. * configuration for the writer.
  123. * @return {@code this}
  124. */
  125. public ReftableWriter setConfig(ReftableConfig cfg) {
  126. this.config = cfg != null ? cfg : new ReftableConfig();
  127. return this;
  128. }
  129. /**
  130. * @param min
  131. * the minimum update index for log entries that appear in this
  132. * reftable. This should be 1 higher than the prior reftable's
  133. * {@code maxUpdateIndex} if this table will be used in a stack.
  134. * @return {@code this}
  135. */
  136. public ReftableWriter setMinUpdateIndex(long min) {
  137. minUpdateIndex = min;
  138. return this;
  139. }
  140. /**
  141. * @param max
  142. * the maximum update index for log entries that appear in this
  143. * reftable. This should be at least 1 higher than the prior
  144. * reftable's {@code maxUpdateIndex} if this table will be used
  145. * in a stack.
  146. * @return {@code this}
  147. */
  148. public ReftableWriter setMaxUpdateIndex(long max) {
  149. maxUpdateIndex = max;
  150. return this;
  151. }
  152. /**
  153. * Begin writing the reftable.
  154. *
  155. * @param os
  156. * stream to write the table to. Caller is responsible for
  157. * closing the stream after invoking {@link #finish()}.
  158. * @return {@code this}
  159. * @throws IOException
  160. * if reftable header cannot be written.
  161. */
  162. public ReftableWriter begin(OutputStream os) throws IOException {
  163. refBlockSize = config.getRefBlockSize();
  164. logBlockSize = config.getLogBlockSize();
  165. restartInterval = config.getRestartInterval();
  166. maxIndexLevels = config.getMaxIndexLevels();
  167. alignBlocks = config.isAlignBlocks();
  168. indexObjects = config.isIndexObjects();
  169. if (refBlockSize <= 0) {
  170. refBlockSize = 4 << 10;
  171. } else if (refBlockSize > MAX_BLOCK_SIZE) {
  172. throw new IllegalArgumentException();
  173. }
  174. if (logBlockSize <= 0) {
  175. logBlockSize = 2 * refBlockSize;
  176. }
  177. if (restartInterval <= 0) {
  178. restartInterval = refBlockSize < (60 << 10) ? 16 : 64;
  179. }
  180. out = new ReftableOutputStream(os, refBlockSize, alignBlocks);
  181. refs = new Section(REF_BLOCK_TYPE);
  182. if (indexObjects) {
  183. obj2ref = new ObjectIdSubclassMap<>();
  184. }
  185. writeFileHeader();
  186. return this;
  187. }
  188. /**
  189. * Sort a collection of references and write them to the reftable.
  190. *
  191. * @param refsToPack
  192. * references to sort and write.
  193. * @return {@code this}
  194. * @throws IOException
  195. * if reftable cannot be written.
  196. */
  197. public ReftableWriter sortAndWriteRefs(Collection<Ref> refsToPack)
  198. throws IOException {
  199. Iterator<RefEntry> itr = refsToPack.stream()
  200. .map(r -> new RefEntry(r, maxUpdateIndex - minUpdateIndex))
  201. .sorted(Entry::compare)
  202. .iterator();
  203. while (itr.hasNext()) {
  204. RefEntry entry = itr.next();
  205. long blockPos = refs.write(entry);
  206. indexRef(entry.ref, blockPos);
  207. }
  208. return this;
  209. }
  210. /**
  211. * Write one reference to the reftable.
  212. * <p>
  213. * References must be passed in sorted order.
  214. *
  215. * @param ref
  216. * the reference to store.
  217. * @throws IOException
  218. * if reftable cannot be written.
  219. */
  220. public void writeRef(Ref ref) throws IOException {
  221. writeRef(ref, maxUpdateIndex);
  222. }
  223. /**
  224. * Write one reference to the reftable.
  225. * <p>
  226. * References must be passed in sorted order.
  227. *
  228. * @param ref
  229. * the reference to store.
  230. * @param updateIndex
  231. * the updateIndex that modified this reference. Must be
  232. * {@code >= minUpdateIndex} for this file.
  233. * @throws IOException
  234. * if reftable cannot be written.
  235. */
  236. public void writeRef(Ref ref, long updateIndex) throws IOException {
  237. if (updateIndex < minUpdateIndex) {
  238. throw new IllegalArgumentException();
  239. }
  240. long d = updateIndex - minUpdateIndex;
  241. long blockPos = refs.write(new RefEntry(ref, d));
  242. indexRef(ref, blockPos);
  243. }
  244. private void indexRef(Ref ref, long blockPos) {
  245. if (indexObjects && !ref.isSymbolic()) {
  246. indexId(ref.getObjectId(), blockPos);
  247. indexId(ref.getPeeledObjectId(), blockPos);
  248. }
  249. }
  250. private void indexId(ObjectId id, long blockPos) {
  251. if (id != null) {
  252. RefList l = obj2ref.get(id);
  253. if (l == null) {
  254. l = new RefList(id);
  255. obj2ref.add(l);
  256. }
  257. l.addBlock(blockPos);
  258. }
  259. }
  260. /**
  261. * Write one reflog entry to the reftable.
  262. * <p>
  263. * Reflog entries must be written in reference name and descending
  264. * {@code updateIndex} (highest first) order.
  265. *
  266. * @param ref
  267. * name of the reference.
  268. * @param updateIndex
  269. * identifier of the transaction that created the log record. The
  270. * {@code updateIndex} must be unique within the scope of
  271. * {@code ref}, and must be within the bounds defined by
  272. * {@code minUpdateIndex <= updateIndex <= maxUpdateIndex}.
  273. * @param who
  274. * committer of the reflog entry.
  275. * @param oldId
  276. * prior id; pass {@link ObjectId#zeroId()} for creations.
  277. * @param newId
  278. * new id; pass {@link ObjectId#zeroId()} for deletions.
  279. * @param message
  280. * optional message (may be null).
  281. * @throws IOException
  282. * if reftable cannot be written.
  283. */
  284. public void writeLog(String ref, long updateIndex, PersonIdent who,
  285. ObjectId oldId, ObjectId newId, @Nullable String message)
  286. throws IOException {
  287. String msg = message != null ? message : ""; //$NON-NLS-1$
  288. beginLog();
  289. logs.write(new LogEntry(ref, updateIndex, who, oldId, newId, msg));
  290. }
  291. /**
  292. * Record deletion of one reflog entry in this reftable.
  293. *
  294. * <p>
  295. * The deletion can shadow an entry stored in a lower table in the stack.
  296. * This is useful for {@code refs/stash} and dropping an entry from its
  297. * reflog.
  298. * <p>
  299. * Deletion must be properly interleaved in sorted updateIndex order with
  300. * any other logs written by
  301. * {@link #writeLog(String, long, PersonIdent, ObjectId, ObjectId, String)}.
  302. *
  303. * @param ref
  304. * the ref to delete (hide) a reflog entry from.
  305. * @param updateIndex
  306. * the update index that must be hidden.
  307. * @throws IOException
  308. * if reftable cannot be written.
  309. */
  310. public void deleteLog(String ref, long updateIndex) throws IOException {
  311. beginLog();
  312. logs.write(new DeleteLogEntry(ref, updateIndex));
  313. }
  314. private void beginLog() throws IOException {
  315. if (logs == null) {
  316. finishRefAndObjSections(); // close prior ref blocks and their index, if present.
  317. out.flushFileHeader();
  318. out.setBlockSize(logBlockSize);
  319. logs = new Section(LOG_BLOCK_TYPE);
  320. }
  321. }
  322. /**
  323. * @return an estimate of the current size in bytes of the reftable, if it
  324. * was finished right now. Estimate is only accurate if
  325. * {@link ReftableConfig#setIndexObjects(boolean)} is {@code false}
  326. * and {@link ReftableConfig#setMaxIndexLevels(int)} is {@code 1}.
  327. */
  328. public long estimateTotalBytes() {
  329. long bytes = out.size();
  330. if (bytes == 0) {
  331. bytes += FILE_HEADER_LEN;
  332. }
  333. if (cur != null) {
  334. long curBlockPos = out.size();
  335. int sz = cur.currentSize();
  336. bytes += sz;
  337. IndexBuilder idx = null;
  338. if (cur.blockType() == REF_BLOCK_TYPE) {
  339. idx = refs.idx;
  340. } else if (cur.blockType() == LOG_BLOCK_TYPE) {
  341. idx = logs.idx;
  342. }
  343. if (idx != null && shouldHaveIndex(idx)) {
  344. if (idx == refs.idx) {
  345. bytes += out.estimatePadBetweenBlocks(sz);
  346. }
  347. bytes += idx.estimateBytes(curBlockPos);
  348. }
  349. }
  350. bytes += FILE_FOOTER_LEN;
  351. return bytes;
  352. }
  353. /**
  354. * Finish writing the reftable by writing its trailer.
  355. *
  356. * @return {@code this}
  357. * @throws IOException
  358. * if reftable cannot be written.
  359. */
  360. public ReftableWriter finish() throws IOException {
  361. finishRefAndObjSections();
  362. finishLogSection();
  363. writeFileFooter();
  364. out.finishFile();
  365. stats = new Stats(this, out);
  366. out = null;
  367. obj2ref = null;
  368. cur = null;
  369. refs = null;
  370. objs = null;
  371. logs = null;
  372. return this;
  373. }
  374. private void finishRefAndObjSections() throws IOException {
  375. if (cur != null && cur.blockType() == REF_BLOCK_TYPE) {
  376. refs.finishSectionMaybeWriteIndex();
  377. if (indexObjects && !obj2ref.isEmpty() && refs.idx.bytes > 0) {
  378. writeObjBlocks();
  379. }
  380. obj2ref = null;
  381. }
  382. }
  383. private void writeObjBlocks() throws IOException {
  384. List<RefList> sorted = sortById(obj2ref);
  385. obj2ref = null;
  386. objIdLen = shortestUniqueAbbreviation(sorted);
  387. out.padBetweenBlocksToNextBlock();
  388. objs = new Section(OBJ_BLOCK_TYPE);
  389. objs.entryCnt = sorted.size();
  390. for (RefList l : sorted) {
  391. objs.write(new ObjEntry(objIdLen, l, l.blockPos));
  392. }
  393. objs.finishSectionMaybeWriteIndex();
  394. }
  395. private void finishLogSection() throws IOException {
  396. if (cur != null && cur.blockType() == LOG_BLOCK_TYPE) {
  397. logs.finishSectionMaybeWriteIndex();
  398. }
  399. }
  400. private boolean shouldHaveIndex(IndexBuilder idx) {
  401. int threshold;
  402. if (idx == refs.idx && alignBlocks) {
  403. threshold = 4;
  404. } else {
  405. threshold = 1;
  406. }
  407. return idx.entries.size() + (cur != null ? 1 : 0) > threshold;
  408. }
  409. private void writeFileHeader() {
  410. byte[] hdr = new byte[FILE_HEADER_LEN];
  411. encodeHeader(hdr);
  412. out.write(hdr, 0, FILE_HEADER_LEN);
  413. }
  414. private void encodeHeader(byte[] hdr) {
  415. System.arraycopy(FILE_HEADER_MAGIC, 0, hdr, 0, 4);
  416. int bs = alignBlocks ? refBlockSize : 0;
  417. NB.encodeInt32(hdr, 4, (VERSION_1 << 24) | bs);
  418. NB.encodeInt64(hdr, 8, minUpdateIndex);
  419. NB.encodeInt64(hdr, 16, maxUpdateIndex);
  420. }
  421. private void writeFileFooter() {
  422. int ftrLen = FILE_FOOTER_LEN;
  423. byte[] ftr = new byte[ftrLen];
  424. encodeHeader(ftr);
  425. NB.encodeInt64(ftr, 24, indexPosition(refs));
  426. NB.encodeInt64(ftr, 32, (firstBlockPosition(objs) << 5) | objIdLen);
  427. NB.encodeInt64(ftr, 40, indexPosition(objs));
  428. NB.encodeInt64(ftr, 48, firstBlockPosition(logs));
  429. NB.encodeInt64(ftr, 56, indexPosition(logs));
  430. CRC32 crc = new CRC32();
  431. crc.update(ftr, 0, ftrLen - 4);
  432. NB.encodeInt32(ftr, ftrLen - 4, (int) crc.getValue());
  433. out.write(ftr, 0, ftrLen);
  434. }
  435. private static long firstBlockPosition(@Nullable Section s) {
  436. return s != null ? s.firstBlockPosition : 0;
  437. }
  438. private static long indexPosition(@Nullable Section s) {
  439. return s != null && s.idx != null ? s.idx.rootPosition : 0;
  440. }
  441. /** @return statistics of the last written reftable. */
  442. public Stats getStats() {
  443. return stats;
  444. }
  445. /** Statistics about a written reftable. */
  446. public static class Stats {
  447. private final int refBlockSize;
  448. private final int logBlockSize;
  449. private final int restartInterval;
  450. private final long minUpdateIndex;
  451. private final long maxUpdateIndex;
  452. private final long refCnt;
  453. private final long objCnt;
  454. private final int objIdLen;
  455. private final long logCnt;
  456. private final long refBytes;
  457. private final long objBytes;
  458. private final long logBytes;
  459. private final long paddingUsed;
  460. private final long totalBytes;
  461. private final int refIndexSize;
  462. private final int refIndexLevels;
  463. private final int objIndexSize;
  464. private final int objIndexLevels;
  465. Stats(ReftableWriter w, ReftableOutputStream o) {
  466. refBlockSize = w.refBlockSize;
  467. logBlockSize = w.logBlockSize;
  468. restartInterval = w.restartInterval;
  469. minUpdateIndex = w.minUpdateIndex;
  470. maxUpdateIndex = w.maxUpdateIndex;
  471. paddingUsed = o.paddingUsed();
  472. totalBytes = o.size();
  473. refCnt = w.refs.entryCnt;
  474. refBytes = w.refs.bytes;
  475. objCnt = w.objs != null ? w.objs.entryCnt : 0;
  476. objBytes = w.objs != null ? w.objs.bytes : 0;
  477. objIdLen = w.objIdLen;
  478. logCnt = w.logs != null ? w.logs.entryCnt : 0;
  479. logBytes = w.logs != null ? w.logs.bytes : 0;
  480. IndexBuilder refIdx = w.refs.idx;
  481. refIndexSize = refIdx.bytes;
  482. refIndexLevels = refIdx.levels;
  483. IndexBuilder objIdx = w.objs != null ? w.objs.idx : null;
  484. objIndexSize = objIdx != null ? objIdx.bytes : 0;
  485. objIndexLevels = objIdx != null ? objIdx.levels : 0;
  486. }
  487. /** @return number of bytes in a ref block. */
  488. public int refBlockSize() {
  489. return refBlockSize;
  490. }
  491. /** @return number of bytes to compress into a log block. */
  492. public int logBlockSize() {
  493. return logBlockSize;
  494. }
  495. /** @return number of references between binary search markers. */
  496. public int restartInterval() {
  497. return restartInterval;
  498. }
  499. /** @return smallest update index contained in this reftable. */
  500. public long minUpdateIndex() {
  501. return minUpdateIndex;
  502. }
  503. /** @return largest update index contained in this reftable. */
  504. public long maxUpdateIndex() {
  505. return maxUpdateIndex;
  506. }
  507. /** @return total number of references in the reftable. */
  508. public long refCount() {
  509. return refCnt;
  510. }
  511. /** @return number of unique objects in the reftable. */
  512. public long objCount() {
  513. return objCnt;
  514. }
  515. /** @return total number of log records in the reftable. */
  516. public long logCount() {
  517. return logCnt;
  518. }
  519. /** @return number of bytes for references, including ref index. */
  520. public long refBytes() {
  521. return refBytes;
  522. }
  523. /** @return number of bytes for objects, including object index. */
  524. public long objBytes() {
  525. return objBytes;
  526. }
  527. /** @return number of bytes for log, including log index. */
  528. public long logBytes() {
  529. return logBytes;
  530. }
  531. /** @return total number of bytes in the reftable. */
  532. public long totalBytes() {
  533. return totalBytes;
  534. }
  535. /** @return bytes of padding used to maintain block alignment. */
  536. public long paddingBytes() {
  537. return paddingUsed;
  538. }
  539. /** @return number of bytes in the ref index; 0 if no index was used. */
  540. public int refIndexSize() {
  541. return refIndexSize;
  542. }
  543. /** @return number of levels in the ref index. */
  544. public int refIndexLevels() {
  545. return refIndexLevels;
  546. }
  547. /** @return number of bytes in the object index; 0 if no index. */
  548. public int objIndexSize() {
  549. return objIndexSize;
  550. }
  551. /** @return number of levels in the object index. */
  552. public int objIndexLevels() {
  553. return objIndexLevels;
  554. }
  555. /**
  556. * @return number of bytes required to uniquely identify all objects in
  557. * the reftable. Unique abbreviations in hex would be
  558. * {@code 2 * objIdLength()}.
  559. */
  560. public int objIdLength() {
  561. return objIdLen;
  562. }
  563. }
  564. private static List<RefList> sortById(ObjectIdSubclassMap<RefList> m) {
  565. List<RefList> s = new ArrayList<>(m.size());
  566. for (RefList l : m) {
  567. s.add(l);
  568. }
  569. Collections.sort(s);
  570. return s;
  571. }
  572. private static int shortestUniqueAbbreviation(List<RefList> in) {
  573. // Estimate minimum number of bytes necessary for unique abbreviations.
  574. int bytes = Math.max(2, (int) (log(in.size()) / log(8)));
  575. Set<AbbreviatedObjectId> tmp = new HashSet<>((int) (in.size() * 0.75f));
  576. retry: for (;;) {
  577. int hexLen = bytes * 2;
  578. for (ObjectId id : in) {
  579. AbbreviatedObjectId a = id.abbreviate(hexLen);
  580. if (!tmp.add(a)) {
  581. if (++bytes >= OBJECT_ID_LENGTH) {
  582. return OBJECT_ID_LENGTH;
  583. }
  584. tmp.clear();
  585. continue retry;
  586. }
  587. }
  588. return bytes;
  589. }
  590. }
  591. private static class RefList extends ObjectIdOwnerMap.Entry {
  592. final LongList blockPos = new LongList(2);
  593. RefList(AnyObjectId id) {
  594. super(id);
  595. }
  596. void addBlock(long pos) {
  597. if (!blockPos.contains(pos)) {
  598. blockPos.add(pos);
  599. }
  600. }
  601. }
  602. private class Section {
  603. final IndexBuilder idx;
  604. final long firstBlockPosition;
  605. long entryCnt;
  606. long bytes;
  607. Section(byte keyType) {
  608. idx = new IndexBuilder(keyType);
  609. firstBlockPosition = out.size();
  610. }
  611. long write(BlockWriter.Entry entry) throws IOException {
  612. if (cur == null) {
  613. beginBlock(entry);
  614. } else if (!cur.tryAdd(entry)) {
  615. flushCurBlock();
  616. if (cur.padBetweenBlocks()) {
  617. out.padBetweenBlocksToNextBlock();
  618. }
  619. beginBlock(entry);
  620. }
  621. entryCnt++;
  622. return out.size();
  623. }
  624. private void beginBlock(BlockWriter.Entry entry)
  625. throws BlockSizeTooSmallException {
  626. byte blockType = entry.blockType();
  627. int bs = out.bytesAvailableInBlock();
  628. cur = new BlockWriter(blockType, idx.keyType, bs, restartInterval);
  629. cur.mustAdd(entry);
  630. }
  631. void flushCurBlock() throws IOException {
  632. idx.entries.add(new IndexEntry(cur.lastKey(), out.size()));
  633. cur.writeTo(out);
  634. }
  635. void finishSectionMaybeWriteIndex() throws IOException {
  636. flushCurBlock();
  637. cur = null;
  638. if (shouldHaveIndex(idx)) {
  639. idx.writeIndex();
  640. }
  641. bytes = out.size() - firstBlockPosition;
  642. }
  643. }
  644. private class IndexBuilder {
  645. final byte keyType;
  646. List<IndexEntry> entries = new ArrayList<>();
  647. long rootPosition;
  648. int bytes;
  649. int levels;
  650. IndexBuilder(byte kt) {
  651. keyType = kt;
  652. }
  653. int estimateBytes(long curBlockPos) {
  654. BlockWriter b = new BlockWriter(
  655. INDEX_BLOCK_TYPE, keyType,
  656. MAX_BLOCK_SIZE,
  657. Math.max(restartInterval, entries.size() / MAX_RESTARTS));
  658. try {
  659. for (Entry e : entries) {
  660. b.mustAdd(e);
  661. }
  662. if (cur != null) {
  663. b.mustAdd(new IndexEntry(cur.lastKey(), curBlockPos));
  664. }
  665. } catch (BlockSizeTooSmallException e) {
  666. return b.currentSize();
  667. }
  668. return b.currentSize();
  669. }
  670. void writeIndex() throws IOException {
  671. if (padBetweenBlocks(keyType)) {
  672. out.padBetweenBlocksToNextBlock();
  673. }
  674. long startPos = out.size();
  675. writeMultiLevelIndex(entries);
  676. bytes = (int) (out.size() - startPos);
  677. entries = null;
  678. }
  679. private void writeMultiLevelIndex(List<IndexEntry> keys)
  680. throws IOException {
  681. levels = 1;
  682. while (maxIndexLevels == 0 || levels < maxIndexLevels) {
  683. keys = writeOneLevel(keys);
  684. if (keys == null) {
  685. return;
  686. }
  687. levels++;
  688. }
  689. // When maxIndexLevels has restricted the writer, write one
  690. // index block with the entire remaining set of keys.
  691. BlockWriter b = new BlockWriter(
  692. INDEX_BLOCK_TYPE, keyType,
  693. MAX_BLOCK_SIZE,
  694. Math.max(restartInterval, keys.size() / MAX_RESTARTS));
  695. for (Entry e : keys) {
  696. b.mustAdd(e);
  697. }
  698. rootPosition = out.size();
  699. b.writeTo(out);
  700. }
  701. private List<IndexEntry> writeOneLevel(List<IndexEntry> keys)
  702. throws IOException {
  703. Section thisLevel = new Section(keyType);
  704. for (Entry e : keys) {
  705. thisLevel.write(e);
  706. }
  707. if (!thisLevel.idx.entries.isEmpty()) {
  708. thisLevel.flushCurBlock();
  709. if (cur.padBetweenBlocks()) {
  710. out.padBetweenBlocksToNextBlock();
  711. }
  712. cur = null;
  713. return thisLevel.idx.entries;
  714. }
  715. // The current block fit entire level; make it the root.
  716. rootPosition = out.size();
  717. cur.writeTo(out);
  718. cur = null;
  719. return null;
  720. }
  721. }
  722. }