Browse Source
This is a simple writer to create reftable formatted files. Follow-up commits will add support for reading from reftable, debugging utilities, and tests. Change-Id: I3d520c3515c580144490b0b45433ea175a3e6e11stable-4.9
Shawn Pearce
8 years ago
9 changed files with 2010 additions and 0 deletions
@ -0,0 +1,62 @@
|
||||
/* |
||||
* Copyright (C) 2017, Google Inc. |
||||
* and other copyright owners as documented in the project's IP log. |
||||
* |
||||
* This program and the accompanying materials are made available |
||||
* under the terms of the Eclipse Distribution License v1.0 which |
||||
* accompanies this distribution, is reproduced below, and is |
||||
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
||||
* |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or |
||||
* without modification, are permitted provided that the following |
||||
* conditions are met: |
||||
* |
||||
* - Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* |
||||
* - Redistributions in binary form must reproduce the above |
||||
* copyright notice, this list of conditions and the following |
||||
* disclaimer in the documentation and/or other materials provided |
||||
* with the distribution. |
||||
* |
||||
* - Neither the name of the Eclipse Foundation, Inc. nor the |
||||
* names of its contributors may be used to endorse or promote |
||||
* products derived from this software without specific prior |
||||
* written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
package org.eclipse.jgit.internal.storage.reftable; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
/** Thrown if {@link ReftableWriter} cannot fit a reference. */ |
||||
public class BlockSizeTooSmallException extends IOException { |
||||
private static final long serialVersionUID = 1L; |
||||
|
||||
private final int minBlockSize; |
||||
|
||||
BlockSizeTooSmallException(int b) { |
||||
minBlockSize = b; |
||||
} |
||||
|
||||
/** @return minimum block size in bytes reftable requires to write a ref. */ |
||||
public int getMinimumBlockSize() { |
||||
return minBlockSize; |
||||
} |
||||
} |
@ -0,0 +1,601 @@
|
||||
/* |
||||
* Copyright (C) 2017, Google Inc. |
||||
* and other copyright owners as documented in the project's IP log. |
||||
* |
||||
* This program and the accompanying materials are made available |
||||
* under the terms of the Eclipse Distribution License v1.0 which |
||||
* accompanies this distribution, is reproduced below, and is |
||||
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
||||
* |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or |
||||
* without modification, are permitted provided that the following |
||||
* conditions are met: |
||||
* |
||||
* - Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* |
||||
* - Redistributions in binary form must reproduce the above |
||||
* copyright notice, this list of conditions and the following |
||||
* disclaimer in the documentation and/or other materials provided |
||||
* with the distribution. |
||||
* |
||||
* - Neither the name of the Eclipse Foundation, Inc. nor the |
||||
* names of its contributors may be used to endorse or promote |
||||
* products derived from this software without specific prior |
||||
* written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
package org.eclipse.jgit.internal.storage.reftable; |
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8; |
||||
import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN; |
||||
import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.INDEX_BLOCK_TYPE; |
||||
import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_BLOCK_TYPE; |
||||
import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_DATA; |
||||
import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_NONE; |
||||
import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.MAX_RESTARTS; |
||||
import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.OBJ_BLOCK_TYPE; |
||||
import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.REF_BLOCK_TYPE; |
||||
import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_1ID; |
||||
import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_2ID; |
||||
import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_NONE; |
||||
import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_SYMREF; |
||||
import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_TYPE_MASK; |
||||
import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.reverseUpdateIndex; |
||||
import static org.eclipse.jgit.internal.storage.reftable.ReftableOutputStream.computeVarintSize; |
||||
import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH; |
||||
import static org.eclipse.jgit.lib.Ref.Storage.NEW; |
||||
|
||||
import java.io.IOException; |
||||
import java.util.ArrayList; |
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
|
||||
import org.eclipse.jgit.internal.JGitText; |
||||
import org.eclipse.jgit.lib.ObjectId; |
||||
import org.eclipse.jgit.lib.PersonIdent; |
||||
import org.eclipse.jgit.lib.Ref; |
||||
import org.eclipse.jgit.util.IntList; |
||||
import org.eclipse.jgit.util.LongList; |
||||
import org.eclipse.jgit.util.NB; |
||||
|
||||
/** Formats and writes blocks for {@link ReftableWriter}. */ |
||||
class BlockWriter { |
||||
private final byte blockType; |
||||
private final byte keyType; |
||||
private final List<Entry> entries; |
||||
private final int blockLimitBytes; |
||||
private final int restartInterval; |
||||
|
||||
private int entriesSumBytes; |
||||
private int restartCnt; |
||||
|
||||
BlockWriter(byte type, byte kt, int bs, int ri) { |
||||
blockType = type; |
||||
keyType = kt; |
||||
blockLimitBytes = bs; |
||||
restartInterval = ri; |
||||
entries = new ArrayList<>(estimateEntryCount(type, kt, bs)); |
||||
} |
||||
|
||||
private static int estimateEntryCount(byte blockType, byte keyType, |
||||
int blockLimitBytes) { |
||||
double avgBytesPerEntry; |
||||
switch (blockType) { |
||||
case REF_BLOCK_TYPE: |
||||
default: |
||||
avgBytesPerEntry = 35.31; |
||||
break; |
||||
|
||||
case OBJ_BLOCK_TYPE: |
||||
avgBytesPerEntry = 4.19; |
||||
break; |
||||
|
||||
case LOG_BLOCK_TYPE: |
||||
avgBytesPerEntry = 101.14; |
||||
break; |
||||
|
||||
case INDEX_BLOCK_TYPE: |
||||
switch (keyType) { |
||||
case REF_BLOCK_TYPE: |
||||
case LOG_BLOCK_TYPE: |
||||
default: |
||||
avgBytesPerEntry = 27.44; |
||||
break; |
||||
|
||||
case OBJ_BLOCK_TYPE: |
||||
avgBytesPerEntry = 11.57; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
int cnt = (int) (Math.ceil(blockLimitBytes / avgBytesPerEntry)); |
||||
return Math.min(cnt, 4096); |
||||
} |
||||
|
||||
byte blockType() { |
||||
return blockType; |
||||
} |
||||
|
||||
boolean padBetweenBlocks() { |
||||
return padBetweenBlocks(blockType) |
||||
|| (blockType == INDEX_BLOCK_TYPE && padBetweenBlocks(keyType)); |
||||
} |
||||
|
||||
static boolean padBetweenBlocks(byte type) { |
||||
return type == REF_BLOCK_TYPE || type == OBJ_BLOCK_TYPE; |
||||
} |
||||
|
||||
byte[] lastKey() { |
||||
return entries.get(entries.size() - 1).key; |
||||
} |
||||
|
||||
int currentSize() { |
||||
return computeBlockBytes(0, false); |
||||
} |
||||
|
||||
void mustAdd(Entry entry) throws BlockSizeTooSmallException { |
||||
if (!tryAdd(entry, true)) { |
||||
// Insanely long names need a larger block size.
|
||||
throw blockSizeTooSmall(entry); |
||||
} |
||||
} |
||||
|
||||
boolean tryAdd(Entry entry) { |
||||
if (entry instanceof ObjEntry |
||||
&& computeBlockBytes(entry.sizeBytes(), 1) > blockLimitBytes) { |
||||
// If the ObjEntry has so many ref block pointers that its
|
||||
// encoding overflows any block, reconfigure it to tell readers to
|
||||
// instead scan all refs for this ObjectId. That significantly
|
||||
// shrinks the entry to a very small size, which may now fit into
|
||||
// this block.
|
||||
((ObjEntry) entry).markScanRequired(); |
||||
} |
||||
|
||||
if (tryAdd(entry, true)) { |
||||
return true; |
||||
} else if (nextShouldBeRestart()) { |
||||
// It was time for another restart, but the entry doesn't fit
|
||||
// with its complete key, as the block is nearly full. Try to
|
||||
// force it to fit with prefix compression rather than waste
|
||||
// the tail of the block with padding.
|
||||
return tryAdd(entry, false); |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
private boolean tryAdd(Entry entry, boolean tryRestart) { |
||||
byte[] key = entry.key; |
||||
int prefixLen = 0; |
||||
boolean restart = tryRestart && nextShouldBeRestart(); |
||||
if (!restart) { |
||||
Entry priorEntry = entries.get(entries.size() - 1); |
||||
byte[] prior = priorEntry.key; |
||||
prefixLen = commonPrefix(prior, prior.length, key); |
||||
if (prefixLen <= 5 /* "refs/" */ && keyType == REF_BLOCK_TYPE) { |
||||
// Force restart points at transitions between namespaces
|
||||
// such as "refs/heads/" to "refs/tags/".
|
||||
restart = true; |
||||
prefixLen = 0; |
||||
} else if (prefixLen == 0) { |
||||
restart = true; |
||||
} |
||||
} |
||||
|
||||
entry.restart = restart; |
||||
entry.prefixLen = prefixLen; |
||||
int entryBytes = entry.sizeBytes(); |
||||
if (computeBlockBytes(entryBytes, restart) > blockLimitBytes) { |
||||
return false; |
||||
} |
||||
|
||||
entriesSumBytes += entryBytes; |
||||
entries.add(entry); |
||||
if (restart) { |
||||
restartCnt++; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
private boolean nextShouldBeRestart() { |
||||
int cnt = entries.size(); |
||||
return (cnt == 0 || ((cnt + 1) % restartInterval) == 0) |
||||
&& restartCnt < MAX_RESTARTS; |
||||
} |
||||
|
||||
private int computeBlockBytes(int entryBytes, boolean restart) { |
||||
return computeBlockBytes( |
||||
entriesSumBytes + entryBytes, |
||||
restartCnt + (restart ? 1 : 0)); |
||||
} |
||||
|
||||
private static int computeBlockBytes(int entryBytes, int restartCnt) { |
||||
return 4 // 4-byte block header
|
||||
+ entryBytes |
||||
+ restartCnt * 3 // restart_offset
|
||||
+ 2; // 2-byte restart_count
|
||||
} |
||||
|
||||
void writeTo(ReftableOutputStream os) throws IOException { |
||||
os.beginBlock(blockType); |
||||
IntList restarts = new IntList(restartCnt); |
||||
for (Entry entry : entries) { |
||||
if (entry.restart) { |
||||
restarts.add(os.bytesWrittenInBlock()); |
||||
} |
||||
entry.writeKey(os); |
||||
entry.writeValue(os); |
||||
} |
||||
if (restarts.size() == 0 || restarts.size() > MAX_RESTARTS) { |
||||
throw new IllegalStateException(); |
||||
} |
||||
for (int i = 0; i < restarts.size(); i++) { |
||||
os.writeInt24(restarts.get(i)); |
||||
} |
||||
os.writeInt16(restarts.size()); |
||||
os.flushBlock(); |
||||
} |
||||
|
||||
private BlockSizeTooSmallException blockSizeTooSmall(Entry entry) { |
||||
// Compute size required to fit this entry by itself.
|
||||
int min = FILE_HEADER_LEN + computeBlockBytes(entry.sizeBytes(), 1); |
||||
return new BlockSizeTooSmallException(min); |
||||
} |
||||
|
||||
static int commonPrefix(byte[] a, int n, byte[] b) { |
||||
int len = Math.min(n, Math.min(a.length, b.length)); |
||||
for (int i = 0; i < len; i++) { |
||||
if (a[i] != b[i]) { |
||||
return i; |
||||
} |
||||
} |
||||
return len; |
||||
} |
||||
|
||||
static int encodeSuffixAndType(int sfx, int valueType) { |
||||
return (sfx << 3) | valueType; |
||||
} |
||||
|
||||
static int compare( |
||||
byte[] a, int ai, int aLen, |
||||
byte[] b, int bi, int bLen) { |
||||
int aEnd = ai + aLen; |
||||
int bEnd = bi + bLen; |
||||
while (ai < aEnd && bi < bEnd) { |
||||
int c = (a[ai++] & 0xff) - (b[bi++] & 0xff); |
||||
if (c != 0) { |
||||
return c; |
||||
} |
||||
} |
||||
return aLen - bLen; |
||||
} |
||||
|
||||
static abstract class Entry { |
||||
static int compare(Entry ea, Entry eb) { |
||||
byte[] a = ea.key; |
||||
byte[] b = eb.key; |
||||
return BlockWriter.compare(a, 0, a.length, b, 0, b.length); |
||||
} |
||||
|
||||
final byte[] key; |
||||
int prefixLen; |
||||
boolean restart; |
||||
|
||||
Entry(byte[] key) { |
||||
this.key = key; |
||||
} |
||||
|
||||
void writeKey(ReftableOutputStream os) { |
||||
int sfxLen = key.length - prefixLen; |
||||
os.writeVarint(prefixLen); |
||||
os.writeVarint(encodeSuffixAndType(sfxLen, valueType())); |
||||
os.write(key, prefixLen, sfxLen); |
||||
} |
||||
|
||||
int sizeBytes() { |
||||
int sfxLen = key.length - prefixLen; |
||||
int sfx = encodeSuffixAndType(sfxLen, valueType()); |
||||
return computeVarintSize(prefixLen) |
||||
+ computeVarintSize(sfx) |
||||
+ sfxLen |
||||
+ valueSize(); |
||||
} |
||||
|
||||
abstract byte blockType(); |
||||
abstract int valueType(); |
||||
abstract int valueSize(); |
||||
abstract void writeValue(ReftableOutputStream os) throws IOException; |
||||
} |
||||
|
||||
static class IndexEntry extends Entry { |
||||
private final long blockPosition; |
||||
|
||||
IndexEntry(byte[] key, long blockPosition) { |
||||
super(key); |
||||
this.blockPosition = blockPosition; |
||||
} |
||||
|
||||
@Override |
||||
byte blockType() { |
||||
return INDEX_BLOCK_TYPE; |
||||
} |
||||
|
||||
@Override |
||||
int valueType() { |
||||
return 0; |
||||
} |
||||
|
||||
@Override |
||||
int valueSize() { |
||||
return computeVarintSize(blockPosition); |
||||
} |
||||
|
||||
@Override |
||||
void writeValue(ReftableOutputStream os) { |
||||
os.writeVarint(blockPosition); |
||||
} |
||||
} |
||||
|
||||
static class RefEntry extends Entry { |
||||
final Ref ref; |
||||
|
||||
RefEntry(Ref ref) { |
||||
super(nameUtf8(ref)); |
||||
this.ref = ref; |
||||
} |
||||
|
||||
@Override |
||||
byte blockType() { |
||||
return REF_BLOCK_TYPE; |
||||
} |
||||
|
||||
@Override |
||||
int valueType() { |
||||
if (ref.isSymbolic()) { |
||||
return VALUE_SYMREF; |
||||
} else if (ref.getStorage() == NEW && ref.getObjectId() == null) { |
||||
return VALUE_NONE; |
||||
} else if (ref.getPeeledObjectId() != null) { |
||||
return VALUE_2ID; |
||||
} else { |
||||
return VALUE_1ID; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
int valueSize() { |
||||
switch (valueType()) { |
||||
case VALUE_NONE: |
||||
return 0; |
||||
case VALUE_1ID: |
||||
return OBJECT_ID_LENGTH; |
||||
case VALUE_2ID: |
||||
return 2 * OBJECT_ID_LENGTH; |
||||
case VALUE_SYMREF: |
||||
if (ref.isSymbolic()) { |
||||
int nameLen = nameUtf8(ref.getTarget()).length; |
||||
return computeVarintSize(nameLen) + nameLen; |
||||
} |
||||
} |
||||
throw new IllegalStateException(); |
||||
} |
||||
|
||||
@Override |
||||
void writeValue(ReftableOutputStream os) throws IOException { |
||||
switch (valueType()) { |
||||
case VALUE_NONE: |
||||
return; |
||||
|
||||
case VALUE_1ID: { |
||||
ObjectId id1 = ref.getObjectId(); |
||||
if (!ref.isPeeled()) { |
||||
throw new IOException(JGitText.get().peeledRefIsRequired); |
||||
} else if (id1 == null) { |
||||
throw new IOException(JGitText.get().invalidId0); |
||||
} |
||||
os.writeId(id1); |
||||
return; |
||||
} |
||||
|
||||
case VALUE_2ID: { |
||||
ObjectId id1 = ref.getObjectId(); |
||||
ObjectId id2 = ref.getPeeledObjectId(); |
||||
if (!ref.isPeeled()) { |
||||
throw new IOException(JGitText.get().peeledRefIsRequired); |
||||
} else if (id1 == null || id2 == null) { |
||||
throw new IOException(JGitText.get().invalidId0); |
||||
} |
||||
os.writeId(id1); |
||||
os.writeId(id2); |
||||
return; |
||||
} |
||||
|
||||
case VALUE_SYMREF: |
||||
if (ref.isSymbolic()) { |
||||
os.writeVarintString(ref.getTarget().getName()); |
||||
return; |
||||
} |
||||
} |
||||
throw new IllegalStateException(); |
||||
} |
||||
|
||||
private static byte[] nameUtf8(Ref ref) { |
||||
return ref.getName().getBytes(UTF_8); |
||||
} |
||||
} |
||||
|
||||
static class ObjEntry extends Entry { |
||||
final LongList blockPos; |
||||
|
||||
ObjEntry(int idLen, ObjectId id, LongList blockPos) { |
||||
super(key(idLen, id)); |
||||
this.blockPos = blockPos; |
||||
} |
||||
|
||||
private static byte[] key(int idLen, ObjectId id) { |
||||
byte[] key = new byte[OBJECT_ID_LENGTH]; |
||||
id.copyRawTo(key, 0); |
||||
if (idLen < OBJECT_ID_LENGTH) { |
||||
return Arrays.copyOf(key, idLen); |
||||
} |
||||
return key; |
||||
} |
||||
|
||||
void markScanRequired() { |
||||
blockPos.clear(); |
||||
} |
||||
|
||||
@Override |
||||
byte blockType() { |
||||
return OBJ_BLOCK_TYPE; |
||||
} |
||||
|
||||
@Override |
||||
int valueType() { |
||||
int cnt = blockPos.size(); |
||||
return cnt != 0 && cnt <= VALUE_TYPE_MASK ? cnt : 0; |
||||
} |
||||
|
||||
@Override |
||||
int valueSize() { |
||||
int cnt = blockPos.size(); |
||||
if (cnt == 0) { |
||||
return computeVarintSize(0); |
||||
} |
||||
|
||||
int n = 0; |
||||
if (cnt > VALUE_TYPE_MASK) { |
||||
n += computeVarintSize(cnt); |
||||
} |
||||
n += computeVarintSize(blockPos.get(0)); |
||||
for (int j = 1; j < cnt; j++) { |
||||
long prior = blockPos.get(j - 1); |
||||
long b = blockPos.get(j); |
||||
n += computeVarintSize(b - prior); |
||||
} |
||||
return n; |
||||
} |
||||
|
||||
@Override |
||||
void writeValue(ReftableOutputStream os) throws IOException { |
||||
int cnt = blockPos.size(); |
||||
if (cnt == 0) { |
||||
os.writeVarint(0); |
||||
return; |
||||
} |
||||
|
||||
if (cnt > VALUE_TYPE_MASK) { |
||||
os.writeVarint(cnt); |
||||
} |
||||
os.writeVarint(blockPos.get(0)); |
||||
for (int j = 1; j < cnt; j++) { |
||||
long prior = blockPos.get(j - 1); |
||||
long b = blockPos.get(j); |
||||
os.writeVarint(b - prior); |
||||
} |
||||
} |
||||
} |
||||
|
||||
static class DeleteLogEntry extends Entry { |
||||
DeleteLogEntry(String refName, long updateIndex) { |
||||
super(LogEntry.key(refName, updateIndex)); |
||||
} |
||||
|
||||
@Override |
||||
byte blockType() { |
||||
return LOG_BLOCK_TYPE; |
||||
} |
||||
|
||||
@Override |
||||
int valueType() { |
||||
return LOG_NONE; |
||||
} |
||||
|
||||
@Override |
||||
int valueSize() { |
||||
return 0; |
||||
} |
||||
|
||||
@Override |
||||
void writeValue(ReftableOutputStream os) { |
||||
// Nothing in a delete log record.
|
||||
} |
||||
} |
||||
|
||||
static class LogEntry extends Entry { |
||||
final ObjectId oldId; |
||||
final ObjectId newId; |
||||
final long timeSecs; |
||||
final short tz; |
||||
final byte[] name; |
||||
final byte[] email; |
||||
final byte[] msg; |
||||
|
||||
LogEntry(String refName, long updateIndex, PersonIdent who, |
||||
ObjectId oldId, ObjectId newId, String message) { |
||||
super(key(refName, updateIndex)); |
||||
|
||||
this.oldId = oldId; |
||||
this.newId = newId; |
||||
this.timeSecs = who.getWhen().getTime() / 1000L; |
||||
this.tz = (short) who.getTimeZoneOffset(); |
||||
this.name = who.getName().getBytes(UTF_8); |
||||
this.email = who.getEmailAddress().getBytes(UTF_8); |
||||
this.msg = message.getBytes(UTF_8); |
||||
} |
||||
|
||||
static byte[] key(String ref, long index) { |
||||
byte[] name = ref.getBytes(UTF_8); |
||||
byte[] key = Arrays.copyOf(name, name.length + 1 + 8); |
||||
NB.encodeInt64(key, key.length - 8, reverseUpdateIndex(index)); |
||||
return key; |
||||
} |
||||
|
||||
@Override |
||||
byte blockType() { |
||||
return LOG_BLOCK_TYPE; |
||||
} |
||||
|
||||
@Override |
||||
int valueType() { |
||||
return LOG_DATA; |
||||
} |
||||
|
||||
@Override |
||||
int valueSize() { |
||||
return 2 * OBJECT_ID_LENGTH |
||||
+ computeVarintSize(name.length) + name.length |
||||
+ computeVarintSize(email.length) + email.length |
||||
+ computeVarintSize(timeSecs) |
||||
+ 2 // tz
|
||||
+ computeVarintSize(msg.length) + msg.length; |
||||
} |
||||
|
||||
@Override |
||||
void writeValue(ReftableOutputStream os) { |
||||
os.writeId(oldId); |
||||
os.writeId(newId); |
||||
os.writeVarintString(name); |
||||
os.writeVarintString(email); |
||||
os.writeVarint(timeSecs); |
||||
os.writeInt16(tz); |
||||
os.writeVarintString(msg); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,216 @@
|
||||
/* |
||||
* Copyright (C) 2017, Google Inc. |
||||
* and other copyright owners as documented in the project's IP log. |
||||
* |
||||
* This program and the accompanying materials are made available |
||||
* under the terms of the Eclipse Distribution License v1.0 which |
||||
* accompanies this distribution, is reproduced below, and is |
||||
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
||||
* |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or |
||||
* without modification, are permitted provided that the following |
||||
* conditions are met: |
||||
* |
||||
* - Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* |
||||
* - Redistributions in binary form must reproduce the above |
||||
* copyright notice, this list of conditions and the following |
||||
* disclaimer in the documentation and/or other materials provided |
||||
* with the distribution. |
||||
* |
||||
* - Neither the name of the Eclipse Foundation, Inc. nor the |
||||
* names of its contributors may be used to endorse or promote |
||||
* products derived from this software without specific prior |
||||
* written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
package org.eclipse.jgit.internal.storage.reftable; |
||||
|
||||
import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.MAX_BLOCK_SIZE; |
||||
|
||||
import org.eclipse.jgit.lib.Config; |
||||
import org.eclipse.jgit.lib.Repository; |
||||
|
||||
/** Configuration used by a reftable writer when constructing the stream. */ |
||||
public class ReftableConfig { |
||||
private int refBlockSize = 4 << 10; |
||||
private int logBlockSize; |
||||
private int restartInterval; |
||||
private int maxIndexLevels; |
||||
private boolean alignBlocks = true; |
||||
private boolean indexObjects = true; |
||||
|
||||
/** Create a default configuration. */ |
||||
public ReftableConfig() { |
||||
} |
||||
|
||||
/** |
||||
* Create a configuration honoring the repository's settings. |
||||
* |
||||
* @param db |
||||
* the repository to read settings from. The repository is not |
||||
* retained by the new configuration, instead its settings are |
||||
* copied during the constructor. |
||||
*/ |
||||
public ReftableConfig(Repository db) { |
||||
fromConfig(db.getConfig()); |
||||
} |
||||
|
||||
/** |
||||
* Create a configuration honoring settings in a {@link Config}. |
||||
* |
||||
* @param cfg |
||||
* the source to read settings from. The source is not retained |
||||
* by the new configuration, instead its settings are copied |
||||
* during the constructor. |
||||
*/ |
||||
public ReftableConfig(Config cfg) { |
||||
fromConfig(cfg); |
||||
} |
||||
|
||||
/** |
||||
* Copy an existing configuration to a new instance. |
||||
* |
||||
* @param cfg |
||||
* the source configuration to copy from. |
||||
*/ |
||||
public ReftableConfig(ReftableConfig cfg) { |
||||
this.refBlockSize = cfg.refBlockSize; |
||||
this.logBlockSize = cfg.logBlockSize; |
||||
this.restartInterval = cfg.restartInterval; |
||||
this.maxIndexLevels = cfg.maxIndexLevels; |
||||
this.alignBlocks = cfg.alignBlocks; |
||||
this.indexObjects = cfg.indexObjects; |
||||
} |
||||
|
||||
/** @return desired output block size for references, in bytes */ |
||||
public int getRefBlockSize() { |
||||
return refBlockSize; |
||||
} |
||||
|
||||
/** |
||||
* @param szBytes |
||||
* desired output block size for references, in bytes. |
||||
*/ |
||||
public void setRefBlockSize(int szBytes) { |
||||
if (szBytes > MAX_BLOCK_SIZE) { |
||||
throw new IllegalArgumentException(); |
||||
} |
||||
refBlockSize = Math.max(0, szBytes); |
||||
} |
||||
|
||||
/** |
||||
* @return desired output block size for log entries, in bytes. If 0 the |
||||
* writer will default to {@code 2 * getRefBlockSize()}. |
||||
*/ |
||||
public int getLogBlockSize() { |
||||
return logBlockSize; |
||||
} |
||||
|
||||
/** |
||||
* @param szBytes |
||||
* desired output block size for log entries, in bytes. If 0 will |
||||
* default to {@code 2 * getRefBlockSize()}. |
||||
*/ |
||||
public void setLogBlockSize(int szBytes) { |
||||
if (szBytes > MAX_BLOCK_SIZE) { |
||||
throw new IllegalArgumentException(); |
||||
} |
||||
logBlockSize = Math.max(0, szBytes); |
||||
} |
||||
|
||||
/** @return number of references between binary search markers. */ |
||||
public int getRestartInterval() { |
||||
return restartInterval; |
||||
} |
||||
|
||||
/** |
||||
* @param interval |
||||
* number of references between binary search markers. If |
||||
* {@code interval} is 0 (default), the writer will select a |
||||
* default value based on the block size. |
||||
*/ |
||||
public void setRestartInterval(int interval) { |
||||
restartInterval = Math.max(0, interval); |
||||
} |
||||
|
||||
/** @return maximum depth of the index; 0 for unlimited. */ |
||||
public int getMaxIndexLevels() { |
||||
return maxIndexLevels; |
||||
} |
||||
|
||||
/** |
||||
* @param levels |
||||
* maximum number of levels to use in indexes. Lower levels of |
||||
* the index respect {@link #getRefBlockSize()}, and the highest |
||||
* level may exceed that if the number of levels is limited. |
||||
*/ |
||||
public void setMaxIndexLevels(int levels) { |
||||
maxIndexLevels = Math.max(0, levels); |
||||
} |
||||
|
||||
/** @return {@code true} if the writer should align blocks. */ |
||||
public boolean isAlignBlocks() { |
||||
return alignBlocks; |
||||
} |
||||
|
||||
/** |
||||
* @param align |
||||
* if {@code true} blocks are written aligned to multiples of |
||||
* {@link #getRefBlockSize()}. May increase file size due to NUL |
||||
* padding bytes added between blocks. Default is {@code true}. |
||||
*/ |
||||
public void setAlignBlocks(boolean align) { |
||||
alignBlocks = align; |
||||
} |
||||
|
||||
/** @return {@code true} if the writer should index object to ref. */ |
||||
public boolean isIndexObjects() { |
||||
return indexObjects; |
||||
} |
||||
|
||||
/** |
||||
* @param index |
||||
* if {@code true} the reftable may include additional storage to |
||||
* efficiently map from {@code ObjectId} to reference names. By |
||||
* default, {@code true}. |
||||
*/ |
||||
public void setIndexObjects(boolean index) { |
||||
indexObjects = index; |
||||
} |
||||
|
||||
/** |
||||
* Update properties by setting fields from the configuration. |
||||
* |
||||
* If a property's corresponding variable is not defined in the supplied |
||||
* configuration, then it is left unmodified. |
||||
* |
||||
* @param rc |
||||
* configuration to read properties from. |
||||
*/ |
||||
public void fromConfig(Config rc) { |
||||
refBlockSize = rc.getInt("reftable", "blockSize", refBlockSize); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
logBlockSize = rc.getInt("reftable", "logBlockSize", logBlockSize); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
restartInterval = rc.getInt("reftable", "restartInterval", restartInterval); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
maxIndexLevels = rc.getInt("reftable", "indexLevels", maxIndexLevels); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
alignBlocks = rc.getBoolean("reftable", "alignBlocks", alignBlocks); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
indexObjects = rc.getBoolean("reftable", "indexObjects", indexObjects); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
} |
||||
} |
@ -0,0 +1,85 @@
|
||||
/* |
||||
* Copyright (C) 2017, Google Inc. |
||||
* and other copyright owners as documented in the project's IP log. |
||||
* |
||||
* This program and the accompanying materials are made available |
||||
* under the terms of the Eclipse Distribution License v1.0 which |
||||
* accompanies this distribution, is reproduced below, and is |
||||
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
||||
* |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or |
||||
* without modification, are permitted provided that the following |
||||
* conditions are met: |
||||
* |
||||
* - Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* |
||||
* - Redistributions in binary form must reproduce the above |
||||
* copyright notice, this list of conditions and the following |
||||
* disclaimer in the documentation and/or other materials provided |
||||
* with the distribution. |
||||
* |
||||
* - Neither the name of the Eclipse Foundation, Inc. nor the |
||||
* names of its contributors may be used to endorse or promote |
||||
* products derived from this software without specific prior |
||||
* written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
package org.eclipse.jgit.internal.storage.reftable; |
||||
|
||||
class ReftableConstants { |
||||
static final byte[] FILE_HEADER_MAGIC = { 'R', 'E', 'F', 'T' }; |
||||
static final byte VERSION_1 = (byte) 1; |
||||
|
||||
static final int FILE_HEADER_LEN = 24; |
||||
static final int FILE_FOOTER_LEN = 68; |
||||
|
||||
static final byte FILE_BLOCK_TYPE = 'R'; |
||||
static final byte REF_BLOCK_TYPE = 'r'; |
||||
static final byte OBJ_BLOCK_TYPE = 'o'; |
||||
static final byte LOG_BLOCK_TYPE = 'g'; |
||||
static final byte INDEX_BLOCK_TYPE = 'i'; |
||||
|
||||
static final int VALUE_NONE = 0x0; |
||||
static final int VALUE_1ID = 0x1; |
||||
static final int VALUE_2ID = 0x2; |
||||
static final int VALUE_SYMREF = 0x3; |
||||
static final int VALUE_TYPE_MASK = 0x7; |
||||
|
||||
static final int LOG_NONE = 0x0; |
||||
static final int LOG_DATA = 0x1; |
||||
|
||||
static final int MAX_BLOCK_SIZE = (1 << 24) - 1; |
||||
static final int MAX_RESTARTS = 65535; |
||||
|
||||
static boolean isFileHeaderMagic(byte[] buf, int o, int n) { |
||||
return (n - o) >= FILE_HEADER_MAGIC.length |
||||
&& buf[o + 0] == FILE_HEADER_MAGIC[0] |
||||
&& buf[o + 1] == FILE_HEADER_MAGIC[1] |
||||
&& buf[o + 2] == FILE_HEADER_MAGIC[2] |
||||
&& buf[o + 3] == FILE_HEADER_MAGIC[3]; |
||||
} |
||||
|
||||
static long reverseUpdateIndex(long time) { |
||||
return 0xffffffffffffffffL - time; |
||||
} |
||||
|
||||
private ReftableConstants() { |
||||
} |
||||
} |
@ -0,0 +1,247 @@
|
||||
/* |
||||
* Copyright (C) 2017, Google Inc. |
||||
* and other copyright owners as documented in the project's IP log. |
||||
* |
||||
* This program and the accompanying materials are made available |
||||
* under the terms of the Eclipse Distribution License v1.0 which |
||||
* accompanies this distribution, is reproduced below, and is |
||||
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
||||
* |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or |
||||
* without modification, are permitted provided that the following |
||||
* conditions are met: |
||||
* |
||||
* - Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* |
||||
* - Redistributions in binary form must reproduce the above |
||||
* copyright notice, this list of conditions and the following |
||||
* disclaimer in the documentation and/or other materials provided |
||||
* with the distribution. |
||||
* |
||||
* - Neither the name of the Eclipse Foundation, Inc. nor the |
||||
* names of its contributors may be used to endorse or promote |
||||
* products derived from this software without specific prior |
||||
* written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
package org.eclipse.jgit.internal.storage.reftable; |
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8; |
||||
import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN; |
||||
import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.INDEX_BLOCK_TYPE; |
||||
import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_BLOCK_TYPE; |
||||
import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.OutputStream; |
||||
import java.util.Arrays; |
||||
import java.util.zip.Deflater; |
||||
import java.util.zip.DeflaterOutputStream; |
||||
|
||||
import org.eclipse.jgit.internal.JGitText; |
||||
import org.eclipse.jgit.lib.ObjectId; |
||||
import org.eclipse.jgit.util.NB; |
||||
import org.eclipse.jgit.util.io.CountingOutputStream; |
||||
|
||||
/** |
||||
* Wrapper to assist formatting a reftable to an {@link OutputStream}. |
||||
* <p> |
||||
* Internally buffers at block size boundaries, flushing only complete blocks to |
||||
* the {@code OutputStream}. |
||||
*/ |
||||
class ReftableOutputStream extends OutputStream { |
||||
private final byte[] tmp = new byte[10]; |
||||
private final CountingOutputStream out; |
||||
private final boolean alignBlocks; |
||||
|
||||
private Deflater deflater; |
||||
private DeflaterOutputStream compressor; |
||||
|
||||
private int blockType; |
||||
private int blockSize; |
||||
private int blockStart; |
||||
private byte[] blockBuf; |
||||
private int cur; |
||||
private long paddingUsed; |
||||
|
||||
ReftableOutputStream(OutputStream os, int bs, boolean align) { |
||||
blockSize = bs; |
||||
blockBuf = new byte[bs]; |
||||
alignBlocks = align; |
||||
out = new CountingOutputStream(os); |
||||
} |
||||
|
||||
void setBlockSize(int bs) { |
||||
blockSize = bs; |
||||
} |
||||
|
||||
@Override |
||||
public void write(int b) { |
||||
ensureBytesAvailableInBlockBuf(1); |
||||
blockBuf[cur++] = (byte) b; |
||||
} |
||||
|
||||
@Override |
||||
public void write(byte[] b, int off, int cnt) { |
||||
ensureBytesAvailableInBlockBuf(cnt); |
||||
System.arraycopy(b, off, blockBuf, cur, cnt); |
||||
cur += cnt; |
||||
} |
||||
|
||||
int bytesWrittenInBlock() { |
||||
return cur; |
||||
} |
||||
|
||||
int bytesAvailableInBlock() { |
||||
return blockSize - cur; |
||||
} |
||||
|
||||
long paddingUsed() { |
||||
return paddingUsed; |
||||
} |
||||
|
||||
/** @return bytes flushed; excludes {@link #bytesWrittenInBlock()}. */ |
||||
long size() { |
||||
return out.getCount(); |
||||
} |
||||
|
||||
static int computeVarintSize(long val) { |
||||
int n = 1; |
||||
for (; (val >>>= 7) != 0; n++) { |
||||
val--; |
||||
} |
||||
return n; |
||||
} |
||||
|
||||
void writeVarint(long val) { |
||||
int n = tmp.length; |
||||
tmp[--n] = (byte) (val & 0x7f); |
||||
while ((val >>>= 7) != 0) { |
||||
tmp[--n] = (byte) (0x80 | (--val & 0x7F)); |
||||
} |
||||
write(tmp, n, tmp.length - n); |
||||
} |
||||
|
||||
void writeInt16(int val) { |
||||
ensureBytesAvailableInBlockBuf(2); |
||||
NB.encodeInt16(blockBuf, cur, val); |
||||
cur += 2; |
||||
} |
||||
|
||||
void writeInt24(int val) { |
||||
ensureBytesAvailableInBlockBuf(3); |
||||
NB.encodeInt24(blockBuf, cur, val); |
||||
cur += 3; |
||||
} |
||||
|
||||
void writeId(ObjectId id) { |
||||
ensureBytesAvailableInBlockBuf(OBJECT_ID_LENGTH); |
||||
id.copyRawTo(blockBuf, cur); |
||||
cur += OBJECT_ID_LENGTH; |
||||
} |
||||
|
||||
void writeVarintString(String s) { |
||||
writeVarintString(s.getBytes(UTF_8)); |
||||
} |
||||
|
||||
void writeVarintString(byte[] msg) { |
||||
writeVarint(msg.length); |
||||
write(msg, 0, msg.length); |
||||
} |
||||
|
||||
private void ensureBytesAvailableInBlockBuf(int cnt) { |
||||
if (cur + cnt > blockBuf.length) { |
||||
int n = Math.max(cur + cnt, blockBuf.length * 2); |
||||
blockBuf = Arrays.copyOf(blockBuf, n); |
||||
} |
||||
} |
||||
|
||||
void flushFileHeader() throws IOException { |
||||
if (cur == FILE_HEADER_LEN && out.getCount() == 0) { |
||||
out.write(blockBuf, 0, cur); |
||||
cur = 0; |
||||
} |
||||
} |
||||
|
||||
void beginBlock(byte type) { |
||||
blockType = type; |
||||
blockStart = cur; |
||||
cur += 4; // reserve space for 4-byte block header.
|
||||
} |
||||
|
||||
void flushBlock() throws IOException { |
||||
if (cur > blockSize && blockType != INDEX_BLOCK_TYPE) { |
||||
throw new IOException(JGitText.get().overflowedReftableBlock); |
||||
} |
||||
NB.encodeInt32(blockBuf, blockStart, (blockType << 24) | cur); |
||||
|
||||
if (blockType == LOG_BLOCK_TYPE) { |
||||
// Log blocks are deflated after the block header.
|
||||
out.write(blockBuf, 0, 4); |
||||
if (deflater != null) { |
||||
deflater.reset(); |
||||
} else { |
||||
deflater = new Deflater(Deflater.BEST_COMPRESSION); |
||||
compressor = new DeflaterOutputStream(out, deflater); |
||||
} |
||||
compressor.write(blockBuf, 4, cur - 4); |
||||
compressor.finish(); |
||||
} else { |
||||
// Other blocks are uncompressed.
|
||||
out.write(blockBuf, 0, cur); |
||||
} |
||||
|
||||
cur = 0; |
||||
blockType = 0; |
||||
blockStart = 0; |
||||
} |
||||
|
||||
void padBetweenBlocksToNextBlock() throws IOException { |
||||
if (alignBlocks) { |
||||
long m = size() % blockSize; |
||||
if (m > 0) { |
||||
int pad = blockSize - (int) m; |
||||
ensureBytesAvailableInBlockBuf(pad); |
||||
Arrays.fill(blockBuf, 0, pad, (byte) 0); |
||||
out.write(blockBuf, 0, pad); |
||||
paddingUsed += pad; |
||||
} |
||||
} |
||||
} |
||||
|
||||
int estimatePadBetweenBlocks(int currentBlockSize) { |
||||
if (alignBlocks) { |
||||
long m = (size() + currentBlockSize) % blockSize; |
||||
return m > 0 ? blockSize - (int) m : 0; |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
void finishFile() throws IOException { |
||||
// File footer doesn't need patching for the block start.
|
||||
// Just flush what has been buffered.
|
||||
out.write(blockBuf, 0, cur); |
||||
cur = 0; |
||||
|
||||
if (deflater != null) { |
||||
deflater.end(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,792 @@
|
||||
/* |
||||
* Copyright (C) 2017, Google Inc. |
||||
* and other copyright owners as documented in the project's IP log. |
||||
* |
||||
* This program and the accompanying materials are made available |
||||
* under the terms of the Eclipse Distribution License v1.0 which |
||||
* accompanies this distribution, is reproduced below, and is |
||||
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
||||
* |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or |
||||
* without modification, are permitted provided that the following |
||||
* conditions are met: |
||||
* |
||||
* - Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* |
||||
* - Redistributions in binary form must reproduce the above |
||||
* copyright notice, this list of conditions and the following |
||||
* disclaimer in the documentation and/or other materials provided |
||||
* with the distribution. |
||||
* |
||||
* - Neither the name of the Eclipse Foundation, Inc. nor the |
||||
* names of its contributors may be used to endorse or promote |
||||
* products derived from this software without specific prior |
||||
* written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
package org.eclipse.jgit.internal.storage.reftable; |
||||
|
||||
import static java.lang.Math.log; |
||||
import static org.eclipse.jgit.internal.storage.reftable.BlockWriter.padBetweenBlocks; |
||||
import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_FOOTER_LEN; |
||||
import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN; |
||||
import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_MAGIC; |
||||
import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.INDEX_BLOCK_TYPE; |
||||
import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_BLOCK_TYPE; |
||||
import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.MAX_BLOCK_SIZE; |
||||
import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.MAX_RESTARTS; |
||||
import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.OBJ_BLOCK_TYPE; |
||||
import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.REF_BLOCK_TYPE; |
||||
import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VERSION_1; |
||||
import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.OutputStream; |
||||
import java.util.ArrayList; |
||||
import java.util.Collection; |
||||
import java.util.Collections; |
||||
import java.util.HashSet; |
||||
import java.util.Iterator; |
||||
import java.util.List; |
||||
import java.util.Set; |
||||
import java.util.zip.CRC32; |
||||
|
||||
import org.eclipse.jgit.annotations.Nullable; |
||||
import org.eclipse.jgit.internal.storage.reftable.BlockWriter.DeleteLogEntry; |
||||
import org.eclipse.jgit.internal.storage.reftable.BlockWriter.Entry; |
||||
import org.eclipse.jgit.internal.storage.reftable.BlockWriter.IndexEntry; |
||||
import org.eclipse.jgit.internal.storage.reftable.BlockWriter.LogEntry; |
||||
import org.eclipse.jgit.internal.storage.reftable.BlockWriter.ObjEntry; |
||||
import org.eclipse.jgit.internal.storage.reftable.BlockWriter.RefEntry; |
||||
import org.eclipse.jgit.lib.AbbreviatedObjectId; |
||||
import org.eclipse.jgit.lib.AnyObjectId; |
||||
import org.eclipse.jgit.lib.ObjectId; |
||||
import org.eclipse.jgit.lib.ObjectIdOwnerMap; |
||||
import org.eclipse.jgit.lib.ObjectIdSubclassMap; |
||||
import org.eclipse.jgit.lib.PersonIdent; |
||||
import org.eclipse.jgit.lib.Ref; |
||||
import org.eclipse.jgit.util.LongList; |
||||
import org.eclipse.jgit.util.NB; |
||||
|
||||
/** |
||||
* Writes a reftable formatted file. |
||||
* <p> |
||||
* A reftable can be written in a streaming fashion, provided the caller sorts |
||||
* all references. A {@link ReftableWriter} is single-use, and not thread-safe. |
||||
*/ |
||||
public class ReftableWriter { |
||||
private ReftableConfig config; |
||||
private int refBlockSize; |
||||
private int logBlockSize; |
||||
private int restartInterval; |
||||
private int maxIndexLevels; |
||||
private boolean alignBlocks; |
||||
private boolean indexObjects; |
||||
|
||||
private long minUpdateIndex; |
||||
private long maxUpdateIndex; |
||||
|
||||
private ReftableOutputStream out; |
||||
private ObjectIdSubclassMap<RefList> obj2ref; |
||||
|
||||
private BlockWriter cur; |
||||
private Section refs; |
||||
private Section objs; |
||||
private Section logs; |
||||
private int objIdLen; |
||||
private Stats stats; |
||||
|
||||
/** Initialize a writer with a default configuration. */ |
||||
public ReftableWriter() { |
||||
this(new ReftableConfig()); |
||||
} |
||||
|
||||
/** |
||||
* Initialize a writer with a specific configuration. |
||||
* |
||||
* @param cfg |
||||
* configuration for the writer. |
||||
*/ |
||||
public ReftableWriter(ReftableConfig cfg) { |
||||
config = cfg; |
||||
} |
||||
|
||||
/** |
||||
* @param cfg |
||||
* configuration for the writer. |
||||
* @return {@code this} |
||||
*/ |
||||
public ReftableWriter setConfig(ReftableConfig cfg) { |
||||
this.config = cfg != null ? cfg : new ReftableConfig(); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* @param min |
||||
* the minimum update index for log entries that appear in this |
||||
* reftable. This should be 1 higher than the prior reftable's |
||||
* {@code maxUpdateIndex} if this table will be used in a stack. |
||||
* @return {@code this} |
||||
*/ |
||||
public ReftableWriter setMinUpdateIndex(long min) { |
||||
minUpdateIndex = min; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* @param max |
||||
* the maximum update index for log entries that appear in this |
||||
* reftable. This should be at least 1 higher than the prior |
||||
* reftable's {@code maxUpdateIndex} if this table will be used |
||||
* in a stack. |
||||
* @return {@code this} |
||||
*/ |
||||
public ReftableWriter setMaxUpdateIndex(long max) { |
||||
maxUpdateIndex = max; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Begin writing the reftable. |
||||
* |
||||
* @param os |
||||
* stream to write the table to. Caller is responsible for |
||||
* closing the stream after invoking {@link #finish()}. |
||||
* @return {@code this} |
||||
* @throws IOException |
||||
* if reftable header cannot be written. |
||||
*/ |
||||
public ReftableWriter begin(OutputStream os) throws IOException { |
||||
refBlockSize = config.getRefBlockSize(); |
||||
logBlockSize = config.getLogBlockSize(); |
||||
restartInterval = config.getRestartInterval(); |
||||
maxIndexLevels = config.getMaxIndexLevels(); |
||||
alignBlocks = config.isAlignBlocks(); |
||||
indexObjects = config.isIndexObjects(); |
||||
|
||||
if (refBlockSize <= 0) { |
||||
refBlockSize = 4 << 10; |
||||
} else if (refBlockSize > MAX_BLOCK_SIZE) { |
||||
throw new IllegalArgumentException(); |
||||
} |
||||
if (logBlockSize <= 0) { |
||||
logBlockSize = 2 * refBlockSize; |
||||
} |
||||
if (restartInterval <= 0) { |
||||
restartInterval = refBlockSize < (60 << 10) ? 16 : 64; |
||||
} |
||||
|
||||
out = new ReftableOutputStream(os, refBlockSize, alignBlocks); |
||||
refs = new Section(REF_BLOCK_TYPE); |
||||
if (indexObjects) { |
||||
obj2ref = new ObjectIdSubclassMap<>(); |
||||
} |
||||
writeFileHeader(); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Sort a collection of references and write them to the reftable. |
||||
* |
||||
* @param refsToPack |
||||
* references to sort and write. |
||||
* @return {@code this} |
||||
* @throws IOException |
||||
* if reftable cannot be written. |
||||
*/ |
||||
public ReftableWriter sortAndWriteRefs(Collection<Ref> refsToPack) |
||||
throws IOException { |
||||
Iterator<RefEntry> itr = refsToPack.stream() |
||||
.map(RefEntry::new) |
||||
.sorted(Entry::compare) |
||||
.iterator(); |
||||
while (itr.hasNext()) { |
||||
RefEntry entry = itr.next(); |
||||
long blockPos = refs.write(entry); |
||||
indexRef(entry.ref, blockPos); |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Write one reference to the reftable. |
||||
* <p> |
||||
* References must be passed in sorted order. |
||||
* |
||||
* @param ref |
||||
* the reference to store. |
||||
* @throws IOException |
||||
* if reftable cannot be written. |
||||
*/ |
||||
public void writeRef(Ref ref) throws IOException { |
||||
long blockPos = refs.write(new RefEntry(ref)); |
||||
indexRef(ref, blockPos); |
||||
} |
||||
|
||||
private void indexRef(Ref ref, long blockPos) { |
||||
if (indexObjects && !ref.isSymbolic()) { |
||||
indexId(ref.getObjectId(), blockPos); |
||||
indexId(ref.getPeeledObjectId(), blockPos); |
||||
} |
||||
} |
||||
|
||||
private void indexId(ObjectId id, long blockPos) { |
||||
if (id != null) { |
||||
RefList l = obj2ref.get(id); |
||||
if (l == null) { |
||||
l = new RefList(id); |
||||
obj2ref.add(l); |
||||
} |
||||
l.addBlock(blockPos); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Write one reflog entry to the reftable. |
||||
* <p> |
||||
* Reflog entries must be written in reference name and descending |
||||
* {@code updateIndex} (highest first) order. |
||||
* |
||||
* @param ref |
||||
* name of the reference. |
||||
* @param updateIndex |
||||
* identifier of the transaction that created the log record. The |
||||
* {@code updateIndex} must be unique within the scope of |
||||
* {@code ref}, and must be within the bounds defined by |
||||
* {@code minUpdateIndex <= updateIndex <= maxUpdateIndex}. |
||||
* @param who |
||||
* committer of the reflog entry. |
||||
* @param oldId |
||||
* prior id; pass {@link ObjectId#zeroId()} for creations. |
||||
* @param newId |
||||
* new id; pass {@link ObjectId#zeroId()} for deletions. |
||||
* @param message |
||||
* optional message (may be null). |
||||
* @throws IOException |
||||
* if reftable cannot be written. |
||||
*/ |
||||
public void writeLog(String ref, long updateIndex, PersonIdent who, |
||||
ObjectId oldId, ObjectId newId, @Nullable String message) |
||||
throws IOException { |
||||
String msg = message != null ? message : ""; //$NON-NLS-1$
|
||||
beginLog(); |
||||
logs.write(new LogEntry(ref, updateIndex, who, oldId, newId, msg)); |
||||
} |
||||
|
||||
/** |
||||
* Record deletion of one reflog entry in this reftable. |
||||
* |
||||
* <p> |
||||
* The deletion can shadow an entry stored in a lower table in the stack. |
||||
* This is useful for {@code refs/stash} and dropping an entry from its |
||||
* reflog. |
||||
* <p> |
||||
* Deletion must be properly interleaved in sorted updateIndex order with |
||||
* any other logs written by |
||||
* {@link #writeLog(String, long, PersonIdent, ObjectId, ObjectId, String)}. |
||||
* |
||||
* @param ref |
||||
* the ref to delete (hide) a reflog entry from. |
||||
* @param updateIndex |
||||
* the update index that must be hidden. |
||||
* @throws IOException |
||||
* if reftable cannot be written. |
||||
*/ |
||||
public void deleteLog(String ref, long updateIndex) throws IOException { |
||||
beginLog(); |
||||
logs.write(new DeleteLogEntry(ref, updateIndex)); |
||||
} |
||||
|
||||
private void beginLog() throws IOException { |
||||
if (logs == null) { |
||||
finishRefAndObjSections(); // close prior ref blocks and their index, if present.
|
||||
out.flushFileHeader(); |
||||
out.setBlockSize(logBlockSize); |
||||
logs = new Section(LOG_BLOCK_TYPE); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @return an estimate of the current size in bytes of the reftable, if it |
||||
* was finished right now. Estimate is only accurate if |
||||
* {@link ReftableConfig#setIndexObjects(boolean)} is {@code false} |
||||
* and {@link ReftableConfig#setMaxIndexLevels(int)} is {@code 1}. |
||||
*/ |
||||
public long estimateTotalBytes() { |
||||
long bytes = out.size(); |
||||
if (bytes == 0) { |
||||
bytes += FILE_HEADER_LEN; |
||||
} |
||||
if (cur != null) { |
||||
long curBlockPos = out.size(); |
||||
int sz = cur.currentSize(); |
||||
bytes += sz; |
||||
|
||||
IndexBuilder idx = null; |
||||
if (cur.blockType() == REF_BLOCK_TYPE) { |
||||
idx = refs.idx; |
||||
} else if (cur.blockType() == LOG_BLOCK_TYPE) { |
||||
idx = logs.idx; |
||||
} |
||||
if (idx != null && shouldHaveIndex(idx)) { |
||||
if (idx == refs.idx) { |
||||
bytes += out.estimatePadBetweenBlocks(sz); |
||||
} |
||||
bytes += idx.estimateBytes(curBlockPos); |
||||
} |
||||
} |
||||
bytes += FILE_FOOTER_LEN; |
||||
return bytes; |
||||
} |
||||
|
||||
/** |
||||
* Finish writing the reftable by writing its trailer. |
||||
* |
||||
* @return {@code this} |
||||
* @throws IOException |
||||
* if reftable cannot be written. |
||||
*/ |
||||
public ReftableWriter finish() throws IOException { |
||||
finishRefAndObjSections(); |
||||
finishLogSection(); |
||||
writeFileFooter(); |
||||
out.finishFile(); |
||||
|
||||
stats = new Stats(this, out); |
||||
out = null; |
||||
obj2ref = null; |
||||
cur = null; |
||||
refs = null; |
||||
objs = null; |
||||
logs = null; |
||||
return this; |
||||
} |
||||
|
||||
private void finishRefAndObjSections() throws IOException { |
||||
if (cur != null && cur.blockType() == REF_BLOCK_TYPE) { |
||||
refs.finishSectionMaybeWriteIndex(); |
||||
if (indexObjects && !obj2ref.isEmpty() && refs.idx.bytes > 0) { |
||||
writeObjBlocks(); |
||||
} |
||||
obj2ref = null; |
||||
} |
||||
} |
||||
|
||||
private void writeObjBlocks() throws IOException { |
||||
List<RefList> sorted = sortById(obj2ref); |
||||
obj2ref = null; |
||||
objIdLen = shortestUniqueAbbreviation(sorted); |
||||
|
||||
out.padBetweenBlocksToNextBlock(); |
||||
objs = new Section(OBJ_BLOCK_TYPE); |
||||
objs.entryCnt = sorted.size(); |
||||
for (RefList l : sorted) { |
||||
objs.write(new ObjEntry(objIdLen, l, l.blockPos)); |
||||
} |
||||
objs.finishSectionMaybeWriteIndex(); |
||||
} |
||||
|
||||
private void finishLogSection() throws IOException { |
||||
if (cur != null && cur.blockType() == LOG_BLOCK_TYPE) { |
||||
logs.finishSectionMaybeWriteIndex(); |
||||
} |
||||
} |
||||
|
||||
private boolean shouldHaveIndex(IndexBuilder idx) { |
||||
int threshold; |
||||
if (idx == refs.idx && alignBlocks) { |
||||
threshold = 4; |
||||
} else { |
||||
threshold = 1; |
||||
} |
||||
return idx.entries.size() + (cur != null ? 1 : 0) > threshold; |
||||
} |
||||
|
||||
private void writeFileHeader() { |
||||
byte[] hdr = new byte[FILE_HEADER_LEN]; |
||||
encodeHeader(hdr); |
||||
out.write(hdr, 0, FILE_HEADER_LEN); |
||||
} |
||||
|
||||
private void encodeHeader(byte[] hdr) { |
||||
System.arraycopy(FILE_HEADER_MAGIC, 0, hdr, 0, 4); |
||||
int bs = alignBlocks ? refBlockSize : 0; |
||||
NB.encodeInt32(hdr, 4, (VERSION_1 << 24) | bs); |
||||
NB.encodeInt64(hdr, 8, minUpdateIndex); |
||||
NB.encodeInt64(hdr, 16, maxUpdateIndex); |
||||
} |
||||
|
||||
private void writeFileFooter() { |
||||
int ftrLen = FILE_FOOTER_LEN; |
||||
byte[] ftr = new byte[ftrLen]; |
||||
encodeHeader(ftr); |
||||
|
||||
NB.encodeInt64(ftr, 24, indexPosition(refs)); |
||||
NB.encodeInt64(ftr, 32, (firstBlockPosition(objs) << 5) | objIdLen); |
||||
NB.encodeInt64(ftr, 40, indexPosition(objs)); |
||||
NB.encodeInt64(ftr, 48, firstBlockPosition(logs)); |
||||
NB.encodeInt64(ftr, 56, indexPosition(logs)); |
||||
|
||||
CRC32 crc = new CRC32(); |
||||
crc.update(ftr, 0, ftrLen - 4); |
||||
NB.encodeInt32(ftr, ftrLen - 4, (int) crc.getValue()); |
||||
|
||||
out.write(ftr, 0, ftrLen); |
||||
} |
||||
|
||||
private static long firstBlockPosition(@Nullable Section s) { |
||||
return s != null ? s.firstBlockPosition : 0; |
||||
} |
||||
|
||||
private static long indexPosition(@Nullable Section s) { |
||||
return s != null && s.idx != null ? s.idx.rootPosition : 0; |
||||
} |
||||
|
||||
/** @return statistics of the last written reftable. */ |
||||
public Stats getStats() { |
||||
return stats; |
||||
} |
||||
|
||||
/** Statistics about a written reftable. */ |
||||
public static class Stats { |
||||
private final int refBlockSize; |
||||
private final int logBlockSize; |
||||
private final int restartInterval; |
||||
|
||||
private final long minUpdateIndex; |
||||
private final long maxUpdateIndex; |
||||
|
||||
private final long refCnt; |
||||
private final long objCnt; |
||||
private final int objIdLen; |
||||
private final long logCnt; |
||||
private final long refBytes; |
||||
private final long objBytes; |
||||
private final long logBytes; |
||||
private final long paddingUsed; |
||||
private final long totalBytes; |
||||
|
||||
private final int refIndexSize; |
||||
private final int refIndexLevels; |
||||
private final int objIndexSize; |
||||
private final int objIndexLevels; |
||||
|
||||
Stats(ReftableWriter w, ReftableOutputStream o) { |
||||
refBlockSize = w.refBlockSize; |
||||
logBlockSize = w.logBlockSize; |
||||
restartInterval = w.restartInterval; |
||||
|
||||
minUpdateIndex = w.minUpdateIndex; |
||||
maxUpdateIndex = w.maxUpdateIndex; |
||||
paddingUsed = o.paddingUsed(); |
||||
totalBytes = o.size(); |
||||
|
||||
refCnt = w.refs.entryCnt; |
||||
refBytes = w.refs.bytes; |
||||
|
||||
objCnt = w.objs != null ? w.objs.entryCnt : 0; |
||||
objBytes = w.objs != null ? w.objs.bytes : 0; |
||||
objIdLen = w.objIdLen; |
||||
|
||||
logCnt = w.logs != null ? w.logs.entryCnt : 0; |
||||
logBytes = w.logs != null ? w.logs.bytes : 0; |
||||
|
||||
IndexBuilder refIdx = w.refs.idx; |
||||
refIndexSize = refIdx.bytes; |
||||
refIndexLevels = refIdx.levels; |
||||
|
||||
IndexBuilder objIdx = w.objs != null ? w.objs.idx : null; |
||||
objIndexSize = objIdx != null ? objIdx.bytes : 0; |
||||
objIndexLevels = objIdx != null ? objIdx.levels : 0; |
||||
} |
||||
|
||||
/** @return number of bytes in a ref block. */ |
||||
public int refBlockSize() { |
||||
return refBlockSize; |
||||
} |
||||
|
||||
/** @return number of bytes to compress into a log block. */ |
||||
public int logBlockSize() { |
||||
return logBlockSize; |
||||
} |
||||
|
||||
/** @return number of references between binary search markers. */ |
||||
public int restartInterval() { |
||||
return restartInterval; |
||||
} |
||||
|
||||
/** @return smallest update index contained in this reftable. */ |
||||
public long minUpdateIndex() { |
||||
return minUpdateIndex; |
||||
} |
||||
|
||||
/** @return largest update index contained in this reftable. */ |
||||
public long maxUpdateIndex() { |
||||
return maxUpdateIndex; |
||||
} |
||||
|
||||
/** @return total number of references in the reftable. */ |
||||
public long refCount() { |
||||
return refCnt; |
||||
} |
||||
|
||||
/** @return number of unique objects in the reftable. */ |
||||
public long objCount() { |
||||
return objCnt; |
||||
} |
||||
|
||||
/** @return total number of log records in the reftable. */ |
||||
public long logCount() { |
||||
return logCnt; |
||||
} |
||||
|
||||
/** @return number of bytes for references, including ref index. */ |
||||
public long refBytes() { |
||||
return refBytes; |
||||
} |
||||
|
||||
/** @return number of bytes for objects, including object index. */ |
||||
public long objBytes() { |
||||
return objBytes; |
||||
} |
||||
|
||||
/** @return number of bytes for log, including log index. */ |
||||
public long logBytes() { |
||||
return logBytes; |
||||
} |
||||
|
||||
/** @return total number of bytes in the reftable. */ |
||||
public long totalBytes() { |
||||
return totalBytes; |
||||
} |
||||
|
||||
/** @return bytes of padding used to maintain block alignment. */ |
||||
public long paddingBytes() { |
||||
return paddingUsed; |
||||
} |
||||
|
||||
/** @return number of bytes in the ref index; 0 if no index was used. */ |
||||
public int refIndexSize() { |
||||
return refIndexSize; |
||||
} |
||||
|
||||
/** @return number of levels in the ref index. */ |
||||
public int refIndexLevels() { |
||||
return refIndexLevels; |
||||
} |
||||
|
||||
/** @return number of bytes in the object index; 0 if no index. */ |
||||
public int objIndexSize() { |
||||
return objIndexSize; |
||||
} |
||||
|
||||
/** @return number of levels in the object index. */ |
||||
public int objIndexLevels() { |
||||
return objIndexLevels; |
||||
} |
||||
|
||||
/** |
||||
* @return number of bytes required to uniquely identify all objects in |
||||
* the reftable. Unique abbreviations in hex would be |
||||
* {@code 2 * objIdLength()}. |
||||
*/ |
||||
public int objIdLength() { |
||||
return objIdLen; |
||||
} |
||||
} |
||||
|
||||
private static List<RefList> sortById(ObjectIdSubclassMap<RefList> m) { |
||||
List<RefList> s = new ArrayList<>(m.size()); |
||||
for (RefList l : m) { |
||||
s.add(l); |
||||
} |
||||
Collections.sort(s); |
||||
return s; |
||||
} |
||||
|
||||
private static int shortestUniqueAbbreviation(List<RefList> in) { |
||||
// Estimate minimum number of bytes necessary for unique abbreviations.
|
||||
int bytes = Math.max(2, (int) (log(in.size()) / log(8))); |
||||
Set<AbbreviatedObjectId> tmp = new HashSet<>((int) (in.size() * 0.75f)); |
||||
retry: for (;;) { |
||||
int hexLen = bytes * 2; |
||||
for (ObjectId id : in) { |
||||
AbbreviatedObjectId a = id.abbreviate(hexLen); |
||||
if (!tmp.add(a)) { |
||||
if (++bytes >= OBJECT_ID_LENGTH) { |
||||
return OBJECT_ID_LENGTH; |
||||
} |
||||
tmp.clear(); |
||||
continue retry; |
||||
} |
||||
} |
||||
return bytes; |
||||
} |
||||
} |
||||
|
||||
private static class RefList extends ObjectIdOwnerMap.Entry { |
||||
final LongList blockPos = new LongList(2); |
||||
|
||||
RefList(AnyObjectId id) { |
||||
super(id); |
||||
} |
||||
|
||||
void addBlock(long pos) { |
||||
if (!blockPos.contains(pos)) { |
||||
blockPos.add(pos); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private class Section { |
||||
final IndexBuilder idx; |
||||
final long firstBlockPosition; |
||||
|
||||
long entryCnt; |
||||
long bytes; |
||||
|
||||
Section(byte keyType) { |
||||
idx = new IndexBuilder(keyType); |
||||
firstBlockPosition = out.size(); |
||||
} |
||||
|
||||
long write(BlockWriter.Entry entry) throws IOException { |
||||
if (cur == null) { |
||||
beginBlock(entry); |
||||
} else if (!cur.tryAdd(entry)) { |
||||
flushCurBlock(); |
||||
if (cur.padBetweenBlocks()) { |
||||
out.padBetweenBlocksToNextBlock(); |
||||
} |
||||
beginBlock(entry); |
||||
} |
||||
entryCnt++; |
||||
return out.size(); |
||||
} |
||||
|
||||
private void beginBlock(BlockWriter.Entry entry) |
||||
throws BlockSizeTooSmallException { |
||||
byte blockType = entry.blockType(); |
||||
int bs = out.bytesAvailableInBlock(); |
||||
cur = new BlockWriter(blockType, idx.keyType, bs, restartInterval); |
||||
cur.mustAdd(entry); |
||||
} |
||||
|
||||
void flushCurBlock() throws IOException { |
||||
idx.entries.add(new IndexEntry(cur.lastKey(), out.size())); |
||||
cur.writeTo(out); |
||||
} |
||||
|
||||
void finishSectionMaybeWriteIndex() throws IOException { |
||||
flushCurBlock(); |
||||
cur = null; |
||||
if (shouldHaveIndex(idx)) { |
||||
idx.writeIndex(); |
||||
} |
||||
bytes = out.size() - firstBlockPosition; |
||||
} |
||||
} |
||||
|
||||
private class IndexBuilder { |
||||
final byte keyType; |
||||
List<IndexEntry> entries = new ArrayList<>(); |
||||
long rootPosition; |
||||
int bytes; |
||||
int levels; |
||||
|
||||
IndexBuilder(byte kt) { |
||||
keyType = kt; |
||||
} |
||||
|
||||
int estimateBytes(long curBlockPos) { |
||||
BlockWriter b = new BlockWriter( |
||||
INDEX_BLOCK_TYPE, keyType, |
||||
MAX_BLOCK_SIZE, |
||||
Math.max(restartInterval, entries.size() / MAX_RESTARTS)); |
||||
try { |
||||
for (Entry e : entries) { |
||||
b.mustAdd(e); |
||||
} |
||||
if (cur != null) { |
||||
b.mustAdd(new IndexEntry(cur.lastKey(), curBlockPos)); |
||||
} |
||||
} catch (BlockSizeTooSmallException e) { |
||||
return b.currentSize(); |
||||
} |
||||
return b.currentSize(); |
||||
} |
||||
|
||||
void writeIndex() throws IOException { |
||||
if (padBetweenBlocks(keyType)) { |
||||
out.padBetweenBlocksToNextBlock(); |
||||
} |
||||
long startPos = out.size(); |
||||
writeMultiLevelIndex(entries); |
||||
bytes = (int) (out.size() - startPos); |
||||
entries = null; |
||||
} |
||||
|
||||
private void writeMultiLevelIndex(List<IndexEntry> keys) |
||||
throws IOException { |
||||
levels = 1; |
||||
while (maxIndexLevels == 0 || levels < maxIndexLevels) { |
||||
keys = writeOneLevel(keys); |
||||
if (keys == null) { |
||||
return; |
||||
} |
||||
levels++; |
||||
} |
||||
|
||||
// When maxIndexLevels has restricted the writer, write one
|
||||
// index block with the entire remaining set of keys.
|
||||
BlockWriter b = new BlockWriter( |
||||
INDEX_BLOCK_TYPE, keyType, |
||||
MAX_BLOCK_SIZE, |
||||
Math.max(restartInterval, keys.size() / MAX_RESTARTS)); |
||||
for (Entry e : keys) { |
||||
b.mustAdd(e); |
||||
} |
||||
rootPosition = out.size(); |
||||
b.writeTo(out); |
||||
} |
||||
|
||||
private List<IndexEntry> writeOneLevel(List<IndexEntry> keys) |
||||
throws IOException { |
||||
Section thisLevel = new Section(keyType); |
||||
for (Entry e : keys) { |
||||
thisLevel.write(e); |
||||
} |
||||
if (!thisLevel.idx.entries.isEmpty()) { |
||||
thisLevel.flushCurBlock(); |
||||
if (cur.padBetweenBlocks()) { |
||||
out.padBetweenBlocksToNextBlock(); |
||||
} |
||||
cur = null; |
||||
return thisLevel.idx.entries; |
||||
} |
||||
|
||||
// The current block fit entire level; make it the root.
|
||||
rootPosition = out.size(); |
||||
cur.writeTo(out); |
||||
cur = null; |
||||
return null; |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue