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