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 25KB

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