Browse Source
* changes: DfsBlockCache: Update hits to not include contains() Add a listener for changes to a DfsObjDatabase's pack files Expose the reverse index size in the DfsPackDescription Add a DfsPackFile method to get the number of cached bytes Expose the list of pack files in the DfsBlockCache Add a DFS repository description and reference it in each pack Clarify the docstring of DfsBlockCache.reconfigure() DFS: A storage layer for JGitstable-1.2
Shawn Pearce
13 years ago
committed by
Code Review
39 changed files with 8103 additions and 0 deletions
@ -0,0 +1,4 @@
|
||||
cannotReadIndex=Cannot read index {0} |
||||
shortReadOfBlock=Short read of block at {0} in pack {1}; expected {2} bytes, received only {3} |
||||
shortReadOfIndex=Short read of index {0} |
||||
willNotStoreEmptyPack=Cannot store empty pack |
@ -0,0 +1,194 @@
|
||||
/* |
||||
* Copyright (C) 2011, 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.storage.dfs; |
||||
|
||||
import java.lang.ref.SoftReference; |
||||
|
||||
/** |
||||
* Caches recently used objects for {@link DfsReader}. |
||||
* <p> |
||||
* This cache is not thread-safe. Each reader should have its own cache. |
||||
*/ |
||||
final class DeltaBaseCache { |
||||
private static final int TABLE_BITS = 10; |
||||
|
||||
private static final int MASK_BITS = 32 - TABLE_BITS; |
||||
|
||||
private static int hash(long position) { |
||||
return (((int) position) << MASK_BITS) >>> MASK_BITS; |
||||
} |
||||
|
||||
private int maxByteCount; |
||||
|
||||
private final Slot[] table; |
||||
|
||||
private Slot lruHead; |
||||
|
||||
private Slot lruTail; |
||||
|
||||
private int curByteCount; |
||||
|
||||
DeltaBaseCache(DfsReader reader) { |
||||
DfsReaderOptions options = reader.getOptions(); |
||||
maxByteCount = options.getDeltaBaseCacheLimit(); |
||||
table = new Slot[1 << TABLE_BITS]; |
||||
} |
||||
|
||||
Entry get(DfsPackKey key, long position) { |
||||
Slot e = table[hash(position)]; |
||||
for (; e != null; e = e.tableNext) { |
||||
if (e.offset == position && key.equals(e.pack)) { |
||||
Entry buf = e.data.get(); |
||||
if (buf != null) { |
||||
moveToHead(e); |
||||
return buf; |
||||
} |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
void put(DfsPackKey key, long offset, int objectType, byte[] data) { |
||||
if (data.length > maxByteCount) |
||||
return; // Too large to cache.
|
||||
|
||||
curByteCount += data.length; |
||||
releaseMemory(); |
||||
|
||||
int tableIdx = hash(offset); |
||||
Slot e = new Slot(key, offset, data.length); |
||||
e.data = new SoftReference<Entry>(new Entry(data, objectType)); |
||||
e.tableNext = table[tableIdx]; |
||||
table[tableIdx] = e; |
||||
moveToHead(e); |
||||
} |
||||
|
||||
private void releaseMemory() { |
||||
while (curByteCount > maxByteCount && lruTail != null) { |
||||
Slot currOldest = lruTail; |
||||
Slot nextOldest = currOldest.lruPrev; |
||||
|
||||
curByteCount -= currOldest.size; |
||||
unlink(currOldest); |
||||
removeFromTable(currOldest); |
||||
|
||||
if (nextOldest == null) |
||||
lruHead = null; |
||||
else |
||||
nextOldest.lruNext = null; |
||||
lruTail = nextOldest; |
||||
} |
||||
} |
||||
|
||||
private void removeFromTable(Slot e) { |
||||
int tableIdx = hash(e.offset); |
||||
Slot p = table[tableIdx]; |
||||
|
||||
if (p == e) { |
||||
table[tableIdx] = e.tableNext; |
||||
return; |
||||
} |
||||
|
||||
for (; p != null; p = p.tableNext) { |
||||
if (p.tableNext == e) { |
||||
p.tableNext = e.tableNext; |
||||
return; |
||||
} |
||||
} |
||||
} |
||||
|
||||
private void moveToHead(final Slot e) { |
||||
unlink(e); |
||||
e.lruPrev = null; |
||||
e.lruNext = lruHead; |
||||
if (lruHead != null) |
||||
lruHead.lruPrev = e; |
||||
else |
||||
lruTail = e; |
||||
lruHead = e; |
||||
} |
||||
|
||||
private void unlink(final Slot e) { |
||||
Slot prev = e.lruPrev; |
||||
Slot next = e.lruNext; |
||||
|
||||
if (prev != null) |
||||
prev.lruNext = next; |
||||
if (next != null) |
||||
next.lruPrev = prev; |
||||
} |
||||
|
||||
static class Entry { |
||||
final byte[] data; |
||||
|
||||
final int type; |
||||
|
||||
Entry(final byte[] aData, final int aType) { |
||||
data = aData; |
||||
type = aType; |
||||
} |
||||
} |
||||
|
||||
private static class Slot { |
||||
final DfsPackKey pack; |
||||
|
||||
final long offset; |
||||
|
||||
final int size; |
||||
|
||||
Slot tableNext; |
||||
|
||||
Slot lruPrev; |
||||
|
||||
Slot lruNext; |
||||
|
||||
SoftReference<Entry> data; |
||||
|
||||
Slot(DfsPackKey key, long offset, int size) { |
||||
this.pack = key; |
||||
this.offset = offset; |
||||
this.size = size; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,155 @@
|
||||
/* |
||||
* Copyright (C) 2008-2011, Google Inc. |
||||
* Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com> |
||||
* Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org> |
||||
* 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.storage.dfs; |
||||
|
||||
import java.io.IOException; |
||||
import java.security.MessageDigest; |
||||
import java.util.zip.CRC32; |
||||
import java.util.zip.DataFormatException; |
||||
import java.util.zip.Inflater; |
||||
|
||||
import org.eclipse.jgit.storage.pack.PackOutputStream; |
||||
|
||||
/** A cached slice of a {@link DfsPackFile}. */ |
||||
final class DfsBlock { |
||||
/** |
||||
* Size in bytes to pass to {@link Inflater} at a time. |
||||
* <p> |
||||
* Blocks can be large (for example 1 MiB), while compressed objects inside |
||||
* of them are very small (for example less than 100 bytes for a delta). JNI |
||||
* forces the data supplied to the Inflater to be copied during setInput(), |
||||
* so use a smaller stride to reduce the risk that too much unnecessary was |
||||
* moved into the native layer. |
||||
*/ |
||||
private static final int INFLATE_STRIDE = 512; |
||||
|
||||
final DfsPackKey pack; |
||||
|
||||
final long start; |
||||
|
||||
final long end; |
||||
|
||||
private final byte[] block; |
||||
|
||||
DfsBlock(DfsPackKey p, long pos, byte[] buf) { |
||||
pack = p; |
||||
start = pos; |
||||
end = pos + buf.length; |
||||
block = buf; |
||||
} |
||||
|
||||
int size() { |
||||
return block.length; |
||||
} |
||||
|
||||
int remaining(long pos) { |
||||
int ptr = (int) (pos - start); |
||||
return block.length - ptr; |
||||
} |
||||
|
||||
boolean contains(DfsPackKey want, long pos) { |
||||
return pack == want && start <= pos && pos < end; |
||||
} |
||||
|
||||
int copy(long pos, byte[] dstbuf, int dstoff, int cnt) { |
||||
int ptr = (int) (pos - start); |
||||
return copy(ptr, dstbuf, dstoff, cnt); |
||||
} |
||||
|
||||
int copy(int p, byte[] b, int o, int n) { |
||||
n = Math.min(block.length - p, n); |
||||
System.arraycopy(block, p, b, o, n); |
||||
return n; |
||||
} |
||||
|
||||
int inflate(Inflater inf, long pos, byte[] dstbuf, int dstoff) |
||||
throws DataFormatException { |
||||
int ptr = (int) (pos - start); |
||||
int in = Math.min(INFLATE_STRIDE, block.length - ptr); |
||||
if (dstoff < dstbuf.length) |
||||
in = Math.min(in, dstbuf.length - dstoff); |
||||
inf.setInput(block, ptr, in); |
||||
|
||||
for (;;) { |
||||
int out = inf.inflate(dstbuf, dstoff, dstbuf.length - dstoff); |
||||
if (out == 0) { |
||||
if (inf.needsInput()) { |
||||
ptr += in; |
||||
in = Math.min(INFLATE_STRIDE, block.length - ptr); |
||||
if (in == 0) |
||||
return dstoff; |
||||
inf.setInput(block, ptr, in); |
||||
continue; |
||||
} |
||||
return dstoff; |
||||
} |
||||
dstoff += out; |
||||
} |
||||
} |
||||
|
||||
void crc32(CRC32 out, long pos, int cnt) { |
||||
int ptr = (int) (pos - start); |
||||
out.update(block, ptr, cnt); |
||||
} |
||||
|
||||
void write(PackOutputStream out, long pos, int cnt, MessageDigest digest) |
||||
throws IOException { |
||||
int ptr = (int) (pos - start); |
||||
out.write(block, ptr, cnt); |
||||
if (digest != null) |
||||
digest.update(block, ptr, cnt); |
||||
} |
||||
|
||||
void check(Inflater inf, byte[] tmp, long pos, int cnt) |
||||
throws DataFormatException { |
||||
// Unlike inflate() above the exact byte count is known by the caller.
|
||||
// Push all of it in a single invocation to avoid unnecessary loops.
|
||||
//
|
||||
inf.setInput(block, (int) (pos - start), cnt); |
||||
while (inf.inflate(tmp, 0, tmp.length) > 0) |
||||
continue; |
||||
} |
||||
} |
@ -0,0 +1,595 @@
|
||||
/* |
||||
* Copyright (C) 2008-2011, Google Inc. |
||||
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> |
||||
* 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.storage.dfs; |
||||
|
||||
import java.io.IOException; |
||||
import java.util.ArrayList; |
||||
import java.util.Collection; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.concurrent.ConcurrentHashMap; |
||||
import java.util.concurrent.ThreadPoolExecutor; |
||||
import java.util.concurrent.atomic.AtomicLong; |
||||
import java.util.concurrent.atomic.AtomicReferenceArray; |
||||
import java.util.concurrent.locks.ReentrantLock; |
||||
|
||||
import org.eclipse.jgit.JGitText; |
||||
|
||||
/** |
||||
* Caches slices of a {@link DfsPackFile} in memory for faster read access. |
||||
* <p> |
||||
* The DfsBlockCache serves as a Java based "buffer cache", loading segments of |
||||
* a DfsPackFile into the JVM heap prior to use. As JGit often wants to do reads |
||||
* of only tiny slices of a file, the DfsBlockCache tries to smooth out these |
||||
* tiny reads into larger block-sized IO operations. |
||||
* <p> |
||||
* Whenever a cache miss occurs, loading is invoked by exactly one thread for |
||||
* the given <code>(DfsPackKey,position)</code> key tuple. This is ensured by an |
||||
* array of locks, with the tuple hashed to a lock instance. |
||||
* <p> |
||||
* Its too expensive during object access to be accurate with a least recently |
||||
* used (LRU) algorithm. Strictly ordering every read is a lot of overhead that |
||||
* typically doesn't yield a corresponding benefit to the application. This |
||||
* cache implements a clock replacement algorithm, giving each block one chance |
||||
* to have been accessed during a sweep of the cache to save itself from |
||||
* eviction. |
||||
* <p> |
||||
* Entities created by the cache are held under hard references, preventing the |
||||
* Java VM from clearing anything. Blocks are discarded by the replacement |
||||
* algorithm when adding a new block would cause the cache to exceed its |
||||
* configured maximum size. |
||||
* <p> |
||||
* The key tuple is passed through to methods as a pair of parameters rather |
||||
* than as a single Object, thus reducing the transient memory allocations of |
||||
* callers. It is more efficient to avoid the allocation, as we can't be 100% |
||||
* sure that a JIT would be able to stack-allocate a key tuple. |
||||
* <p> |
||||
* The internal hash table does not expand at runtime, instead it is fixed in |
||||
* size at cache creation time. The internal lock table used to gate load |
||||
* invocations is also fixed in size. |
||||
*/ |
||||
public final class DfsBlockCache { |
||||
private static volatile DfsBlockCache cache; |
||||
|
||||
static { |
||||
reconfigure(new DfsBlockCacheConfig()); |
||||
} |
||||
|
||||
/** |
||||
* Modify the configuration of the window cache. |
||||
* <p> |
||||
* The new configuration is applied immediately, and the existing cache is |
||||
* cleared. |
||||
* |
||||
* @param cfg |
||||
* the new window cache configuration. |
||||
* @throws IllegalArgumentException |
||||
* the cache configuration contains one or more invalid |
||||
* settings, usually too low of a limit. |
||||
*/ |
||||
public static void reconfigure(DfsBlockCacheConfig cfg) { |
||||
DfsBlockCache nc = new DfsBlockCache(cfg); |
||||
DfsBlockCache oc = cache; |
||||
cache = nc; |
||||
|
||||
if (oc != null) { |
||||
if (oc.readAheadService != null) |
||||
oc.readAheadService.shutdown(); |
||||
for (DfsPackFile pack : oc.getPackFiles()) |
||||
pack.key.cachedSize.set(0); |
||||
} |
||||
} |
||||
|
||||
/** @return the currently active DfsBlockCache. */ |
||||
public static DfsBlockCache getInstance() { |
||||
return cache; |
||||
} |
||||
|
||||
/** Number of entries in {@link #table}. */ |
||||
private final int tableSize; |
||||
|
||||
/** Hash bucket directory; entries are chained below. */ |
||||
private final AtomicReferenceArray<HashEntry> table; |
||||
|
||||
/** Locks to prevent concurrent loads for same (PackFile,position). */ |
||||
private final ReentrantLock[] loadLocks; |
||||
|
||||
/** Maximum number of bytes the cache should hold. */ |
||||
private final long maxBytes; |
||||
|
||||
/** |
||||
* Suggested block size to read from pack files in. |
||||
* <p> |
||||
* If a pack file does not have a native block size, this size will be used. |
||||
* <p> |
||||
* If a pack file has a native size, a whole multiple of the native size |
||||
* will be used until it matches this size. |
||||
*/ |
||||
private final int blockSize; |
||||
|
||||
/** As {@link #blockSize} is a power of 2, bits to shift for a / blockSize. */ |
||||
private final int blockSizeShift; |
||||
|
||||
/** Number of bytes to read-ahead from current read position. */ |
||||
private final int readAheadLimit; |
||||
|
||||
/** Thread pool to handle optimistic read-ahead. */ |
||||
private final ThreadPoolExecutor readAheadService; |
||||
|
||||
/** Cache of pack files, indexed by description. */ |
||||
private final Map<DfsPackDescription, DfsPackFile> packCache; |
||||
|
||||
/** View of pack files in the pack cache. */ |
||||
private final Collection<DfsPackFile> packFiles; |
||||
|
||||
/** Number of times a block was found in the cache. */ |
||||
private final AtomicLong statHit; |
||||
|
||||
/** Number of times a block was not found, and had to be loaded. */ |
||||
private final AtomicLong statMiss; |
||||
|
||||
/** Number of blocks evicted due to cache being full. */ |
||||
private volatile long statEvict; |
||||
|
||||
/** Protects the clock and its related data. */ |
||||
private final ReentrantLock clockLock; |
||||
|
||||
/** Current position of the clock. */ |
||||
private Ref clockHand; |
||||
|
||||
/** Number of bytes currently loaded in the cache. */ |
||||
private volatile long liveBytes; |
||||
|
||||
private DfsBlockCache(final DfsBlockCacheConfig cfg) { |
||||
tableSize = tableSize(cfg); |
||||
if (tableSize < 1) |
||||
throw new IllegalArgumentException(JGitText.get().tSizeMustBeGreaterOrEqual1); |
||||
|
||||
table = new AtomicReferenceArray<HashEntry>(tableSize); |
||||
loadLocks = new ReentrantLock[32]; |
||||
for (int i = 0; i < loadLocks.length; i++) |
||||
loadLocks[i] = new ReentrantLock(true /* fair */); |
||||
|
||||
int eb = (int) (tableSize * .1); |
||||
if (64 < eb) |
||||
eb = 64; |
||||
else if (eb < 4) |
||||
eb = 4; |
||||
if (tableSize < eb) |
||||
eb = tableSize; |
||||
|
||||
maxBytes = cfg.getBlockLimit(); |
||||
blockSize = cfg.getBlockSize(); |
||||
blockSizeShift = Integer.numberOfTrailingZeros(blockSize); |
||||
|
||||
clockLock = new ReentrantLock(true /* fair */); |
||||
clockHand = new Ref<Object>(null, -1, 0, null); |
||||
clockHand.next = clockHand; |
||||
|
||||
readAheadLimit = cfg.getReadAheadLimit(); |
||||
readAheadService = cfg.getReadAheadService(); |
||||
|
||||
packCache = new ConcurrentHashMap<DfsPackDescription, DfsPackFile>( |
||||
16, 0.75f, 1); |
||||
packFiles = Collections.unmodifiableCollection(packCache.values()); |
||||
|
||||
statHit = new AtomicLong(); |
||||
statMiss = new AtomicLong(); |
||||
} |
||||
|
||||
/** @return total number of bytes in the cache. */ |
||||
public long getCurrentSize() { |
||||
return liveBytes; |
||||
} |
||||
|
||||
/** @return 0..100, defining how full the cache is. */ |
||||
public long getFillPercentage() { |
||||
return getCurrentSize() * 100 / maxBytes; |
||||
} |
||||
|
||||
/** @return number of requests for items in the cache. */ |
||||
public long getHitCount() { |
||||
return statHit.get(); |
||||
} |
||||
|
||||
/** @return number of requests for items not in the cache. */ |
||||
public long getMissCount() { |
||||
return statMiss.get(); |
||||
} |
||||
|
||||
/** @return total number of requests (hit + miss). */ |
||||
public long getTotalRequestCount() { |
||||
return getHitCount() + getMissCount(); |
||||
} |
||||
|
||||
/** @return 0..100, defining number of cache hits. */ |
||||
public long getHitRatio() { |
||||
long hits = statHit.get(); |
||||
long miss = statMiss.get(); |
||||
long total = hits + miss; |
||||
if (total == 0) |
||||
return 0; |
||||
return hits * 100 / total; |
||||
} |
||||
|
||||
/** @return number of evictions performed due to cache being full. */ |
||||
public long getEvictions() { |
||||
return statEvict; |
||||
} |
||||
|
||||
/** |
||||
* Get the pack files stored in this cache. |
||||
* |
||||
* @return a collection of pack files, some of which may not actually be |
||||
* present; the caller should check the pack's cached size. |
||||
*/ |
||||
public Collection<DfsPackFile> getPackFiles() { |
||||
return packFiles; |
||||
} |
||||
|
||||
DfsPackFile getOrCreate(DfsPackDescription dsc, DfsPackKey key) { |
||||
// TODO This table grows without bound. It needs to clean up
|
||||
// entries that aren't in cache anymore, and aren't being used
|
||||
// by a live DfsObjDatabase reference.
|
||||
synchronized (packCache) { |
||||
DfsPackFile pack = packCache.get(dsc); |
||||
if (pack != null && pack.invalid()) { |
||||
packCache.remove(dsc); |
||||
pack = null; |
||||
} |
||||
if (pack == null) { |
||||
if (key == null) |
||||
key = new DfsPackKey(); |
||||
pack = new DfsPackFile(this, dsc, key); |
||||
packCache.put(dsc, pack); |
||||
} |
||||
return pack; |
||||
} |
||||
} |
||||
|
||||
private int hash(int packHash, long off) { |
||||
return packHash + (int) (off >>> blockSizeShift); |
||||
} |
||||
|
||||
int getBlockSize() { |
||||
return blockSize; |
||||
} |
||||
|
||||
private static int tableSize(final DfsBlockCacheConfig cfg) { |
||||
final int wsz = cfg.getBlockSize(); |
||||
final long limit = cfg.getBlockLimit(); |
||||
if (wsz <= 0) |
||||
throw new IllegalArgumentException(JGitText.get().invalidWindowSize); |
||||
if (limit < wsz) |
||||
throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit); |
||||
return (int) Math.min(5 * (limit / wsz) / 2, Integer.MAX_VALUE); |
||||
} |
||||
|
||||
/** |
||||
* Lookup a cached object, creating and loading it if it doesn't exist. |
||||
* |
||||
* @param pack |
||||
* the pack that "contains" the cached object. |
||||
* @param position |
||||
* offset within <code>pack</code> of the object. |
||||
* @param ctx |
||||
* current thread's reader. |
||||
* @return the object reference. |
||||
* @throws IOException |
||||
* the reference was not in the cache and could not be loaded. |
||||
*/ |
||||
DfsBlock getOrLoad(DfsPackFile pack, long position, DfsReader ctx) |
||||
throws IOException { |
||||
final long requestedPosition = position; |
||||
position = pack.alignToBlock(position); |
||||
|
||||
DfsPackKey key = pack.key; |
||||
int slot = slot(key, position); |
||||
HashEntry e1 = table.get(slot); |
||||
DfsBlock v = scan(e1, key, position); |
||||
if (v != null) { |
||||
statHit.incrementAndGet(); |
||||
return v; |
||||
} |
||||
|
||||
reserveSpace(blockSize); |
||||
ReentrantLock regionLock = lockFor(key, position); |
||||
regionLock.lock(); |
||||
try { |
||||
HashEntry e2 = table.get(slot); |
||||
if (e2 != e1) { |
||||
v = scan(e2, key, position); |
||||
if (v != null) { |
||||
statHit.incrementAndGet(); |
||||
creditSpace(blockSize); |
||||
return v; |
||||
} |
||||
} |
||||
|
||||
statMiss.incrementAndGet(); |
||||
boolean credit = true; |
||||
try { |
||||
v = pack.readOneBlock(position, ctx); |
||||
credit = false; |
||||
} finally { |
||||
if (credit) |
||||
creditSpace(blockSize); |
||||
} |
||||
if (position != v.start) { |
||||
// The file discovered its blockSize and adjusted.
|
||||
position = v.start; |
||||
slot = slot(key, position); |
||||
e2 = table.get(slot); |
||||
} |
||||
|
||||
key.cachedSize.addAndGet(v.size()); |
||||
Ref<DfsBlock> ref = new Ref<DfsBlock>(key, position, v.size(), v); |
||||
ref.hot = true; |
||||
for (;;) { |
||||
HashEntry n = new HashEntry(clean(e2), ref); |
||||
if (table.compareAndSet(slot, e2, n)) |
||||
break; |
||||
e2 = table.get(slot); |
||||
} |
||||
addToClock(ref, blockSize - v.size()); |
||||
} finally { |
||||
regionLock.unlock(); |
||||
} |
||||
|
||||
// If the block size changed from the default, it is possible the block
|
||||
// that was loaded is the wrong block for the requested position.
|
||||
if (v.contains(pack.key, requestedPosition)) |
||||
return v; |
||||
return getOrLoad(pack, requestedPosition, ctx); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
private void reserveSpace(int reserve) { |
||||
clockLock.lock(); |
||||
long live = liveBytes + reserve; |
||||
if (maxBytes < live) { |
||||
Ref prev = clockHand; |
||||
Ref hand = clockHand.next; |
||||
do { |
||||
if (hand.hot) { |
||||
// Value was recently touched. Clear
|
||||
// hot and give it another chance.
|
||||
hand.hot = false; |
||||
prev = hand; |
||||
hand = hand.next; |
||||
continue; |
||||
} else if (prev == hand) |
||||
break; |
||||
|
||||
// No recent access since last scan, kill
|
||||
// value and remove from clock.
|
||||
Ref dead = hand; |
||||
hand = hand.next; |
||||
prev.next = hand; |
||||
dead.next = null; |
||||
dead.value = null; |
||||
live -= dead.size; |
||||
dead.pack.cachedSize.addAndGet(-dead.size); |
||||
statEvict++; |
||||
} while (maxBytes < live); |
||||
clockHand = prev; |
||||
} |
||||
liveBytes = live; |
||||
clockLock.unlock(); |
||||
} |
||||
|
||||
private void creditSpace(int credit) { |
||||
clockLock.lock(); |
||||
liveBytes -= credit; |
||||
clockLock.unlock(); |
||||
} |
||||
|
||||
private void addToClock(Ref ref, int credit) { |
||||
clockLock.lock(); |
||||
if (credit != 0) |
||||
liveBytes -= credit; |
||||
Ref ptr = clockHand; |
||||
ref.next = ptr.next; |
||||
ptr.next = ref; |
||||
clockHand = ref; |
||||
clockLock.unlock(); |
||||
} |
||||
|
||||
void put(DfsBlock v) { |
||||
put(v.pack, v.start, v.size(), v); |
||||
} |
||||
|
||||
<T> Ref<T> put(DfsPackKey key, long pos, int size, T v) { |
||||
int slot = slot(key, pos); |
||||
HashEntry e1 = table.get(slot); |
||||
Ref<T> ref = scanRef(e1, key, pos); |
||||
if (ref != null) |
||||
return ref; |
||||
|
||||
reserveSpace(size); |
||||
ReentrantLock regionLock = lockFor(key, pos); |
||||
regionLock.lock(); |
||||
try { |
||||
HashEntry e2 = table.get(slot); |
||||
if (e2 != e1) { |
||||
ref = scanRef(e2, key, pos); |
||||
if (ref != null) { |
||||
creditSpace(size); |
||||
return ref; |
||||
} |
||||
} |
||||
|
||||
key.cachedSize.addAndGet(size); |
||||
ref = new Ref<T>(key, pos, size, v); |
||||
ref.hot = true; |
||||
for (;;) { |
||||
HashEntry n = new HashEntry(clean(e2), ref); |
||||
if (table.compareAndSet(slot, e2, n)) |
||||
break; |
||||
e2 = table.get(slot); |
||||
} |
||||
addToClock(ref, 0); |
||||
} finally { |
||||
regionLock.unlock(); |
||||
} |
||||
return ref; |
||||
} |
||||
|
||||
boolean contains(DfsPackKey key, long position) { |
||||
return scan(table.get(slot(key, position)), key, position) != null; |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
<T> T get(DfsPackKey key, long position) { |
||||
T val = (T) scan(table.get(slot(key, position)), key, position); |
||||
if (val == null) |
||||
statMiss.incrementAndGet(); |
||||
else |
||||
statHit.incrementAndGet(); |
||||
return val; |
||||
} |
||||
|
||||
boolean readAhead(ReadableChannel rc, DfsPackKey key, int size, long pos, |
||||
long len, DfsReader ctx) { |
||||
if (!ctx.wantReadAhead() || readAheadLimit <= 0 || readAheadService == null) |
||||
return false; |
||||
|
||||
int cap = readAheadLimit / size; |
||||
long readAheadEnd = pos + readAheadLimit; |
||||
List<ReadAheadTask.BlockFuture> blocks = new ArrayList<ReadAheadTask.BlockFuture>(cap); |
||||
while (pos < readAheadEnd && pos < len) { |
||||
long end = Math.min(pos + size, len); |
||||
if (!contains(key, pos)) |
||||
blocks.add(new ReadAheadTask.BlockFuture(key, pos, end)); |
||||
pos = end; |
||||
} |
||||
if (blocks.isEmpty()) |
||||
return false; |
||||
|
||||
ReadAheadTask task = new ReadAheadTask(this, rc, blocks); |
||||
ReadAheadTask.TaskFuture t = new ReadAheadTask.TaskFuture(task); |
||||
for (ReadAheadTask.BlockFuture b : blocks) |
||||
b.setTask(t); |
||||
readAheadService.execute(t); |
||||
ctx.startedReadAhead(blocks); |
||||
return true; |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
private <T> T scan(HashEntry n, DfsPackKey pack, long position) { |
||||
Ref<T> r = scanRef(n, pack, position); |
||||
return r != null ? r.get() : null; |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
private <T> Ref<T> scanRef(HashEntry n, DfsPackKey pack, long position) { |
||||
for (; n != null; n = n.next) { |
||||
Ref<T> r = n.ref; |
||||
if (r.pack == pack && r.position == position) |
||||
return r.get() != null ? r : null; |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
void remove(DfsPackFile pack) { |
||||
synchronized (packCache) { |
||||
packCache.remove(pack.getPackDescription()); |
||||
} |
||||
} |
||||
|
||||
private int slot(DfsPackKey pack, long position) { |
||||
return (hash(pack.hash, position) >>> 1) % tableSize; |
||||
} |
||||
|
||||
private ReentrantLock lockFor(DfsPackKey pack, long position) { |
||||
return loadLocks[(hash(pack.hash, position) >>> 1) % loadLocks.length]; |
||||
} |
||||
|
||||
private static HashEntry clean(HashEntry top) { |
||||
while (top != null && top.ref.next == null) |
||||
top = top.next; |
||||
if (top == null) |
||||
return null; |
||||
HashEntry n = clean(top.next); |
||||
return n == top.next ? top : new HashEntry(n, top.ref); |
||||
} |
||||
|
||||
private static final class HashEntry { |
||||
/** Next entry in the hash table's chain list. */ |
||||
final HashEntry next; |
||||
|
||||
/** The referenced object. */ |
||||
final Ref ref; |
||||
|
||||
HashEntry(HashEntry n, Ref r) { |
||||
next = n; |
||||
ref = r; |
||||
} |
||||
} |
||||
|
||||
static final class Ref<T> { |
||||
final DfsPackKey pack; |
||||
final long position; |
||||
final int size; |
||||
volatile T value; |
||||
Ref next; |
||||
volatile boolean hot; |
||||
|
||||
Ref(DfsPackKey pack, long position, int size, T v) { |
||||
this.pack = pack; |
||||
this.position = position; |
||||
this.size = size; |
||||
this.value = v; |
||||
} |
||||
|
||||
T get() { |
||||
T v = value; |
||||
if (v != null) |
||||
hot = true; |
||||
return v; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,212 @@
|
||||
/* |
||||
* Copyright (C) 2011, 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.storage.dfs; |
||||
|
||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_CORE_SECTION; |
||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DFS_SECTION; |
||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BLOCK_LIMIT; |
||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BLOCK_SIZE; |
||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_READ_AHEAD_LIMIT; |
||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_READ_AHEAD_THREADS; |
||||
|
||||
import java.util.concurrent.ArrayBlockingQueue; |
||||
import java.util.concurrent.RejectedExecutionHandler; |
||||
import java.util.concurrent.ThreadFactory; |
||||
import java.util.concurrent.ThreadPoolExecutor; |
||||
import java.util.concurrent.TimeUnit; |
||||
import java.util.concurrent.atomic.AtomicInteger; |
||||
|
||||
import org.eclipse.jgit.lib.Config; |
||||
|
||||
/** Configuration parameters for {@link DfsBlockCache}. */ |
||||
public class DfsBlockCacheConfig { |
||||
/** 1024 (number of bytes in one kibibyte/kilobyte) */ |
||||
public static final int KB = 1024; |
||||
|
||||
/** 1024 {@link #KB} (number of bytes in one mebibyte/megabyte) */ |
||||
public static final int MB = 1024 * KB; |
||||
|
||||
private long blockLimit; |
||||
|
||||
private int blockSize; |
||||
|
||||
private int readAheadLimit; |
||||
|
||||
private ThreadPoolExecutor readAheadService; |
||||
|
||||
/** Create a default configuration. */ |
||||
public DfsBlockCacheConfig() { |
||||
setBlockLimit(32 * MB); |
||||
setBlockSize(64 * KB); |
||||
} |
||||
|
||||
/** |
||||
* @return maximum number bytes of heap memory to dedicate to caching pack |
||||
* file data. <b>Default is 32 MB.</b> |
||||
*/ |
||||
public long getBlockLimit() { |
||||
return blockLimit; |
||||
} |
||||
|
||||
/** |
||||
* @param newLimit |
||||
* maximum number bytes of heap memory to dedicate to caching |
||||
* pack file data. |
||||
* @return {@code this} |
||||
*/ |
||||
public DfsBlockCacheConfig setBlockLimit(final long newLimit) { |
||||
blockLimit = newLimit; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* @return size in bytes of a single window mapped or read in from the pack |
||||
* file. <b>Default is 64 KB.</b> |
||||
*/ |
||||
public int getBlockSize() { |
||||
return blockSize; |
||||
} |
||||
|
||||
/** |
||||
* @param newSize |
||||
* size in bytes of a single window read in from the pack file. |
||||
* @return {@code this} |
||||
*/ |
||||
public DfsBlockCacheConfig setBlockSize(final int newSize) { |
||||
blockSize = Math.max(512, newSize); |
||||
return this; |
||||
} |
||||
|
||||
/** @return number of bytes to read ahead sequentially by. */ |
||||
public int getReadAheadLimit() { |
||||
return readAheadLimit; |
||||
} |
||||
|
||||
/** |
||||
* @param newSize |
||||
* new read-ahead limit, in bytes. |
||||
* @return {@code this} |
||||
*/ |
||||
public DfsBlockCacheConfig setReadAheadLimit(final int newSize) { |
||||
readAheadLimit = Math.max(0, newSize); |
||||
return this; |
||||
} |
||||
|
||||
/** @return service to perform read-ahead of sequential blocks. */ |
||||
public ThreadPoolExecutor getReadAheadService() { |
||||
return readAheadService; |
||||
} |
||||
|
||||
/** |
||||
* @param svc |
||||
* service to perform read-ahead of sequential blocks with. If |
||||
* not null the {@link RejectedExecutionHandler} must be managed |
||||
* by the JGit DFS library and not the application. |
||||
* @return {@code this}. |
||||
*/ |
||||
public DfsBlockCacheConfig setReadAheadService(ThreadPoolExecutor svc) { |
||||
if (svc != null) |
||||
svc.setRejectedExecutionHandler(ReadAheadRejectedExecutionHandler.INSTANCE); |
||||
readAheadService = svc; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Update properties by setting fields from the configuration. |
||||
* <p> |
||||
* If a property is not defined in the configuration, then it is left |
||||
* unmodified. |
||||
* |
||||
* @param rc |
||||
* configuration to read properties from. |
||||
* @return {@code this} |
||||
*/ |
||||
public DfsBlockCacheConfig fromConfig(final Config rc) { |
||||
setBlockLimit(rc.getLong( |
||||
CONFIG_CORE_SECTION, |
||||
CONFIG_DFS_SECTION, |
||||
CONFIG_KEY_BLOCK_LIMIT, |
||||
getBlockLimit())); |
||||
|
||||
setBlockSize(rc.getInt( |
||||
CONFIG_CORE_SECTION, |
||||
CONFIG_DFS_SECTION, |
||||
CONFIG_KEY_BLOCK_SIZE, |
||||
getBlockSize())); |
||||
|
||||
setReadAheadLimit(rc.getInt( |
||||
CONFIG_CORE_SECTION, |
||||
CONFIG_DFS_SECTION, |
||||
CONFIG_KEY_READ_AHEAD_LIMIT, |
||||
getReadAheadLimit())); |
||||
|
||||
int readAheadThreads = rc.getInt( |
||||
CONFIG_CORE_SECTION, |
||||
CONFIG_DFS_SECTION, |
||||
CONFIG_KEY_READ_AHEAD_THREADS, |
||||
0); |
||||
|
||||
if (0 < getReadAheadLimit() && 0 < readAheadThreads) { |
||||
setReadAheadService(new ThreadPoolExecutor( |
||||
1, // Minimum number of threads kept alive.
|
||||
readAheadThreads, // Maximum threads active.
|
||||
60, TimeUnit.SECONDS, // Idle threads wait this long before ending.
|
||||
new ArrayBlockingQueue<Runnable>(1), // Do not queue deeply.
|
||||
new ThreadFactory() { |
||||
private final String name = "JGit-DFS-ReadAhead"; |
||||
private final AtomicInteger cnt = new AtomicInteger(); |
||||
private final ThreadGroup group = new ThreadGroup(name); |
||||
|
||||
public Thread newThread(Runnable body) { |
||||
int id = cnt.incrementAndGet(); |
||||
Thread thread = new Thread(group, body, name + "-" + id); |
||||
thread.setDaemon(true); |
||||
thread.setContextClassLoader(getClass().getClassLoader()); |
||||
return thread; |
||||
} |
||||
}, ReadAheadRejectedExecutionHandler.INSTANCE)); |
||||
} |
||||
return this; |
||||
} |
||||
} |
@ -0,0 +1,92 @@
|
||||
/* |
||||
* Copyright (C) 2011, 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.storage.dfs; |
||||
|
||||
import java.io.IOException; |
||||
import java.util.Set; |
||||
|
||||
import org.eclipse.jgit.lib.ObjectId; |
||||
import org.eclipse.jgit.storage.pack.CachedPack; |
||||
import org.eclipse.jgit.storage.pack.ObjectToPack; |
||||
import org.eclipse.jgit.storage.pack.PackOutputStream; |
||||
import org.eclipse.jgit.storage.pack.StoredObjectRepresentation; |
||||
|
||||
/** A DfsPackFile available for reuse as-is. */ |
||||
public class DfsCachedPack extends CachedPack { |
||||
private final DfsPackFile pack; |
||||
|
||||
DfsCachedPack(DfsPackFile pack) { |
||||
this.pack = pack; |
||||
} |
||||
|
||||
/** @return the description of the pack. */ |
||||
public DfsPackDescription getPackDescription() { |
||||
return pack.getPackDescription(); |
||||
} |
||||
|
||||
@Override |
||||
public Set<ObjectId> getTips() { |
||||
return getPackDescription().getTips(); |
||||
} |
||||
|
||||
@Override |
||||
public long getObjectCount() throws IOException { |
||||
return getPackDescription().getObjectCount(); |
||||
} |
||||
|
||||
@Override |
||||
public long getDeltaCount() throws IOException { |
||||
return getPackDescription().getDeltaCount(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean hasObject(ObjectToPack obj, StoredObjectRepresentation rep) { |
||||
return ((DfsObjectRepresentation) rep).pack == pack; |
||||
} |
||||
|
||||
void copyAsIs(PackOutputStream out, boolean validate, DfsReader ctx) |
||||
throws IOException { |
||||
pack.copyPackAsIs(out, validate, ctx); |
||||
} |
||||
} |
@ -0,0 +1,61 @@
|
||||
/* |
||||
* Copyright (C) 2011, 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.storage.dfs; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
import org.eclipse.jgit.errors.ConfigInvalidException; |
||||
import org.eclipse.jgit.lib.StoredConfig; |
||||
|
||||
final class DfsConfig extends StoredConfig { |
||||
@Override |
||||
public void load() throws IOException, ConfigInvalidException { |
||||
clear(); |
||||
} |
||||
|
||||
@Override |
||||
public void save() throws IOException { |
||||
// TODO actually store this configuration.
|
||||
} |
||||
} |
@ -0,0 +1,347 @@
|
||||
/* |
||||
* Copyright (C) 2011, 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.storage.dfs; |
||||
|
||||
import static org.eclipse.jgit.storage.dfs.DfsObjDatabase.PackSource.GC; |
||||
import static org.eclipse.jgit.storage.dfs.DfsObjDatabase.PackSource.UNREACHABLE_GARBAGE; |
||||
|
||||
import java.io.IOException; |
||||
import java.util.ArrayList; |
||||
import java.util.Arrays; |
||||
import java.util.Collections; |
||||
import java.util.HashSet; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
|
||||
import org.eclipse.jgit.lib.AnyObjectId; |
||||
import org.eclipse.jgit.lib.Constants; |
||||
import org.eclipse.jgit.lib.NullProgressMonitor; |
||||
import org.eclipse.jgit.lib.ObjectId; |
||||
import org.eclipse.jgit.lib.ProgressMonitor; |
||||
import org.eclipse.jgit.lib.Ref; |
||||
import org.eclipse.jgit.revwalk.RevWalk; |
||||
import org.eclipse.jgit.storage.dfs.DfsObjDatabase.PackSource; |
||||
import org.eclipse.jgit.storage.file.PackIndex; |
||||
import org.eclipse.jgit.storage.pack.PackConfig; |
||||
import org.eclipse.jgit.storage.pack.PackWriter; |
||||
import org.eclipse.jgit.util.io.CountingOutputStream; |
||||
|
||||
/** Repack and garbage collect a repository. */ |
||||
public class DfsGarbageCollector { |
||||
private final DfsRepository repo; |
||||
|
||||
private final DfsRefDatabase refdb; |
||||
|
||||
private final DfsObjDatabase objdb; |
||||
|
||||
private final List<DfsPackDescription> newPackDesc; |
||||
|
||||
private final List<PackWriter.Statistics> newPackStats; |
||||
|
||||
private final List<DfsPackFile> newPackList; |
||||
|
||||
private DfsReader ctx; |
||||
|
||||
private PackConfig packConfig; |
||||
|
||||
private Map<String, Ref> refsBefore; |
||||
|
||||
private List<DfsPackFile> packsBefore; |
||||
|
||||
private Set<ObjectId> allHeads; |
||||
|
||||
private Set<ObjectId> nonHeads; |
||||
|
||||
/** Sum of object counts in {@link #packsBefore}. */ |
||||
private long objectsBefore; |
||||
|
||||
/** Sum of object counts iN {@link #newPackDesc}. */ |
||||
private long objectsPacked; |
||||
|
||||
private Set<ObjectId> tagTargets; |
||||
|
||||
/** |
||||
* Initialize a garbage collector. |
||||
* |
||||
* @param repository |
||||
* repository objects to be packed will be read from. |
||||
*/ |
||||
public DfsGarbageCollector(DfsRepository repository) { |
||||
repo = repository; |
||||
refdb = repo.getRefDatabase(); |
||||
objdb = repo.getObjectDatabase(); |
||||
newPackDesc = new ArrayList<DfsPackDescription>(4); |
||||
newPackStats = new ArrayList<PackWriter.Statistics>(4); |
||||
newPackList = new ArrayList<DfsPackFile>(4); |
||||
|
||||
packConfig = new PackConfig(repo); |
||||
packConfig.setIndexVersion(2); |
||||
} |
||||
|
||||
/** @return configuration used to generate the new pack file. */ |
||||
public PackConfig getPackConfig() { |
||||
return packConfig; |
||||
} |
||||
|
||||
/** |
||||
* @param newConfig |
||||
* the new configuration to use when creating the pack file. |
||||
* @return {@code this} |
||||
*/ |
||||
public DfsGarbageCollector setPackConfig(PackConfig newConfig) { |
||||
packConfig = newConfig; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Create a single new pack file containing all of the live objects. |
||||
* <p> |
||||
* This method safely decides which packs can be expired after the new pack |
||||
* is created by validating the references have not been modified in an |
||||
* incompatible way. |
||||
* |
||||
* @param pm |
||||
* progress monitor to receive updates on as packing may take a |
||||
* while, depending on the size of the repository. |
||||
* @return true if the repack was successful without race conditions. False |
||||
* if a race condition was detected and the repack should be run |
||||
* again later. |
||||
* @throws IOException |
||||
* a new pack cannot be created. |
||||
*/ |
||||
public boolean pack(ProgressMonitor pm) throws IOException { |
||||
if (pm == null) |
||||
pm = NullProgressMonitor.INSTANCE; |
||||
if (packConfig.getIndexVersion() != 2) |
||||
throw new IllegalStateException("Only index version 2"); |
||||
|
||||
ctx = (DfsReader) objdb.newReader(); |
||||
try { |
||||
refdb.clearCache(); |
||||
objdb.clearCache(); |
||||
|
||||
refsBefore = repo.getAllRefs(); |
||||
packsBefore = Arrays.asList(objdb.getPacks()); |
||||
if (packsBefore.isEmpty()) |
||||
return true; |
||||
|
||||
allHeads = new HashSet<ObjectId>(); |
||||
nonHeads = new HashSet<ObjectId>(); |
||||
tagTargets = new HashSet<ObjectId>(); |
||||
for (Ref ref : refsBefore.values()) { |
||||
if (ref.isSymbolic() || ref.getObjectId() == null) |
||||
continue; |
||||
if (isHead(ref)) |
||||
allHeads.add(ref.getObjectId()); |
||||
else |
||||
nonHeads.add(ref.getObjectId()); |
||||
if (ref.getPeeledObjectId() != null) |
||||
tagTargets.add(ref.getPeeledObjectId()); |
||||
} |
||||
tagTargets.addAll(allHeads); |
||||
|
||||
boolean rollback = true; |
||||
try { |
||||
packHeads(pm); |
||||
packRest(pm); |
||||
packGarbage(pm); |
||||
objdb.commitPack(newPackDesc, toPrune()); |
||||
rollback = false; |
||||
return true; |
||||
} finally { |
||||
if (rollback) |
||||
objdb.rollbackPack(newPackDesc); |
||||
} |
||||
} finally { |
||||
ctx.release(); |
||||
} |
||||
} |
||||
|
||||
/** @return all of the source packs that fed into this compaction. */ |
||||
public List<DfsPackDescription> getSourcePacks() { |
||||
return toPrune(); |
||||
} |
||||
|
||||
/** @return new packs created by this compaction. */ |
||||
public List<DfsPackDescription> getNewPacks() { |
||||
return newPackDesc; |
||||
} |
||||
|
||||
/** @return statistics corresponding to the {@link #getNewPacks()}. */ |
||||
public List<PackWriter.Statistics> getNewPackStatistics() { |
||||
return newPackStats; |
||||
} |
||||
|
||||
private List<DfsPackDescription> toPrune() { |
||||
int cnt = packsBefore.size(); |
||||
List<DfsPackDescription> all = new ArrayList<DfsPackDescription>(cnt); |
||||
for (DfsPackFile pack : packsBefore) |
||||
all.add(pack.getPackDescription()); |
||||
return all; |
||||
} |
||||
|
||||
private void packHeads(ProgressMonitor pm) throws IOException { |
||||
if (allHeads.isEmpty()) |
||||
return; |
||||
|
||||
PackWriter pw = newPackWriter(); |
||||
try { |
||||
pw.preparePack(pm, allHeads, Collections.<ObjectId> emptySet()); |
||||
if (0 < pw.getObjectCount()) |
||||
writePack(GC, pw, pm).setTips(allHeads); |
||||
} finally { |
||||
pw.release(); |
||||
} |
||||
} |
||||
|
||||
private void packRest(ProgressMonitor pm) throws IOException { |
||||
if (nonHeads.isEmpty() || objectsPacked == getObjectsBefore()) |
||||
return; |
||||
|
||||
PackWriter pw = newPackWriter(); |
||||
try { |
||||
for (DfsPackFile pack : newPackList) |
||||
pw.excludeObjects(pack.getPackIndex(ctx)); |
||||
pw.preparePack(pm, nonHeads, allHeads); |
||||
if (0 < pw.getObjectCount()) |
||||
writePack(GC, pw, pm); |
||||
} finally { |
||||
pw.release(); |
||||
} |
||||
} |
||||
|
||||
private void packGarbage(ProgressMonitor pm) throws IOException { |
||||
if (objectsPacked == getObjectsBefore()) |
||||
return; |
||||
|
||||
// TODO(sop) This is ugly. The garbage pack needs to be deleted.
|
||||
List<PackIndex> newIdx = new ArrayList<PackIndex>(newPackList.size()); |
||||
for (DfsPackFile pack : newPackList) |
||||
newIdx.add(pack.getPackIndex(ctx)); |
||||
|
||||
PackWriter pw = newPackWriter(); |
||||
try { |
||||
RevWalk pool = new RevWalk(ctx); |
||||
for (DfsPackFile oldPack : packsBefore) { |
||||
PackIndex oldIdx = oldPack.getPackIndex(ctx); |
||||
pm.beginTask("Finding garbage", (int) oldIdx.getObjectCount()); |
||||
for (PackIndex.MutableEntry ent : oldIdx) { |
||||
pm.update(1); |
||||
ObjectId id = ent.toObjectId(); |
||||
if (pool.lookupOrNull(id) != null || anyIndexHas(newIdx, id)) |
||||
continue; |
||||
|
||||
int type = oldPack.getObjectType(ctx, ent.getOffset()); |
||||
pw.addObject(pool.lookupAny(id, type)); |
||||
} |
||||
pm.endTask(); |
||||
} |
||||
if (0 < pw.getObjectCount()) |
||||
writePack(UNREACHABLE_GARBAGE, pw, pm); |
||||
} finally { |
||||
pw.release(); |
||||
} |
||||
} |
||||
|
||||
private static boolean anyIndexHas(List<PackIndex> list, AnyObjectId id) { |
||||
for (PackIndex idx : list) |
||||
if (idx.hasObject(id)) |
||||
return true; |
||||
return false; |
||||
} |
||||
|
||||
private static boolean isHead(Ref ref) { |
||||
return ref.getName().startsWith(Constants.R_HEADS); |
||||
} |
||||
|
||||
private long getObjectsBefore() { |
||||
if (objectsBefore == 0) { |
||||
for (DfsPackFile p : packsBefore) |
||||
objectsBefore += p.getPackDescription().getObjectCount(); |
||||
} |
||||
return objectsBefore; |
||||
} |
||||
|
||||
private PackWriter newPackWriter() { |
||||
PackWriter pw = new PackWriter(packConfig, ctx); |
||||
pw.setDeltaBaseAsOffset(true); |
||||
pw.setReuseDeltaCommits(false); |
||||
pw.setTagTargets(tagTargets); |
||||
return pw; |
||||
} |
||||
|
||||
private DfsPackDescription writePack(PackSource source, PackWriter pw, |
||||
ProgressMonitor pm) throws IOException { |
||||
DfsOutputStream out; |
||||
DfsPackDescription pack = repo.getObjectDatabase().newPack(source); |
||||
newPackDesc.add(pack); |
||||
|
||||
out = objdb.writePackFile(pack); |
||||
try { |
||||
pw.writePack(pm, pm, out); |
||||
} finally { |
||||
out.close(); |
||||
} |
||||
|
||||
out = objdb.writePackIndex(pack); |
||||
try { |
||||
CountingOutputStream cnt = new CountingOutputStream(out); |
||||
pw.writeIndex(cnt); |
||||
pack.setIndexSize(cnt.getCount()); |
||||
} finally { |
||||
out.close(); |
||||
} |
||||
|
||||
PackWriter.Statistics stats = pw.getStatistics(); |
||||
pack.setPackStats(stats); |
||||
pack.setPackSize(stats.getTotalBytes()); |
||||
pack.setObjectCount(stats.getTotalObjects()); |
||||
pack.setDeltaCount(stats.getTotalDeltas()); |
||||
objectsPacked += stats.getTotalObjects(); |
||||
newPackStats.add(stats); |
||||
newPackList.add(DfsBlockCache.getInstance().getOrCreate(pack, null)); |
||||
return pack; |
||||
} |
||||
} |
@ -0,0 +1,393 @@
|
||||
/* |
||||
* Copyright (C) 2011, 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.storage.dfs; |
||||
|
||||
import java.io.EOFException; |
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.io.OutputStream; |
||||
import java.security.MessageDigest; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.zip.CRC32; |
||||
import java.util.zip.Deflater; |
||||
import java.util.zip.DeflaterOutputStream; |
||||
|
||||
import org.eclipse.jgit.lib.Constants; |
||||
import org.eclipse.jgit.lib.ObjectId; |
||||
import org.eclipse.jgit.lib.ObjectIdOwnerMap; |
||||
import org.eclipse.jgit.lib.ObjectInserter; |
||||
import org.eclipse.jgit.storage.file.PackIndex; |
||||
import org.eclipse.jgit.storage.file.PackIndexWriter; |
||||
import org.eclipse.jgit.transport.PackedObjectInfo; |
||||
import org.eclipse.jgit.util.BlockList; |
||||
import org.eclipse.jgit.util.IO; |
||||
import org.eclipse.jgit.util.NB; |
||||
import org.eclipse.jgit.util.TemporaryBuffer; |
||||
import org.eclipse.jgit.util.io.CountingOutputStream; |
||||
|
||||
/** Inserts objects into the DFS. */ |
||||
public class DfsInserter extends ObjectInserter { |
||||
/** Always produce version 2 indexes, to get CRC data. */ |
||||
private static final int INDEX_VERSION = 2; |
||||
|
||||
private final DfsObjDatabase db; |
||||
|
||||
private List<PackedObjectInfo> objectList; |
||||
private ObjectIdOwnerMap<PackedObjectInfo> objectMap; |
||||
|
||||
private DfsBlockCache cache; |
||||
private DfsPackKey packKey; |
||||
private DfsPackDescription packDsc; |
||||
private PackStream packOut; |
||||
private boolean rollback; |
||||
|
||||
/** |
||||
* Initialize a new inserter. |
||||
* |
||||
* @param db |
||||
* database the inserter writes to. |
||||
*/ |
||||
protected DfsInserter(DfsObjDatabase db) { |
||||
this.db = db; |
||||
} |
||||
|
||||
@Override |
||||
public DfsPackParser newPackParser(InputStream in) throws IOException { |
||||
return new DfsPackParser(db, this, in); |
||||
} |
||||
|
||||
@Override |
||||
public ObjectId insert(int type, byte[] data, int off, int len) |
||||
throws IOException { |
||||
ObjectId id = idFor(type, data, off, len); |
||||
if (objectMap != null && objectMap.contains(id)) |
||||
return id; |
||||
if (db.has(id)) |
||||
return id; |
||||
|
||||
long offset = beginObject(type, len); |
||||
packOut.compress.write(data, off, len); |
||||
packOut.compress.finish(); |
||||
return endObject(id, offset); |
||||
} |
||||
|
||||
@Override |
||||
public ObjectId insert(int type, long len, InputStream in) |
||||
throws IOException { |
||||
byte[] buf = buffer(); |
||||
if (len <= buf.length) { |
||||
IO.readFully(in, buf, 0, (int) len); |
||||
return insert(type, buf, 0, (int) len); |
||||
} |
||||
|
||||
long offset = beginObject(type, len); |
||||
MessageDigest md = digest(); |
||||
md.update(Constants.encodedTypeString(type)); |
||||
md.update((byte) ' '); |
||||
md.update(Constants.encodeASCII(len)); |
||||
md.update((byte) 0); |
||||
|
||||
while (0 < len) { |
||||
int n = in.read(buf, 0, (int) Math.min(buf.length, len)); |
||||
if (n <= 0) |
||||
throw new EOFException(); |
||||
md.update(buf, 0, n); |
||||
packOut.compress.write(buf, 0, n); |
||||
len -= n; |
||||
} |
||||
packOut.compress.finish(); |
||||
return endObject(ObjectId.fromRaw(md.digest()), offset); |
||||
} |
||||
|
||||
@Override |
||||
public void flush() throws IOException { |
||||
if (packDsc == null) |
||||
return; |
||||
|
||||
if (packOut == null) |
||||
throw new IOException(); |
||||
|
||||
byte[] packHash = packOut.writePackFooter(); |
||||
packDsc.setPackSize(packOut.getCount()); |
||||
packOut.close(); |
||||
packOut = null; |
||||
|
||||
sortObjectsById(); |
||||
|
||||
PackIndex index = writePackIndex(packDsc, packHash, objectList); |
||||
db.commitPack(Collections.singletonList(packDsc), null); |
||||
rollback = false; |
||||
|
||||
DfsPackFile p = cache.getOrCreate(packDsc, packKey); |
||||
if (index != null) |
||||
p.setPackIndex(index); |
||||
db.addPack(p); |
||||
clear(); |
||||
} |
||||
|
||||
@Override |
||||
public void release() { |
||||
if (packOut != null) { |
||||
try { |
||||
packOut.close(); |
||||
} catch (IOException err) { |
||||
// Ignore a close failure, the pack should be removed.
|
||||
} finally { |
||||
packOut = null; |
||||
} |
||||
} |
||||
if (rollback && packDsc != null) { |
||||
try { |
||||
db.rollbackPack(Collections.singletonList(packDsc)); |
||||
} finally { |
||||
packDsc = null; |
||||
rollback = false; |
||||
} |
||||
} |
||||
clear(); |
||||
} |
||||
|
||||
private void clear() { |
||||
objectList = null; |
||||
objectMap = null; |
||||
packKey = null; |
||||
packDsc = null; |
||||
} |
||||
|
||||
private long beginObject(int type, long len) throws IOException { |
||||
if (packOut == null) |
||||
beginPack(); |
||||
long offset = packOut.getCount(); |
||||
packOut.beginObject(type, len); |
||||
return offset; |
||||
} |
||||
|
||||
private ObjectId endObject(ObjectId id, long offset) { |
||||
PackedObjectInfo obj = new PackedObjectInfo(id); |
||||
obj.setOffset(offset); |
||||
obj.setCRC((int) packOut.crc32.getValue()); |
||||
objectList.add(obj); |
||||
objectMap.addIfAbsent(obj); |
||||
return id; |
||||
} |
||||
|
||||
private void beginPack() throws IOException { |
||||
objectList = new BlockList<PackedObjectInfo>(); |
||||
objectMap = new ObjectIdOwnerMap<PackedObjectInfo>(); |
||||
cache = DfsBlockCache.getInstance(); |
||||
|
||||
rollback = true; |
||||
packDsc = db.newPack(DfsObjDatabase.PackSource.INSERT); |
||||
packOut = new PackStream(db.writePackFile(packDsc)); |
||||
packKey = new DfsPackKey(); |
||||
|
||||
// Write the header as though it were a single object pack.
|
||||
byte[] buf = packOut.hdrBuf; |
||||
System.arraycopy(Constants.PACK_SIGNATURE, 0, buf, 0, 4); |
||||
NB.encodeInt32(buf, 4, 2); // Always use pack version 2.
|
||||
NB.encodeInt32(buf, 8, 1); // Always assume 1 object.
|
||||
packOut.write(buf, 0, 12); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
private void sortObjectsById() { |
||||
Collections.sort(objectList); |
||||
} |
||||
|
||||
PackIndex writePackIndex(DfsPackDescription pack, byte[] packHash, |
||||
List<PackedObjectInfo> list) throws IOException { |
||||
pack.setObjectCount(list.size()); |
||||
|
||||
// If there are less than 58,000 objects, the entire index fits in under
|
||||
// 2 MiB. Callers will probably need the index immediately, so buffer
|
||||
// the index in process and load from the buffer.
|
||||
TemporaryBuffer.Heap buf = null; |
||||
PackIndex packIndex = null; |
||||
if (list.size() <= 58000) { |
||||
buf = new TemporaryBuffer.Heap(2 << 20); |
||||
index(buf, packHash, list); |
||||
packIndex = PackIndex.read(buf.openInputStream()); |
||||
} |
||||
|
||||
DfsOutputStream os = db.writePackIndex(pack); |
||||
try { |
||||
CountingOutputStream cnt = new CountingOutputStream(os); |
||||
if (buf != null) |
||||
buf.writeTo(cnt, null); |
||||
else |
||||
index(cnt, packHash, list); |
||||
pack.setIndexSize(cnt.getCount()); |
||||
} finally { |
||||
os.close(); |
||||
} |
||||
return packIndex; |
||||
} |
||||
|
||||
private static void index(OutputStream out, byte[] packHash, |
||||
List<PackedObjectInfo> list) throws IOException { |
||||
PackIndexWriter.createVersion(out, INDEX_VERSION).write(list, packHash); |
||||
} |
||||
|
||||
private class PackStream extends OutputStream { |
||||
private final DfsOutputStream out; |
||||
private final MessageDigest md; |
||||
private final byte[] hdrBuf; |
||||
private final Deflater deflater; |
||||
private final int blockSize; |
||||
|
||||
private long currPos; // Position of currBuf[0] in the output stream.
|
||||
private int currPtr; // Number of bytes in currBuf.
|
||||
private byte[] currBuf; |
||||
|
||||
final CRC32 crc32; |
||||
final DeflaterOutputStream compress; |
||||
|
||||
PackStream(DfsOutputStream out) { |
||||
this.out = out; |
||||
|
||||
hdrBuf = new byte[32]; |
||||
md = Constants.newMessageDigest(); |
||||
crc32 = new CRC32(); |
||||
deflater = new Deflater(Deflater.BEST_COMPRESSION); |
||||
compress = new DeflaterOutputStream(this, deflater, 8192); |
||||
|
||||
int size = out.blockSize(); |
||||
if (size <= 0) |
||||
size = cache.getBlockSize(); |
||||
else if (size < cache.getBlockSize()) |
||||
size = (cache.getBlockSize() / size) * size; |
||||
blockSize = size; |
||||
currBuf = new byte[blockSize]; |
||||
} |
||||
|
||||
long getCount() { |
||||
return currPos + currPtr; |
||||
} |
||||
|
||||
void beginObject(int objectType, long length) throws IOException { |
||||
crc32.reset(); |
||||
deflater.reset(); |
||||
write(hdrBuf, 0, encodeTypeSize(objectType, length)); |
||||
} |
||||
|
||||
private int encodeTypeSize(int type, long rawLength) { |
||||
long nextLength = rawLength >>> 4; |
||||
hdrBuf[0] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (type << 4) | (rawLength & 0x0F)); |
||||
rawLength = nextLength; |
||||
int n = 1; |
||||
while (rawLength > 0) { |
||||
nextLength >>>= 7; |
||||
hdrBuf[n++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (rawLength & 0x7F)); |
||||
rawLength = nextLength; |
||||
} |
||||
return n; |
||||
} |
||||
|
||||
@Override |
||||
public void write(final int b) throws IOException { |
||||
hdrBuf[0] = (byte) b; |
||||
write(hdrBuf, 0, 1); |
||||
} |
||||
|
||||
@Override |
||||
public void write(byte[] data, int off, int len) throws IOException { |
||||
crc32.update(data, off, len); |
||||
md.update(data, off, len); |
||||
writeNoHash(data, off, len); |
||||
} |
||||
|
||||
private void writeNoHash(byte[] data, int off, int len) |
||||
throws IOException { |
||||
while (0 < len) { |
||||
int n = Math.min(len, currBuf.length - currPtr); |
||||
if (n == 0) { |
||||
flushBlock(); |
||||
currBuf = new byte[blockSize]; |
||||
continue; |
||||
} |
||||
|
||||
System.arraycopy(data, off, currBuf, currPtr, n); |
||||
off += n; |
||||
len -= n; |
||||
currPtr += n; |
||||
} |
||||
} |
||||
|
||||
private void flushBlock() throws IOException { |
||||
out.write(currBuf, 0, currPtr); |
||||
|
||||
byte[] buf; |
||||
if (currPtr == currBuf.length) |
||||
buf = currBuf; |
||||
else |
||||
buf = copyOf(currBuf, 0, currPtr); |
||||
cache.put(new DfsBlock(packKey, currPos, buf)); |
||||
|
||||
currPos += currPtr; |
||||
currPtr = 0; |
||||
currBuf = null; |
||||
} |
||||
|
||||
private byte[] copyOf(byte[] src, int ptr, int cnt) { |
||||
byte[] dst = new byte[cnt]; |
||||
System.arraycopy(src, ptr, dst, 0, cnt); |
||||
return dst; |
||||
} |
||||
|
||||
byte[] writePackFooter() throws IOException { |
||||
byte[] packHash = md.digest(); |
||||
writeNoHash(packHash, 0, packHash.length); |
||||
if (currPtr != 0) |
||||
flushBlock(); |
||||
return packHash; |
||||
} |
||||
|
||||
@Override |
||||
public void close() throws IOException { |
||||
deflater.end(); |
||||
out.close(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,426 @@
|
||||
/* |
||||
* Copyright (C) 2011, 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.storage.dfs; |
||||
|
||||
import java.io.FileNotFoundException; |
||||
import java.io.IOException; |
||||
import java.util.ArrayList; |
||||
import java.util.Collection; |
||||
import java.util.Collections; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.concurrent.atomic.AtomicReference; |
||||
|
||||
import org.eclipse.jgit.lib.ObjectDatabase; |
||||
import org.eclipse.jgit.lib.ObjectInserter; |
||||
import org.eclipse.jgit.lib.ObjectReader; |
||||
|
||||
/** Manages objects stored in {@link DfsPackFile} on a storage system. */ |
||||
public abstract class DfsObjDatabase extends ObjectDatabase { |
||||
private static final PackList NO_PACKS = new PackList(new DfsPackFile[0]); |
||||
|
||||
/** Sources for a pack file. */ |
||||
public static enum PackSource { |
||||
/** The pack is created by ObjectInserter due to local activity. */ |
||||
INSERT, |
||||
|
||||
/** |
||||
* The pack is created by PackParser due to a network event. |
||||
* <p> |
||||
* A received pack can be from either a push into the repository, or a |
||||
* fetch into the repository, the direction doesn't matter. A received |
||||
* pack was built by the remote Git implementation and may not match the |
||||
* storage layout preferred by this version. Received packs are likely |
||||
* to be either compacted or garbage collected in the future. |
||||
*/ |
||||
RECEIVE, |
||||
|
||||
/** |
||||
* Pack was created by Git garbage collection by this implementation. |
||||
* <p> |
||||
* This source is only used by the {@link DfsGarbageCollector} when it |
||||
* builds a pack file by traversing the object graph and copying all |
||||
* reachable objects into a new pack stream. |
||||
* |
||||
* @see DfsGarbageCollector |
||||
*/ |
||||
GC, |
||||
|
||||
/** |
||||
* The pack was created by compacting multiple packs together. |
||||
* <p> |
||||
* Packs created by compacting multiple packs together aren't nearly as |
||||
* efficient as a fully garbage collected repository, but may save disk |
||||
* space by reducing redundant copies of base objects. |
||||
* |
||||
* @see DfsPackCompactor |
||||
*/ |
||||
COMPACT, |
||||
|
||||
/** |
||||
* Pack was created by Git garbage collection. |
||||
* <p> |
||||
* This pack contains only unreachable garbage that was found during the |
||||
* last GC pass. It is retained in a new pack until it is safe to prune |
||||
* these objects from the repository. |
||||
*/ |
||||
UNREACHABLE_GARBAGE; |
||||
} |
||||
|
||||
private final AtomicReference<PackList> packList; |
||||
|
||||
private final DfsRepository repository; |
||||
|
||||
private DfsReaderOptions readerOptions; |
||||
|
||||
/** |
||||
* Initialize an object database for our repository. |
||||
* |
||||
* @param repository |
||||
* repository owning this object database. |
||||
* |
||||
* @param options |
||||
* how readers should access the object database. |
||||
*/ |
||||
protected DfsObjDatabase(DfsRepository repository, |
||||
DfsReaderOptions options) { |
||||
this.repository = repository; |
||||
this.packList = new AtomicReference<PackList>(NO_PACKS); |
||||
this.readerOptions = options; |
||||
} |
||||
|
||||
/** @return configured reader options, such as read-ahead. */ |
||||
public DfsReaderOptions getReaderOptions() { |
||||
return readerOptions; |
||||
} |
||||
|
||||
@Override |
||||
public ObjectReader newReader() { |
||||
return new DfsReader(this); |
||||
} |
||||
|
||||
@Override |
||||
public ObjectInserter newInserter() { |
||||
return new DfsInserter(this); |
||||
} |
||||
|
||||
/** |
||||
* Scan and list all available pack files in the repository. |
||||
* |
||||
* @return list of available packs. The returned array is shared with the |
||||
* implementation and must not be modified by the caller. |
||||
* @throws IOException |
||||
* the pack list cannot be initialized. |
||||
*/ |
||||
public DfsPackFile[] getPacks() throws IOException { |
||||
return scanPacks(NO_PACKS).packs; |
||||
} |
||||
|
||||
/** @return repository owning this object database. */ |
||||
protected DfsRepository getRepository() { |
||||
return repository; |
||||
} |
||||
|
||||
/** |
||||
* List currently known pack files in the repository, without scanning. |
||||
* |
||||
* @return list of available packs. The returned array is shared with the |
||||
* implementation and must not be modified by the caller. |
||||
*/ |
||||
public DfsPackFile[] getCurrentPacks() { |
||||
return packList.get().packs; |
||||
} |
||||
|
||||
/** |
||||
* Generate a new unique name for a pack file. |
||||
* |
||||
* @param source |
||||
* where the pack stream is created. |
||||
* @return a unique name for the pack file. Must not collide with any other |
||||
* pack file name in the same DFS. |
||||
* @throws IOException |
||||
* a new unique pack description cannot be generated. |
||||
*/ |
||||
protected abstract DfsPackDescription newPack(PackSource source) |
||||
throws IOException; |
||||
|
||||
/** |
||||
* Commit a pack and index pair that was written to the DFS. |
||||
* <p> |
||||
* Committing the pack/index pair makes them visible to readers. The JGit |
||||
* DFS code always writes the pack, then the index. This allows a simple |
||||
* commit process to do nothing if readers always look for both files to |
||||
* exist and the DFS performs atomic creation of the file (e.g. stream to a |
||||
* temporary file and rename to target on close). |
||||
* <p> |
||||
* During pack compaction or GC the new pack file may be replacing other |
||||
* older files. Implementations should remove those older files (if any) as |
||||
* part of the commit of the new file. |
||||
* |
||||
* @param desc |
||||
* description of the new packs. |
||||
* @param replaces |
||||
* if not null, list of packs to remove. |
||||
* @throws IOException |
||||
* the packs cannot be committed. On failure a rollback must |
||||
* also be attempted by the caller. |
||||
*/ |
||||
protected abstract void commitPack(Collection<DfsPackDescription> desc, |
||||
Collection<DfsPackDescription> replaces) throws IOException; |
||||
|
||||
/** |
||||
* Try to rollback a pack creation. |
||||
* <p> |
||||
* JGit DFS always writes the pack first, then the index. If the pack does |
||||
* not yet exist, then neither does the index. A safe DFS implementation |
||||
* would try to remove both files to ensure they are really gone. |
||||
* <p> |
||||
* A rollback does not support failures, as it only occurs when there is |
||||
* already a failure in progress. A DFS implementor may wish to log |
||||
* warnings/error messages when a rollback fails, but should not send new |
||||
* exceptions up the Java callstack. |
||||
* |
||||
* @param desc |
||||
* pack to delete. |
||||
*/ |
||||
protected abstract void rollbackPack(Collection<DfsPackDescription> desc); |
||||
|
||||
/** |
||||
* List the available pack files. |
||||
* <p> |
||||
* The returned list must support random access and must be mutable by the |
||||
* caller. It is sorted in place using the natural sorting of the returned |
||||
* DfsPackDescription objects. |
||||
* |
||||
* @return available packs. May be empty if there are no packs. |
||||
* @throws IOException |
||||
* the packs cannot be listed and the object database is not |
||||
* functional to the caller. |
||||
*/ |
||||
protected abstract List<DfsPackDescription> listPacks() throws IOException; |
||||
|
||||
/** |
||||
* Open a pack file for reading. |
||||
* |
||||
* @param desc |
||||
* description of pack to read. This is an instance previously |
||||
* obtained from {@link #listPacks()}, but not necessarily from |
||||
* the same DfsObjDatabase instance. |
||||
* @return channel to read the pack file. |
||||
* @throws FileNotFoundException |
||||
* the file does not exist. |
||||
* @throws IOException |
||||
* the file cannot be opened. |
||||
*/ |
||||
protected abstract ReadableChannel openPackFile(DfsPackDescription desc) |
||||
throws FileNotFoundException, IOException; |
||||
|
||||
/** |
||||
* Open a pack index for reading. |
||||
* |
||||
* @param desc |
||||
* description of index to read. This is an instance previously |
||||
* obtained from {@link #listPacks()}, but not necessarily from |
||||
* the same DfsObjDatabase instance. |
||||
* @return channel to read the pack file. |
||||
* @throws FileNotFoundException |
||||
* the file does not exist. |
||||
* @throws IOException |
||||
* the file cannot be opened. |
||||
*/ |
||||
protected abstract ReadableChannel openPackIndex(DfsPackDescription desc) |
||||
throws FileNotFoundException, IOException; |
||||
|
||||
/** |
||||
* Open a pack file for writing. |
||||
* |
||||
* @param desc |
||||
* description of pack to write. This is an instance previously |
||||
* obtained from {@link #newPack(PackSource)}. |
||||
* @return channel to write the pack file. |
||||
* @throws IOException |
||||
* the file cannot be opened. |
||||
*/ |
||||
protected abstract DfsOutputStream writePackFile(DfsPackDescription desc) |
||||
throws IOException; |
||||
|
||||
/** |
||||
* Open a pack index for writing. |
||||
* |
||||
* @param desc |
||||
* description of index to write. This is an instance previously |
||||
* obtained from {@link #newPack(PackSource)}. |
||||
* @return channel to write the index file. |
||||
* @throws IOException |
||||
* the file cannot be opened. |
||||
*/ |
||||
protected abstract DfsOutputStream writePackIndex(DfsPackDescription desc) |
||||
throws IOException; |
||||
|
||||
void addPack(DfsPackFile newPack) throws IOException { |
||||
PackList o, n; |
||||
do { |
||||
o = packList.get(); |
||||
if (o == NO_PACKS) { |
||||
// The repository may not have needed any existing objects to
|
||||
// complete the current task of creating a pack (e.g. push of a
|
||||
// pack with no external deltas). Because we don't scan for
|
||||
// newly added packs on missed object lookups, scan now to
|
||||
// make sure all older packs are available in the packList.
|
||||
o = scanPacks(o); |
||||
|
||||
// Its possible the scan identified the pack we were asked to
|
||||
// add, as the pack was already committed via commitPack().
|
||||
// If this is the case return without changing the list.
|
||||
for (DfsPackFile p : o.packs) { |
||||
if (p == newPack) |
||||
return; |
||||
} |
||||
} |
||||
|
||||
DfsPackFile[] packs = new DfsPackFile[1 + o.packs.length]; |
||||
packs[0] = newPack; |
||||
System.arraycopy(o.packs, 0, packs, 1, o.packs.length); |
||||
n = new PackList(packs); |
||||
} while (!packList.compareAndSet(o, n)); |
||||
} |
||||
|
||||
private PackList scanPacks(final PackList original) throws IOException { |
||||
PackList o, n; |
||||
synchronized (packList) { |
||||
do { |
||||
o = packList.get(); |
||||
if (o != original) { |
||||
// Another thread did the scan for us, while we
|
||||
// were blocked on the monitor above.
|
||||
//
|
||||
return o; |
||||
} |
||||
n = scanPacksImpl(o); |
||||
if (n == o) |
||||
return n; |
||||
} while (!packList.compareAndSet(o, n)); |
||||
} |
||||
getRepository().fireEvent(new DfsPacksChangedEvent()); |
||||
return n; |
||||
} |
||||
|
||||
private PackList scanPacksImpl(PackList old) throws IOException { |
||||
DfsBlockCache cache = DfsBlockCache.getInstance(); |
||||
Map<DfsPackDescription, DfsPackFile> forReuse = reuseMap(old); |
||||
List<DfsPackDescription> scanned = listPacks(); |
||||
Collections.sort(scanned); |
||||
|
||||
List<DfsPackFile> list = new ArrayList<DfsPackFile>(scanned.size()); |
||||
boolean foundNew = false; |
||||
for (DfsPackDescription dsc : scanned) { |
||||
DfsPackFile oldPack = forReuse.remove(dsc); |
||||
if (oldPack != null) { |
||||
list.add(oldPack); |
||||
} else { |
||||
list.add(cache.getOrCreate(dsc, null)); |
||||
foundNew = true; |
||||
} |
||||
} |
||||
|
||||
for (DfsPackFile p : forReuse.values()) |
||||
p.close(); |
||||
if (list.isEmpty()) |
||||
return new PackList(NO_PACKS.packs); |
||||
if (!foundNew) |
||||
return old; |
||||
return new PackList(list.toArray(new DfsPackFile[list.size()])); |
||||
} |
||||
|
||||
private static Map<DfsPackDescription, DfsPackFile> reuseMap(PackList old) { |
||||
Map<DfsPackDescription, DfsPackFile> forReuse |
||||
= new HashMap<DfsPackDescription, DfsPackFile>(); |
||||
for (DfsPackFile p : old.packs) { |
||||
if (p.invalid()) { |
||||
// The pack instance is corrupted, and cannot be safely used
|
||||
// again. Do not include it in our reuse map.
|
||||
//
|
||||
p.close(); |
||||
continue; |
||||
} |
||||
|
||||
DfsPackFile prior = forReuse.put(p.getPackDescription(), p); |
||||
if (prior != null) { |
||||
// This should never occur. It should be impossible for us
|
||||
// to have two pack files with the same name, as all of them
|
||||
// came out of the same directory. If it does, we promised to
|
||||
// close any PackFiles we did not reuse, so close the second,
|
||||
// readers are likely to be actively using the first.
|
||||
//
|
||||
forReuse.put(prior.getPackDescription(), prior); |
||||
p.close(); |
||||
} |
||||
} |
||||
return forReuse; |
||||
} |
||||
|
||||
void clearCache() { |
||||
packList.set(NO_PACKS); |
||||
} |
||||
|
||||
@Override |
||||
public void close() { |
||||
// PackList packs = packList.get();
|
||||
packList.set(NO_PACKS); |
||||
|
||||
// TODO Close packs if they aren't cached.
|
||||
// for (DfsPackFile p : packs.packs)
|
||||
// p.close();
|
||||
} |
||||
|
||||
private static final class PackList { |
||||
/** All known packs, sorted. */ |
||||
final DfsPackFile[] packs; |
||||
|
||||
PackList(final DfsPackFile[] packs) { |
||||
this.packs = packs; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,90 @@
|
||||
/* |
||||
* Copyright (C) 2011, 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.storage.dfs; |
||||
|
||||
import org.eclipse.jgit.lib.ObjectId; |
||||
import org.eclipse.jgit.storage.pack.ObjectToPack; |
||||
import org.eclipse.jgit.storage.pack.StoredObjectRepresentation; |
||||
|
||||
class DfsObjectRepresentation extends StoredObjectRepresentation { |
||||
final ObjectToPack object; |
||||
|
||||
DfsPackFile pack; |
||||
|
||||
/** |
||||
* Position of {@link #pack} in the reader's pack list. Lower numbers are |
||||
* newer/more recent packs and less likely to contain the best format for a |
||||
* base object. Higher numbered packs are bigger, more stable, and favored |
||||
* by PackWriter when selecting representations... but only if they come |
||||
* last in the representation ordering. |
||||
*/ |
||||
int packIndex; |
||||
|
||||
long offset; |
||||
|
||||
int format; |
||||
|
||||
long length; |
||||
|
||||
ObjectId baseId; |
||||
|
||||
DfsObjectRepresentation(ObjectToPack object) { |
||||
this.object = object; |
||||
} |
||||
|
||||
@Override |
||||
public int getFormat() { |
||||
return format; |
||||
} |
||||
|
||||
@Override |
||||
public int getWeight() { |
||||
return (int) Math.min(length, Integer.MAX_VALUE); |
||||
} |
||||
|
||||
@Override |
||||
public ObjectId getDeltaBase() { |
||||
return baseId; |
||||
} |
||||
} |
@ -0,0 +1,82 @@
|
||||
/* |
||||
* Copyright (C) 2011, 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.storage.dfs; |
||||
|
||||
import org.eclipse.jgit.revwalk.RevObject; |
||||
import org.eclipse.jgit.storage.pack.ObjectToPack; |
||||
import org.eclipse.jgit.storage.pack.StoredObjectRepresentation; |
||||
|
||||
/** {@link ObjectToPack} for {@link DfsObjDatabase}. */ |
||||
class DfsObjectToPack extends ObjectToPack { |
||||
/** Pack to reuse compressed data from, otherwise null. */ |
||||
DfsPackFile pack; |
||||
|
||||
/** Position of the pack in the reader's pack list. */ |
||||
int packIndex; |
||||
|
||||
/** Offset of the object's header in {@link #pack}. */ |
||||
long offset; |
||||
|
||||
/** Length of the data section of the object. */ |
||||
long length; |
||||
|
||||
DfsObjectToPack(RevObject obj) { |
||||
super(obj); |
||||
} |
||||
|
||||
@Override |
||||
protected void clearReuseAsIs() { |
||||
super.clearReuseAsIs(); |
||||
pack = null; |
||||
} |
||||
|
||||
@Override |
||||
public void select(StoredObjectRepresentation ref) { |
||||
DfsObjectRepresentation ptr = (DfsObjectRepresentation) ref; |
||||
this.pack = ptr.pack; |
||||
this.packIndex = ptr.packIndex; |
||||
this.offset = ptr.offset; |
||||
this.length = ptr.length; |
||||
} |
||||
} |
@ -0,0 +1,98 @@
|
||||
/* |
||||
* Copyright (C) 2011, 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.storage.dfs; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.OutputStream; |
||||
import java.nio.ByteBuffer; |
||||
|
||||
/** |
||||
* Output stream to create a file on the DFS. |
||||
* |
||||
* @see DfsObjDatabase#writePackFile(DfsPackDescription) |
||||
* @see DfsObjDatabase#writePackIndex(DfsPackDescription) |
||||
*/ |
||||
public abstract class DfsOutputStream extends OutputStream { |
||||
/** |
||||
* Get the recommended alignment for writing. |
||||
* <p> |
||||
* Starting a write at multiples of the blockSize is more efficient than |
||||
* starting a write at any other position. If 0 or -1 the channel does not |
||||
* have any specific block size recommendation. |
||||
* <p> |
||||
* Channels should not recommend large block sizes. Sizes up to 1-4 MiB may |
||||
* be reasonable, but sizes above that may be horribly inefficient. |
||||
* |
||||
* @return recommended alignment size for randomly positioned reads. Does |
||||
* not need to be a power of 2. |
||||
*/ |
||||
public int blockSize() { |
||||
return 0; |
||||
} |
||||
|
||||
@Override |
||||
public void write(int b) throws IOException { |
||||
write(new byte[] { (byte) b }); |
||||
} |
||||
|
||||
@Override |
||||
public abstract void write(byte[] buf, int off, int len) throws IOException; |
||||
|
||||
/** |
||||
* Read back a portion of already written data. |
||||
* <p> |
||||
* The writing position of the output stream is not affected by a read. |
||||
* |
||||
* @param position |
||||
* offset to read from. |
||||
* @param buf |
||||
* buffer to populate. Up to {@code buf.remaining()} bytes will |
||||
* be read from {@code position}. |
||||
* @return number of bytes actually read. |
||||
* @throws IOException |
||||
* reading is not supported, or the read cannot be performed due |
||||
* to DFS errors. |
||||
*/ |
||||
public abstract int read(long position, ByteBuffer buf) throws IOException; |
||||
} |
@ -0,0 +1,317 @@
|
||||
/* |
||||
* Copyright (C) 2011, 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.storage.dfs; |
||||
|
||||
import static org.eclipse.jgit.storage.dfs.DfsObjDatabase.PackSource.COMPACT; |
||||
|
||||
import java.io.IOException; |
||||
import java.util.ArrayList; |
||||
import java.util.Collections; |
||||
import java.util.Comparator; |
||||
import java.util.List; |
||||
|
||||
import org.eclipse.jgit.JGitText; |
||||
import org.eclipse.jgit.errors.IncorrectObjectTypeException; |
||||
import org.eclipse.jgit.lib.AnyObjectId; |
||||
import org.eclipse.jgit.lib.NullProgressMonitor; |
||||
import org.eclipse.jgit.lib.ObjectId; |
||||
import org.eclipse.jgit.lib.ProgressMonitor; |
||||
import org.eclipse.jgit.revwalk.RevFlag; |
||||
import org.eclipse.jgit.revwalk.RevObject; |
||||
import org.eclipse.jgit.revwalk.RevWalk; |
||||
import org.eclipse.jgit.storage.file.PackIndex; |
||||
import org.eclipse.jgit.storage.pack.PackConfig; |
||||
import org.eclipse.jgit.storage.pack.PackWriter; |
||||
import org.eclipse.jgit.util.BlockList; |
||||
import org.eclipse.jgit.util.io.CountingOutputStream; |
||||
|
||||
/** |
||||
* Combine several pack files into one pack. |
||||
* <p> |
||||
* The compactor combines several pack files together by including all objects |
||||
* contained in each pack file into the same output pack. If an object appears |
||||
* multiple times, it is only included once in the result. Because the new pack |
||||
* is constructed by enumerating the indexes of the source packs, it is quicker |
||||
* than doing a full repack of the repository, however the result is not nearly |
||||
* as space efficient as new delta compression is disabled. |
||||
* <p> |
||||
* This method is suitable for quickly combining several packs together after |
||||
* receiving a number of small fetch or push operations into a repository, |
||||
* allowing the system to maintain reasonable read performance without expending |
||||
* a lot of time repacking the entire repository. |
||||
*/ |
||||
public class DfsPackCompactor { |
||||
private final DfsRepository repo; |
||||
|
||||
private final List<DfsPackFile> srcPacks; |
||||
|
||||
private final List<DfsPackDescription> newPacks; |
||||
|
||||
private final List<PackWriter.Statistics> newStats; |
||||
|
||||
private int autoAddSize; |
||||
|
||||
/** |
||||
* Initialize a pack compactor. |
||||
* |
||||
* @param repository |
||||
* repository objects to be packed will be read from. |
||||
*/ |
||||
public DfsPackCompactor(DfsRepository repository) { |
||||
repo = repository; |
||||
autoAddSize = 5 * 1024 * 1024; // 5 MiB
|
||||
srcPacks = new ArrayList<DfsPackFile>(); |
||||
newPacks = new ArrayList<DfsPackDescription>(1); |
||||
newStats = new ArrayList<PackWriter.Statistics>(1); |
||||
} |
||||
|
||||
/** |
||||
* Add a pack to be compacted. |
||||
* <p> |
||||
* All of the objects in this pack will be copied into the resulting pack. |
||||
* The resulting pack will order objects according to the source pack's own |
||||
* description ordering (which is based on creation date), and then by the |
||||
* order the objects appear in the source pack. |
||||
* |
||||
* @param pack |
||||
* a pack to combine into the resulting pack. |
||||
* @return {@code this} |
||||
*/ |
||||
public DfsPackCompactor add(DfsPackFile pack) { |
||||
srcPacks.add(pack); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Automatically select packs to be included, and add them. |
||||
* <p> |
||||
* Packs are selected based on size, smaller packs get included while bigger |
||||
* ones are omitted. |
||||
* |
||||
* @return {@code this} |
||||
* @throws IOException |
||||
* existing packs cannot be read. |
||||
*/ |
||||
public DfsPackCompactor autoAdd() throws IOException { |
||||
DfsObjDatabase objdb = repo.getObjectDatabase(); |
||||
for (DfsPackFile pack : objdb.getPacks()) { |
||||
DfsPackDescription d = pack.getPackDescription(); |
||||
if (d.getPackSize() < autoAddSize) |
||||
add(pack); |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Compact the pack files together. |
||||
* |
||||
* @param pm |
||||
* progress monitor to receive updates on as packing may take a |
||||
* while, depending on the size of the repository. |
||||
* @throws IOException |
||||
* the packs cannot be compacted. |
||||
*/ |
||||
public void compact(ProgressMonitor pm) throws IOException { |
||||
if (pm == null) |
||||
pm = NullProgressMonitor.INSTANCE; |
||||
|
||||
DfsObjDatabase objdb = repo.getObjectDatabase(); |
||||
DfsReader ctx = (DfsReader) objdb.newReader(); |
||||
try { |
||||
PackConfig pc = new PackConfig(repo); |
||||
pc.setIndexVersion(2); |
||||
pc.setDeltaCompress(false); |
||||
pc.setReuseDeltas(true); |
||||
pc.setReuseObjects(true); |
||||
|
||||
PackWriter pw = new PackWriter(pc, ctx); |
||||
try { |
||||
pw.setDeltaBaseAsOffset(true); |
||||
pw.setReuseDeltaCommits(false); |
||||
|
||||
addObjectsToPack(pw, ctx, pm); |
||||
if (pw.getObjectCount() == 0) |
||||
return; |
||||
|
||||
boolean rollback = true; |
||||
DfsPackDescription pack = objdb.newPack(COMPACT); |
||||
try { |
||||
writePack(objdb, pack, pw, pm); |
||||
writeIndex(objdb, pack, pw); |
||||
|
||||
PackWriter.Statistics stats = pw.getStatistics(); |
||||
pw.release(); |
||||
pw = null; |
||||
|
||||
pack.setPackStats(stats); |
||||
objdb.commitPack(Collections.singletonList(pack), toPrune()); |
||||
newPacks.add(pack); |
||||
newStats.add(stats); |
||||
rollback = false; |
||||
} finally { |
||||
if (rollback) |
||||
objdb.rollbackPack(Collections.singletonList(pack)); |
||||
} |
||||
} finally { |
||||
if (pw != null) |
||||
pw.release(); |
||||
} |
||||
} finally { |
||||
ctx.release(); |
||||
} |
||||
} |
||||
|
||||
/** @return all of the source packs that fed into this compaction. */ |
||||
public List<DfsPackDescription> getSourcePacks() { |
||||
return toPrune(); |
||||
} |
||||
|
||||
/** @return new packs created by this compaction. */ |
||||
public List<DfsPackDescription> getNewPacks() { |
||||
return newPacks; |
||||
} |
||||
|
||||
/** @return statistics corresponding to the {@link #getNewPacks()}. */ |
||||
public List<PackWriter.Statistics> getNewPackStatistics() { |
||||
return newStats; |
||||
} |
||||
|
||||
private List<DfsPackDescription> toPrune() { |
||||
int cnt = srcPacks.size(); |
||||
List<DfsPackDescription> all = new ArrayList<DfsPackDescription>(cnt); |
||||
for (DfsPackFile pack : srcPacks) |
||||
all.add(pack.getPackDescription()); |
||||
return all; |
||||
} |
||||
|
||||
private void addObjectsToPack(PackWriter pw, DfsReader ctx, |
||||
ProgressMonitor pm) throws IOException, |
||||
IncorrectObjectTypeException { |
||||
// Sort packs by description ordering, this places newer packs before
|
||||
// older packs, allowing the PackWriter to be handed newer objects
|
||||
// first and older objects last.
|
||||
Collections.sort(srcPacks, new Comparator<DfsPackFile>() { |
||||
public int compare(DfsPackFile a, DfsPackFile b) { |
||||
return a.getPackDescription().compareTo(b.getPackDescription()); |
||||
} |
||||
}); |
||||
|
||||
RevWalk rw = new RevWalk(ctx); |
||||
RevFlag added = rw.newFlag("ADDED"); |
||||
|
||||
pm.beginTask(JGitText.get().countingObjects, ProgressMonitor.UNKNOWN); |
||||
for (DfsPackFile src : srcPacks) { |
||||
List<ObjectIdWithOffset> want = new BlockList<ObjectIdWithOffset>(); |
||||
for (PackIndex.MutableEntry ent : src.getPackIndex(ctx)) { |
||||
ObjectId id = ent.toObjectId(); |
||||
RevObject obj = rw.lookupOrNull(id); |
||||
if (obj == null || !obj.has(added)) |
||||
want.add(new ObjectIdWithOffset(id, ent.getOffset())); |
||||
} |
||||
|
||||
// Sort objects by the order they appear in the pack file, for
|
||||
// two benefits. Scanning object type information is faster when
|
||||
// the pack is traversed in order, and this allows the PackWriter
|
||||
// to be given the new objects in a relatively sane newest-first
|
||||
// ordering without additional logic, like unpacking commits and
|
||||
// walking a commit queue.
|
||||
Collections.sort(want, new Comparator<ObjectIdWithOffset>() { |
||||
public int compare(ObjectIdWithOffset a, ObjectIdWithOffset b) { |
||||
return Long.signum(a.offset - b.offset); |
||||
} |
||||
}); |
||||
|
||||
// Only pack each object at most once into the output file. The
|
||||
// PackWriter will later select a representation to reuse, which
|
||||
// may be the version in this pack, or may be from another pack if
|
||||
// the object was copied here to complete a thin pack and is larger
|
||||
// than a delta from another pack. This is actually somewhat common
|
||||
// if an object is modified frequently, such as the top level tree.
|
||||
for (ObjectIdWithOffset id : want) { |
||||
int type = src.getObjectType(ctx, id.offset); |
||||
RevObject obj = rw.lookupAny(id, type); |
||||
if (!obj.has(added)) { |
||||
pm.update(1); |
||||
pw.addObject(obj); |
||||
obj.add(added); |
||||
} |
||||
} |
||||
} |
||||
pm.endTask(); |
||||
} |
||||
|
||||
private void writePack(DfsObjDatabase objdb, DfsPackDescription pack, |
||||
PackWriter pw, ProgressMonitor pm) throws IOException { |
||||
DfsOutputStream out = objdb.writePackFile(pack); |
||||
try { |
||||
CountingOutputStream cnt = new CountingOutputStream(out); |
||||
pw.writePack(pm, pm, cnt); |
||||
pack.setObjectCount(pw.getObjectCount()); |
||||
pack.setPackSize(cnt.getCount()); |
||||
} finally { |
||||
out.close(); |
||||
} |
||||
} |
||||
|
||||
private void writeIndex(DfsObjDatabase objdb, DfsPackDescription pack, |
||||
PackWriter pw) throws IOException { |
||||
DfsOutputStream out = objdb.writePackIndex(pack); |
||||
try { |
||||
CountingOutputStream cnt = new CountingOutputStream(out); |
||||
pw.writeIndex(cnt); |
||||
pack.setIndexSize(cnt.getCount()); |
||||
} finally { |
||||
out.close(); |
||||
} |
||||
} |
||||
|
||||
private static class ObjectIdWithOffset extends ObjectId { |
||||
final long offset; |
||||
|
||||
ObjectIdWithOffset(AnyObjectId id, long ofs) { |
||||
super(id); |
||||
offset = ofs; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,286 @@
|
||||
/* |
||||
* Copyright (C) 2011, 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.storage.dfs; |
||||
|
||||
import java.util.Set; |
||||
|
||||
import org.eclipse.jgit.lib.ObjectId; |
||||
import org.eclipse.jgit.storage.pack.PackWriter; |
||||
|
||||
/** |
||||
* Description of a DFS stored pack/index file. |
||||
* <p> |
||||
* Implementors may extend this class and add additional data members. |
||||
* <p> |
||||
* Instances of this class are cached with the DfsPackFile, and should not be |
||||
* modified once initialized and presented to the JGit DFS library. |
||||
*/ |
||||
public class DfsPackDescription implements Comparable<DfsPackDescription> { |
||||
private final DfsRepositoryDescription repoDesc; |
||||
|
||||
private final String packName; |
||||
|
||||
private long lastModified; |
||||
|
||||
private long packSize; |
||||
|
||||
private long indexSize; |
||||
|
||||
private long objectCount; |
||||
|
||||
private long deltaCount; |
||||
|
||||
private Set<ObjectId> tips; |
||||
|
||||
private PackWriter.Statistics stats; |
||||
|
||||
/** |
||||
* Initialize a description by pack name and repository. |
||||
* <p> |
||||
* The corresponding index file is assumed to exist and end with ".idx" |
||||
* instead of ".pack". If this is not true implementors must extend the |
||||
* class and override {@link #getIndexName()}. |
||||
* <p> |
||||
* Callers should also try to fill in other fields if they are reasonably |
||||
* free to access at the time this instance is being initialized. |
||||
* |
||||
* @param name |
||||
* name of the pack file. Must end with ".pack". |
||||
* @param repoDesc |
||||
* description of the repo containing the pack file. |
||||
*/ |
||||
public DfsPackDescription(DfsRepositoryDescription repoDesc, String name) { |
||||
this.repoDesc = repoDesc; |
||||
this.packName = name; |
||||
} |
||||
|
||||
/** @return description of the repository. */ |
||||
public DfsRepositoryDescription getRepositoryDescription() { |
||||
return repoDesc; |
||||
} |
||||
|
||||
/** @return name of the pack file. */ |
||||
public String getPackName() { |
||||
return packName; |
||||
} |
||||
|
||||
/** @return name of the index file. */ |
||||
public String getIndexName() { |
||||
String name = getPackName(); |
||||
int dot = name.lastIndexOf('.'); |
||||
if (dot < 0) |
||||
dot = name.length(); |
||||
return name.substring(0, dot) + ".idx"; |
||||
} |
||||
|
||||
/** @return time the pack was created, in milliseconds. */ |
||||
public long getLastModified() { |
||||
return lastModified; |
||||
} |
||||
|
||||
/** |
||||
* @param timeMillis |
||||
* time the pack was created, in milliseconds. 0 if not known. |
||||
* @return {@code this} |
||||
*/ |
||||
public DfsPackDescription setLastModified(long timeMillis) { |
||||
lastModified = timeMillis; |
||||
return this; |
||||
} |
||||
|
||||
/** @return size of the pack, in bytes. If 0 the pack size is not yet known. */ |
||||
public long getPackSize() { |
||||
return packSize; |
||||
} |
||||
|
||||
/** |
||||
* @param bytes |
||||
* size of the pack in bytes. If 0 the size is not known and will |
||||
* be determined on first read. |
||||
* @return {@code this} |
||||
*/ |
||||
public DfsPackDescription setPackSize(long bytes) { |
||||
packSize = Math.max(0, bytes); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* @return size of the index, in bytes. If 0 the index size is not yet |
||||
* known. |
||||
*/ |
||||
public long getIndexSize() { |
||||
return indexSize; |
||||
} |
||||
|
||||
/** |
||||
* @param bytes |
||||
* size of the index in bytes. If 0 the size is not known and |
||||
* will be determined on first read. |
||||
* @return {@code this} |
||||
*/ |
||||
public DfsPackDescription setIndexSize(long bytes) { |
||||
indexSize = Math.max(0, bytes); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* @return size of the reverse index, in bytes. |
||||
*/ |
||||
public int getReverseIndexSize() { |
||||
return (int) Math.min(objectCount * 8, Integer.MAX_VALUE); |
||||
} |
||||
|
||||
/** @return number of objects in the pack. */ |
||||
public long getObjectCount() { |
||||
return objectCount; |
||||
} |
||||
|
||||
/** |
||||
* @param cnt |
||||
* number of objects in the pack. |
||||
* @return {@code this} |
||||
*/ |
||||
public DfsPackDescription setObjectCount(long cnt) { |
||||
objectCount = Math.max(0, cnt); |
||||
return this; |
||||
} |
||||
|
||||
/** @return number of delta compressed objects in the pack. */ |
||||
public long getDeltaCount() { |
||||
return deltaCount; |
||||
} |
||||
|
||||
/** |
||||
* @param cnt |
||||
* number of delta compressed objects in the pack. |
||||
* @return {@code this} |
||||
*/ |
||||
public DfsPackDescription setDeltaCount(long cnt) { |
||||
deltaCount = Math.max(0, cnt); |
||||
return this; |
||||
} |
||||
|
||||
/** @return the tips that created this pack, if known. */ |
||||
public Set<ObjectId> getTips() { |
||||
return tips; |
||||
} |
||||
|
||||
/** |
||||
* @param tips |
||||
* the tips of the pack, null if it has no known tips. |
||||
* @return {@code this} |
||||
*/ |
||||
public DfsPackDescription setTips(Set<ObjectId> tips) { |
||||
this.tips = tips; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* @return statistics from PackWriter, if the pack was built with it. |
||||
* Generally this is only available for packs created by |
||||
* DfsGarbageCollector or DfsPackCompactor, and only when the pack |
||||
* is being committed to the repository. |
||||
*/ |
||||
public PackWriter.Statistics getPackStats() { |
||||
return stats; |
||||
} |
||||
|
||||
DfsPackDescription setPackStats(PackWriter.Statistics stats) { |
||||
this.stats = stats; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Discard the pack statistics, if it was populated. |
||||
* |
||||
* @return {@code this} |
||||
*/ |
||||
public DfsPackDescription clearPackStats() { |
||||
stats = null; |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return getPackName().hashCode(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object b) { |
||||
if (b instanceof DfsPackDescription) { |
||||
DfsPackDescription desc = (DfsPackDescription) b; |
||||
return getPackName().equals(desc.getPackName()) && |
||||
getRepositoryDescription().equals(desc.getRepositoryDescription()); |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* Sort packs according to the optimal lookup ordering. |
||||
* <p> |
||||
* This method tries to position packs in the order readers should examine |
||||
* them when looking for objects by SHA-1. The default tries to sort packs |
||||
* with more recent modification dates before older packs, and packs with |
||||
* fewer objects before packs with more objects. |
||||
* |
||||
* @param b |
||||
* the other pack. |
||||
*/ |
||||
public int compareTo(DfsPackDescription b) { |
||||
// Newer packs should sort first.
|
||||
int cmp = Long.signum(b.getLastModified() - getLastModified()); |
||||
if (cmp != 0) |
||||
return cmp; |
||||
|
||||
// Break ties on smaller index. Readers may get lucky and find
|
||||
// the object they care about in the smaller index. This also pushes
|
||||
// big historical packs to the end of the list, due to more objects.
|
||||
return Long.signum(getObjectCount() - b.getObjectCount()); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return getPackName(); |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,60 @@
|
||||
/* |
||||
* Copyright (C) 2011, 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.storage.dfs; |
||||
|
||||
import java.util.concurrent.atomic.AtomicLong; |
||||
|
||||
final class DfsPackKey { |
||||
final int hash; |
||||
|
||||
final AtomicLong cachedSize; |
||||
|
||||
DfsPackKey() { |
||||
// Multiply by 31 here so we can more directly combine with another
|
||||
// value without doing the multiply there.
|
||||
//
|
||||
hash = System.identityHashCode(this) * 31; |
||||
cachedSize = new AtomicLong(); |
||||
} |
||||
} |
@ -0,0 +1,450 @@
|
||||
/* |
||||
* Copyright (C) 2011, 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.storage.dfs; |
||||
|
||||
import java.io.EOFException; |
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.nio.ByteBuffer; |
||||
import java.security.MessageDigest; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.zip.CRC32; |
||||
import java.util.zip.Deflater; |
||||
|
||||
import org.eclipse.jgit.lib.AnyObjectId; |
||||
import org.eclipse.jgit.lib.Constants; |
||||
import org.eclipse.jgit.lib.ProgressMonitor; |
||||
import org.eclipse.jgit.storage.file.PackIndex; |
||||
import org.eclipse.jgit.storage.file.PackLock; |
||||
import org.eclipse.jgit.transport.PackParser; |
||||
import org.eclipse.jgit.transport.PackedObjectInfo; |
||||
|
||||
/** Parses a pack stream into the DFS, by creating a new pack and index. */ |
||||
public class DfsPackParser extends PackParser { |
||||
private final DfsObjDatabase objdb; |
||||
|
||||
private final DfsInserter objins; |
||||
|
||||
/** CRC-32 computation for objects that are appended onto the pack. */ |
||||
private final CRC32 crc; |
||||
|
||||
/** Running SHA-1 of the entire pack stream. */ |
||||
private final MessageDigest packDigest; |
||||
|
||||
/** Block size to use when caching data for read back. */ |
||||
private int blockSize; |
||||
|
||||
/** Current end of the pack file. */ |
||||
private long packEnd; |
||||
|
||||
/** Checksum of the entire pack file. */ |
||||
private byte[] packHash; |
||||
|
||||
/** Compresses delta bases when completing a thin pack. */ |
||||
private Deflater def; |
||||
|
||||
/** True if the pack is an empty pack. */ |
||||
private boolean isEmptyPack; |
||||
|
||||
/** Name of the pack file, computed in {@link #onPackHeader(long)}. */ |
||||
private DfsPackDescription packDsc; |
||||
|
||||
/** Key used during delta resolution reading delta chains. */ |
||||
private DfsPackKey packKey; |
||||
|
||||
/** If the index was small enough, the entire index after writing. */ |
||||
private PackIndex packIndex; |
||||
|
||||
/** Stream to the DFS storage, opened during {@link #onPackHeader(long)}. */ |
||||
private DfsOutputStream out; |
||||
|
||||
/** Data being written that has not yet been cached. */ |
||||
private byte[] currBuf; |
||||
private long currPos; // Position of currBuf in the file.
|
||||
private int currEnd; // Position inside of currBuf to append to next.
|
||||
|
||||
/** Cache the chunks were stored into or get read back from. */ |
||||
private DfsBlockCache blockCache; |
||||
|
||||
/** Cached block that is being read. */ |
||||
private long readPos; |
||||
private DfsBlock readBlock; |
||||
|
||||
/** |
||||
* Initialize a new pack parser. |
||||
* |
||||
* @param db |
||||
* database the objects will be imported into. |
||||
* @param ins |
||||
* inserter the parser will use to help it inject the objects. |
||||
* @param in |
||||
* the stream to parse. |
||||
*/ |
||||
protected DfsPackParser(DfsObjDatabase db, DfsInserter ins, InputStream in) { |
||||
super(db, in); |
||||
this.objdb = db; |
||||
this.objins = ins; |
||||
this.crc = new CRC32(); |
||||
this.packDigest = Constants.newMessageDigest(); |
||||
} |
||||
|
||||
@Override |
||||
public PackLock parse(ProgressMonitor receiving, ProgressMonitor resolving) |
||||
throws IOException { |
||||
boolean rollback = true; |
||||
try { |
||||
blockCache = DfsBlockCache.getInstance(); |
||||
super.parse(receiving, resolving); |
||||
if (isEmptyPack) |
||||
return null; |
||||
buffer(packHash, 0, packHash.length); |
||||
if (currEnd != 0) |
||||
flushBlock(); |
||||
out.close(); |
||||
out = null; |
||||
currBuf = null; |
||||
readBlock = null; |
||||
packDsc.setPackSize(packEnd); |
||||
|
||||
writePackIndex(); |
||||
objdb.commitPack(Collections.singletonList(packDsc), null); |
||||
rollback = false; |
||||
|
||||
DfsPackFile p = blockCache.getOrCreate(packDsc, packKey); |
||||
p.setBlockSize(blockSize); |
||||
if (packIndex != null) |
||||
p.setPackIndex(packIndex); |
||||
|
||||
objdb.addPack(p); |
||||
|
||||
return null; |
||||
} finally { |
||||
blockCache = null; |
||||
currBuf = null; |
||||
readBlock = null; |
||||
|
||||
if (def != null) { |
||||
def.end(); |
||||
def = null; |
||||
} |
||||
|
||||
if (out != null) { |
||||
try { |
||||
out.close(); |
||||
} catch (IOException err) { |
||||
// Ignore a close error, rollbackPack is also important.
|
||||
} |
||||
out = null; |
||||
} |
||||
|
||||
if (rollback && packDsc != null) { |
||||
try { |
||||
objdb.rollbackPack(Collections.singletonList(packDsc)); |
||||
} finally { |
||||
packDsc = null; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** @return description of the imported pack, if one was made. */ |
||||
public DfsPackDescription getPackDescription() { |
||||
return packDsc; |
||||
} |
||||
|
||||
@Override |
||||
protected void onPackHeader(long objectCount) throws IOException { |
||||
if (objectCount == 0) { |
||||
isEmptyPack = true; |
||||
currBuf = new byte[256]; |
||||
return; |
||||
} |
||||
|
||||
packDsc = objdb.newPack(DfsObjDatabase.PackSource.RECEIVE); |
||||
packKey = new DfsPackKey(); |
||||
|
||||
out = objdb.writePackFile(packDsc); |
||||
int size = out.blockSize(); |
||||
if (size <= 0) |
||||
size = blockCache.getBlockSize(); |
||||
else if (size < blockCache.getBlockSize()) |
||||
size = (blockCache.getBlockSize() / size) * size; |
||||
blockSize = size; |
||||
currBuf = new byte[blockSize]; |
||||
} |
||||
|
||||
@Override |
||||
protected void onBeginWholeObject(long streamPosition, int type, |
||||
long inflatedSize) throws IOException { |
||||
crc.reset(); |
||||
} |
||||
|
||||
@Override |
||||
protected void onEndWholeObject(PackedObjectInfo info) throws IOException { |
||||
info.setCRC((int) crc.getValue()); |
||||
} |
||||
|
||||
@Override |
||||
protected void onBeginOfsDelta(long streamPosition, |
||||
long baseStreamPosition, long inflatedSize) throws IOException { |
||||
crc.reset(); |
||||
} |
||||
|
||||
@Override |
||||
protected void onBeginRefDelta(long streamPosition, AnyObjectId baseId, |
||||
long inflatedSize) throws IOException { |
||||
crc.reset(); |
||||
} |
||||
|
||||
@Override |
||||
protected UnresolvedDelta onEndDelta() throws IOException { |
||||
UnresolvedDelta delta = new UnresolvedDelta(); |
||||
delta.setCRC((int) crc.getValue()); |
||||
return delta; |
||||
} |
||||
|
||||
@Override |
||||
protected void onInflatedObjectData(PackedObjectInfo obj, int typeCode, |
||||
byte[] data) throws IOException { |
||||
// DfsPackParser ignores this event.
|
||||
} |
||||
|
||||
@Override |
||||
protected void onObjectHeader(Source src, byte[] raw, int pos, int len) |
||||
throws IOException { |
||||
crc.update(raw, pos, len); |
||||
} |
||||
|
||||
@Override |
||||
protected void onObjectData(Source src, byte[] raw, int pos, int len) |
||||
throws IOException { |
||||
crc.update(raw, pos, len); |
||||
} |
||||
|
||||
@Override |
||||
protected void onStoreStream(byte[] raw, int pos, int len) |
||||
throws IOException { |
||||
buffer(raw, pos, len); |
||||
packDigest.update(raw, pos, len); |
||||
} |
||||
|
||||
private void buffer(byte[] raw, int pos, int len) throws IOException { |
||||
while (0 < len) { |
||||
int n = Math.min(len, currBuf.length - currEnd); |
||||
if (n == 0) { |
||||
DfsBlock v = flushBlock(); |
||||
currBuf = new byte[blockSize]; |
||||
currEnd = 0; |
||||
currPos += v.size(); |
||||
continue; |
||||
} |
||||
|
||||
System.arraycopy(raw, pos, currBuf, currEnd, n); |
||||
pos += n; |
||||
len -= n; |
||||
currEnd += n; |
||||
packEnd += n; |
||||
} |
||||
} |
||||
|
||||
private DfsBlock flushBlock() throws IOException { |
||||
if (isEmptyPack) |
||||
throw new IOException(DfsText.get().willNotStoreEmptyPack); |
||||
|
||||
out.write(currBuf, 0, currEnd); |
||||
|
||||
byte[] buf; |
||||
if (currEnd == currBuf.length) { |
||||
buf = currBuf; |
||||
} else { |
||||
buf = new byte[currEnd]; |
||||
System.arraycopy(currBuf, 0, buf, 0, currEnd); |
||||
} |
||||
|
||||
DfsBlock v = new DfsBlock(packKey, currPos, buf); |
||||
readBlock = v; |
||||
blockCache.put(v); |
||||
return v; |
||||
} |
||||
|
||||
@Override |
||||
protected void onPackFooter(byte[] hash) throws IOException { |
||||
// The base class will validate the original hash matches
|
||||
// what the stream has stored at the end. We are called
|
||||
// only if the hash was good. Save it in case there are no
|
||||
// missing bases to append.
|
||||
packHash = hash; |
||||
} |
||||
|
||||
@Override |
||||
protected ObjectTypeAndSize seekDatabase(PackedObjectInfo obj, |
||||
ObjectTypeAndSize info) throws IOException { |
||||
readPos = obj.getOffset(); |
||||
crc.reset(); |
||||
return readObjectHeader(info); |
||||
} |
||||
|
||||
@Override |
||||
protected ObjectTypeAndSize seekDatabase(UnresolvedDelta delta, |
||||
ObjectTypeAndSize info) throws IOException { |
||||
readPos = delta.getOffset(); |
||||
crc.reset(); |
||||
return readObjectHeader(info); |
||||
} |
||||
|
||||
@Override |
||||
protected int readDatabase(byte[] dst, int pos, int cnt) throws IOException { |
||||
if (cnt == 0) |
||||
return 0; |
||||
|
||||
if (currPos <= readPos) { |
||||
// Requested read is still buffered. Copy direct from buffer.
|
||||
int p = (int) (readPos - currPos); |
||||
int n = Math.min(cnt, currEnd - p); |
||||
if (n == 0) |
||||
throw new EOFException(); |
||||
System.arraycopy(currBuf, p, dst, pos, n); |
||||
readPos += n; |
||||
return n; |
||||
} |
||||
|
||||
if (readBlock == null || !readBlock.contains(packKey, readPos)) { |
||||
long start = toBlockStart(readPos); |
||||
readBlock = blockCache.get(packKey, start); |
||||
if (readBlock == null) { |
||||
int size = (int) Math.min(blockSize, packEnd - start); |
||||
byte[] buf = new byte[size]; |
||||
if (read(start, buf, 0, size) != size) |
||||
throw new EOFException(); |
||||
readBlock = new DfsBlock(packKey, start, buf); |
||||
blockCache.put(readBlock); |
||||
} |
||||
} |
||||
|
||||
int n = readBlock.copy(readPos, dst, pos, cnt); |
||||
readPos += n; |
||||
return n; |
||||
} |
||||
|
||||
private int read(long pos, byte[] dst, int off, int len) throws IOException { |
||||
if (len == 0) |
||||
return 0; |
||||
|
||||
int cnt = 0; |
||||
while (0 < len) { |
||||
int r = out.read(pos, ByteBuffer.wrap(dst, off, len)); |
||||
if (r <= 0) |
||||
break; |
||||
pos += r; |
||||
off += r; |
||||
len -= r; |
||||
cnt += r; |
||||
} |
||||
return cnt != 0 ? cnt : -1; |
||||
} |
||||
|
||||
private long toBlockStart(long pos) { |
||||
return (pos / blockSize) * blockSize; |
||||
} |
||||
|
||||
@Override |
||||
protected boolean checkCRC(int oldCRC) { |
||||
return oldCRC == (int) crc.getValue(); |
||||
} |
||||
|
||||
@Override |
||||
protected boolean onAppendBase(final int typeCode, final byte[] data, |
||||
final PackedObjectInfo info) throws IOException { |
||||
info.setOffset(packEnd); |
||||
|
||||
final byte[] buf = buffer(); |
||||
int sz = data.length; |
||||
int len = 0; |
||||
buf[len++] = (byte) ((typeCode << 4) | sz & 15); |
||||
sz >>>= 4; |
||||
while (sz > 0) { |
||||
buf[len - 1] |= 0x80; |
||||
buf[len++] = (byte) (sz & 0x7f); |
||||
sz >>>= 7; |
||||
} |
||||
|
||||
packDigest.update(buf, 0, len); |
||||
crc.reset(); |
||||
crc.update(buf, 0, len); |
||||
buffer(buf, 0, len); |
||||
|
||||
if (def == null) |
||||
def = new Deflater(Deflater.DEFAULT_COMPRESSION, false); |
||||
else |
||||
def.reset(); |
||||
def.setInput(data); |
||||
def.finish(); |
||||
|
||||
while (!def.finished()) { |
||||
len = def.deflate(buf); |
||||
packDigest.update(buf, 0, len); |
||||
crc.update(buf, 0, len); |
||||
buffer(buf, 0, len); |
||||
} |
||||
|
||||
info.setCRC((int) crc.getValue()); |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
protected void onEndThinPack() throws IOException { |
||||
// Normally when a thin pack is closed the pack header gets
|
||||
// updated to reflect the actual object count. This is not going
|
||||
// to be possible on most DFS backends, so instead we allow
|
||||
// the header to have an incorrect count, but we do change the
|
||||
// trailing digest to be correct.
|
||||
packHash = packDigest.digest(); |
||||
} |
||||
|
||||
private void writePackIndex() throws IOException { |
||||
List<PackedObjectInfo> list = getSortedObjectList(null /* by ObjectId */); |
||||
packIndex = objins.writePackIndex(packDsc, packHash, list); |
||||
} |
||||
} |
@ -0,0 +1,60 @@
|
||||
/* |
||||
* Copyright (C) 2011, 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.storage.dfs; |
||||
|
||||
import org.eclipse.jgit.events.RepositoryEvent; |
||||
|
||||
/** Describes a change to the list of packs in a {@link DfsRepository}. */ |
||||
public class DfsPacksChangedEvent |
||||
extends RepositoryEvent<DfsPacksChangedListener> { |
||||
@Override |
||||
public Class<DfsPacksChangedListener> getListenerType() { |
||||
return DfsPacksChangedListener.class; |
||||
} |
||||
|
||||
@Override |
||||
public void dispatch(DfsPacksChangedListener listener) { |
||||
listener.onPacksChanged(this); |
||||
} |
||||
} |
@ -0,0 +1,57 @@
|
||||
/* |
||||
* Copyright (C) 2011, 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.storage.dfs; |
||||
|
||||
import org.eclipse.jgit.events.RepositoryListener; |
||||
|
||||
/** Receives {@link DfsPacksChangedEvent}s. */ |
||||
public interface DfsPacksChangedListener extends RepositoryListener { |
||||
/** |
||||
* Invoked when all packs in a repository are listed. |
||||
* |
||||
* @param event |
||||
* information about the packs. |
||||
*/ |
||||
void onPacksChanged(DfsPacksChangedEvent event); |
||||
} |
@ -0,0 +1,793 @@
|
||||
/* |
||||
* Copyright (C) 2008-2011, Google Inc. |
||||
* Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org> |
||||
* 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.storage.dfs; |
||||
|
||||
import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH; |
||||
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; |
||||
import static org.eclipse.jgit.lib.Constants.OBJ_TREE; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.InterruptedIOException; |
||||
import java.security.MessageDigest; |
||||
import java.text.MessageFormat; |
||||
import java.util.ArrayList; |
||||
import java.util.Arrays; |
||||
import java.util.Collection; |
||||
import java.util.Collections; |
||||
import java.util.Comparator; |
||||
import java.util.HashSet; |
||||
import java.util.Iterator; |
||||
import java.util.LinkedList; |
||||
import java.util.List; |
||||
import java.util.concurrent.ExecutionException; |
||||
import java.util.zip.DataFormatException; |
||||
import java.util.zip.Inflater; |
||||
|
||||
import org.eclipse.jgit.JGitText; |
||||
import org.eclipse.jgit.errors.IncorrectObjectTypeException; |
||||
import org.eclipse.jgit.errors.MissingObjectException; |
||||
import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; |
||||
import org.eclipse.jgit.lib.AbbreviatedObjectId; |
||||
import org.eclipse.jgit.lib.AnyObjectId; |
||||
import org.eclipse.jgit.lib.AsyncObjectLoaderQueue; |
||||
import org.eclipse.jgit.lib.AsyncObjectSizeQueue; |
||||
import org.eclipse.jgit.lib.Constants; |
||||
import org.eclipse.jgit.lib.InflaterCache; |
||||
import org.eclipse.jgit.lib.ObjectId; |
||||
import org.eclipse.jgit.lib.ObjectLoader; |
||||
import org.eclipse.jgit.lib.ObjectReader; |
||||
import org.eclipse.jgit.lib.ProgressMonitor; |
||||
import org.eclipse.jgit.revwalk.ObjectWalk; |
||||
import org.eclipse.jgit.revwalk.RevCommit; |
||||
import org.eclipse.jgit.revwalk.RevObject; |
||||
import org.eclipse.jgit.revwalk.RevWalk; |
||||
import org.eclipse.jgit.storage.pack.CachedPack; |
||||
import org.eclipse.jgit.storage.pack.ObjectReuseAsIs; |
||||
import org.eclipse.jgit.storage.pack.ObjectToPack; |
||||
import org.eclipse.jgit.storage.pack.PackOutputStream; |
||||
import org.eclipse.jgit.storage.pack.PackWriter; |
||||
import org.eclipse.jgit.util.BlockList; |
||||
|
||||
final class DfsReader extends ObjectReader implements ObjectReuseAsIs { |
||||
/** Temporary buffer large enough for at least one raw object id. */ |
||||
final byte[] tempId = new byte[OBJECT_ID_LENGTH]; |
||||
|
||||
/** Database this reader loads objects from. */ |
||||
final DfsObjDatabase db; |
||||
|
||||
private Inflater inf; |
||||
|
||||
private DfsBlock block; |
||||
|
||||
private DeltaBaseCache baseCache; |
||||
|
||||
private DfsPackFile last; |
||||
|
||||
private boolean wantReadAhead; |
||||
|
||||
private List<ReadAheadTask.BlockFuture> pendingReadAhead; |
||||
|
||||
DfsReader(DfsObjDatabase db) { |
||||
this.db = db; |
||||
} |
||||
|
||||
DfsReaderOptions getOptions() { |
||||
return db.getReaderOptions(); |
||||
} |
||||
|
||||
DeltaBaseCache getDeltaBaseCache() { |
||||
if (baseCache == null) |
||||
baseCache = new DeltaBaseCache(this); |
||||
return baseCache; |
||||
} |
||||
|
||||
int getStreamFileThreshold() { |
||||
return getOptions().getStreamFileThreshold(); |
||||
} |
||||
|
||||
@Override |
||||
public ObjectReader newReader() { |
||||
return new DfsReader(db); |
||||
} |
||||
|
||||
@Override |
||||
public Collection<ObjectId> resolve(AbbreviatedObjectId id) |
||||
throws IOException { |
||||
if (id.isComplete()) |
||||
return Collections.singleton(id.toObjectId()); |
||||
HashSet<ObjectId> matches = new HashSet<ObjectId>(4); |
||||
for (DfsPackFile pack : db.getPacks()) { |
||||
pack.resolve(this, matches, id, 256); |
||||
if (256 <= matches.size()) |
||||
break; |
||||
} |
||||
return matches; |
||||
} |
||||
|
||||
@Override |
||||
public boolean has(AnyObjectId objectId) throws IOException { |
||||
if (last != null && last.hasObject(this, objectId)) |
||||
return true; |
||||
for (DfsPackFile pack : db.getPacks()) { |
||||
if (last == pack) |
||||
continue; |
||||
if (pack.hasObject(this, objectId)) { |
||||
last = pack; |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public ObjectLoader open(AnyObjectId objectId, int typeHint) |
||||
throws MissingObjectException, IncorrectObjectTypeException, |
||||
IOException { |
||||
if (last != null) { |
||||
ObjectLoader ldr = last.get(this, objectId); |
||||
if (ldr != null) |
||||
return ldr; |
||||
} |
||||
|
||||
for (DfsPackFile pack : db.getPacks()) { |
||||
if (pack == last) |
||||
continue; |
||||
ObjectLoader ldr = pack.get(this, objectId); |
||||
if (ldr != null) { |
||||
last = pack; |
||||
return ldr; |
||||
} |
||||
} |
||||
|
||||
if (typeHint == OBJ_ANY) |
||||
throw new MissingObjectException(objectId.copy(), "unknown"); |
||||
throw new MissingObjectException(objectId.copy(), typeHint); |
||||
} |
||||
|
||||
private static final Comparator<FoundObject<?>> FOUND_OBJECT_SORT = new Comparator<FoundObject<?>>() { |
||||
public int compare(FoundObject<?> a, FoundObject<?> b) { |
||||
int cmp = a.packIndex - b.packIndex; |
||||
if (cmp == 0) |
||||
cmp = Long.signum(a.offset - b.offset); |
||||
return cmp; |
||||
} |
||||
}; |
||||
|
||||
private static class FoundObject<T extends ObjectId> { |
||||
final T id; |
||||
final DfsPackFile pack; |
||||
final long offset; |
||||
final int packIndex; |
||||
|
||||
FoundObject(T objectId, int packIdx, DfsPackFile pack, long offset) { |
||||
this.id = objectId; |
||||
this.pack = pack; |
||||
this.offset = offset; |
||||
this.packIndex = packIdx; |
||||
} |
||||
|
||||
FoundObject(T objectId) { |
||||
this.id = objectId; |
||||
this.pack = null; |
||||
this.offset = 0; |
||||
this.packIndex = 0; |
||||
} |
||||
} |
||||
|
||||
private <T extends ObjectId> Iterable<FoundObject<T>> findAll( |
||||
Iterable<T> objectIds) throws IOException { |
||||
ArrayList<FoundObject<T>> r = new ArrayList<FoundObject<T>>(); |
||||
DfsPackFile[] packList = db.getPacks(); |
||||
if (packList.length == 0) { |
||||
for (T t : objectIds) |
||||
r.add(new FoundObject<T>(t)); |
||||
return r; |
||||
} |
||||
|
||||
int lastIdx = 0; |
||||
DfsPackFile lastPack = packList[lastIdx]; |
||||
|
||||
OBJECT_SCAN: for (T t : objectIds) { |
||||
try { |
||||
long p = lastPack.findOffset(this, t); |
||||
if (0 < p) { |
||||
r.add(new FoundObject<T>(t, lastIdx, lastPack, p)); |
||||
continue; |
||||
} |
||||
} catch (IOException e) { |
||||
// Fall though and try to examine other packs.
|
||||
} |
||||
|
||||
for (int i = 0; i < packList.length; i++) { |
||||
if (i == lastIdx) |
||||
continue; |
||||
DfsPackFile pack = packList[i]; |
||||
try { |
||||
long p = pack.findOffset(this, t); |
||||
if (0 < p) { |
||||
r.add(new FoundObject<T>(t, i, pack, p)); |
||||
lastIdx = i; |
||||
lastPack = pack; |
||||
continue OBJECT_SCAN; |
||||
} |
||||
} catch (IOException e) { |
||||
// Examine other packs.
|
||||
} |
||||
} |
||||
|
||||
r.add(new FoundObject<T>(t)); |
||||
} |
||||
|
||||
Collections.sort(r, FOUND_OBJECT_SORT); |
||||
last = lastPack; |
||||
return r; |
||||
} |
||||
|
||||
@Override |
||||
public <T extends ObjectId> AsyncObjectLoaderQueue<T> open( |
||||
Iterable<T> objectIds, final boolean reportMissing) { |
||||
wantReadAhead = true; |
||||
|
||||
Iterable<FoundObject<T>> order; |
||||
IOException error = null; |
||||
try { |
||||
order = findAll(objectIds); |
||||
} catch (IOException e) { |
||||
order = Collections.emptyList(); |
||||
error = e; |
||||
} |
||||
|
||||
final Iterator<FoundObject<T>> idItr = order.iterator(); |
||||
final IOException findAllError = error; |
||||
return new AsyncObjectLoaderQueue<T>() { |
||||
private FoundObject<T> cur; |
||||
|
||||
public boolean next() throws MissingObjectException, IOException { |
||||
if (idItr.hasNext()) { |
||||
cur = idItr.next(); |
||||
return true; |
||||
} else if (findAllError != null) { |
||||
throw findAllError; |
||||
} else { |
||||
cancelReadAhead(); |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
public T getCurrent() { |
||||
return cur.id; |
||||
} |
||||
|
||||
public ObjectId getObjectId() { |
||||
return cur.id; |
||||
} |
||||
|
||||
public ObjectLoader open() throws IOException { |
||||
if (cur.pack == null) |
||||
throw new MissingObjectException(cur.id, "unknown"); |
||||
return cur.pack.load(DfsReader.this, cur.offset); |
||||
} |
||||
|
||||
public boolean cancel(boolean mayInterruptIfRunning) { |
||||
cancelReadAhead(); |
||||
return true; |
||||
} |
||||
|
||||
public void release() { |
||||
cancelReadAhead(); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
@Override |
||||
public <T extends ObjectId> AsyncObjectSizeQueue<T> getObjectSize( |
||||
Iterable<T> objectIds, final boolean reportMissing) { |
||||
wantReadAhead = true; |
||||
|
||||
Iterable<FoundObject<T>> order; |
||||
IOException error = null; |
||||
try { |
||||
order = findAll(objectIds); |
||||
} catch (IOException e) { |
||||
order = Collections.emptyList(); |
||||
error = e; |
||||
} |
||||
|
||||
final Iterator<FoundObject<T>> idItr = order.iterator(); |
||||
final IOException findAllError = error; |
||||
return new AsyncObjectSizeQueue<T>() { |
||||
private FoundObject<T> cur; |
||||
|
||||
private long sz; |
||||
|
||||
public boolean next() throws MissingObjectException, IOException { |
||||
if (idItr.hasNext()) { |
||||
cur = idItr.next(); |
||||
if (cur.pack == null) |
||||
throw new MissingObjectException(cur.id, "unknown"); |
||||
sz = cur.pack.getObjectSize(DfsReader.this, cur.offset); |
||||
return true; |
||||
} else if (findAllError != null) { |
||||
throw findAllError; |
||||
} else { |
||||
cancelReadAhead(); |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
public T getCurrent() { |
||||
return cur.id; |
||||
} |
||||
|
||||
public ObjectId getObjectId() { |
||||
return cur.id; |
||||
} |
||||
|
||||
public long getSize() { |
||||
return sz; |
||||
} |
||||
|
||||
public boolean cancel(boolean mayInterruptIfRunning) { |
||||
cancelReadAhead(); |
||||
return true; |
||||
} |
||||
|
||||
public void release() { |
||||
cancelReadAhead(); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
@Override |
||||
public void walkAdviceBeginCommits(RevWalk walk, Collection<RevCommit> roots) { |
||||
wantReadAhead = true; |
||||
} |
||||
|
||||
@Override |
||||
public void walkAdviceBeginTrees(ObjectWalk ow, RevCommit min, RevCommit max) { |
||||
wantReadAhead = true; |
||||
} |
||||
|
||||
@Override |
||||
public void walkAdviceEnd() { |
||||
cancelReadAhead(); |
||||
} |
||||
|
||||
@Override |
||||
public long getObjectSize(AnyObjectId objectId, int typeHint) |
||||
throws MissingObjectException, IncorrectObjectTypeException, |
||||
IOException { |
||||
if (last != null) { |
||||
long sz = last.getObjectSize(this, objectId); |
||||
if (0 <= sz) |
||||
return sz; |
||||
} |
||||
|
||||
for (DfsPackFile pack : db.getPacks()) { |
||||
if (pack == last) |
||||
continue; |
||||
long sz = pack.getObjectSize(this, objectId); |
||||
if (0 <= sz) { |
||||
last = pack; |
||||
return sz; |
||||
} |
||||
} |
||||
|
||||
if (typeHint == OBJ_ANY) |
||||
throw new MissingObjectException(objectId.copy(), "unknown"); |
||||
throw new MissingObjectException(objectId.copy(), typeHint); |
||||
} |
||||
|
||||
public DfsObjectToPack newObjectToPack(RevObject obj) { |
||||
return new DfsObjectToPack(obj); |
||||
} |
||||
|
||||
private static final Comparator<DfsObjectRepresentation> REPRESENTATION_SORT = new Comparator<DfsObjectRepresentation>() { |
||||
public int compare(DfsObjectRepresentation a, DfsObjectRepresentation b) { |
||||
int cmp = a.packIndex - b.packIndex; |
||||
if (cmp == 0) |
||||
cmp = Long.signum(a.offset - b.offset); |
||||
return cmp; |
||||
} |
||||
}; |
||||
|
||||
public void selectObjectRepresentation(PackWriter packer, |
||||
ProgressMonitor monitor, Iterable<ObjectToPack> objects) |
||||
throws IOException, MissingObjectException { |
||||
DfsPackFile[] packList = db.getPacks(); |
||||
if (packList.length == 0) { |
||||
Iterator<ObjectToPack> itr = objects.iterator(); |
||||
if (itr.hasNext()) |
||||
throw new MissingObjectException(itr.next(), "unknown"); |
||||
return; |
||||
} |
||||
|
||||
int packIndex = 0; |
||||
DfsPackFile packLast = packList[packIndex]; |
||||
|
||||
int updated = 0; |
||||
int posted = 0; |
||||
List<DfsObjectRepresentation> all = new BlockList<DfsObjectRepresentation>(); |
||||
for (ObjectToPack otp : objects) { |
||||
long p = packLast.findOffset(this, otp); |
||||
if (p < 0) { |
||||
int skip = packIndex; |
||||
for (packIndex = 0; packIndex < packList.length; packIndex++) { |
||||
if (skip == packIndex) |
||||
continue; |
||||
packLast = packList[packIndex]; |
||||
p = packLast.findOffset(this, otp); |
||||
if (0 < p) |
||||
break; |
||||
} |
||||
if (packIndex == packList.length) |
||||
throw new MissingObjectException(otp, otp.getType()); |
||||
} |
||||
|
||||
DfsObjectRepresentation r = new DfsObjectRepresentation(otp); |
||||
r.pack = packLast; |
||||
r.packIndex = packIndex; |
||||
r.offset = p; |
||||
all.add(r); |
||||
|
||||
if ((++updated & 1) == 1) { |
||||
monitor.update(1); // Update by 50%, the other 50% is below.
|
||||
posted++; |
||||
} |
||||
} |
||||
Collections.sort(all, REPRESENTATION_SORT); |
||||
|
||||
try { |
||||
wantReadAhead = true; |
||||
for (DfsObjectRepresentation r : all) { |
||||
r.pack.representation(this, r); |
||||
packer.select(r.object, r); |
||||
if ((++updated & 1) == 1) { |
||||
monitor.update(1); |
||||
posted++; |
||||
} |
||||
} |
||||
} finally { |
||||
cancelReadAhead(); |
||||
} |
||||
if (posted < all.size()) |
||||
monitor.update(all.size() - posted); |
||||
} |
||||
|
||||
public void copyObjectAsIs(PackOutputStream out, ObjectToPack otp, |
||||
boolean validate) throws IOException, |
||||
StoredObjectRepresentationNotAvailableException { |
||||
DfsObjectToPack src = (DfsObjectToPack) otp; |
||||
src.pack.copyAsIs(out, src, validate, this); |
||||
} |
||||
|
||||
private static final Comparator<ObjectToPack> WRITE_SORT = new Comparator<ObjectToPack>() { |
||||
public int compare(ObjectToPack o1, ObjectToPack o2) { |
||||
DfsObjectToPack a = (DfsObjectToPack) o1; |
||||
DfsObjectToPack b = (DfsObjectToPack) o2; |
||||
int cmp = a.packIndex - b.packIndex; |
||||
if (cmp == 0) |
||||
cmp = Long.signum(a.offset - b.offset); |
||||
return cmp; |
||||
} |
||||
}; |
||||
|
||||
public void writeObjects(PackOutputStream out, List<ObjectToPack> list) |
||||
throws IOException { |
||||
if (list.isEmpty()) |
||||
return; |
||||
|
||||
// Sorting objects by order in the current packs is usually
|
||||
// worthwhile. Most packs are going to be OFS_DELTA style,
|
||||
// where the base must appear before the deltas. If both base
|
||||
// and delta are to be reused, this ensures the base writes in
|
||||
// the output first without the recursive write-base-first logic
|
||||
// used by PackWriter to ensure OFS_DELTA can be used.
|
||||
//
|
||||
// Sorting by pack also ensures newer objects go first, which
|
||||
// typically matches the desired order.
|
||||
//
|
||||
// Only do this sorting for OBJ_TREE and OBJ_BLOB. Commits
|
||||
// are very likely to already be sorted in a good order in the
|
||||
// incoming list, and if they aren't, JGit's PackWriter has fixed
|
||||
// the order to be more optimal for readers, so honor that.
|
||||
switch (list.get(0).getType()) { |
||||
case OBJ_TREE: |
||||
case OBJ_BLOB: |
||||
Collections.sort(list, WRITE_SORT); |
||||
} |
||||
|
||||
try { |
||||
wantReadAhead = true; |
||||
for (ObjectToPack otp : list) |
||||
out.writeObject(otp); |
||||
} finally { |
||||
cancelReadAhead(); |
||||
} |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
public Collection<CachedPack> getCachedPacks() throws IOException { |
||||
DfsPackFile[] packList = db.getPacks(); |
||||
List<CachedPack> cached = new ArrayList<CachedPack>(packList.length); |
||||
for (DfsPackFile pack : packList) { |
||||
DfsPackDescription desc = pack.getPackDescription(); |
||||
if (desc.getTips() == null || desc.getTips().isEmpty()) |
||||
continue; |
||||
cached.add(new DfsCachedPack(pack)); |
||||
} |
||||
return cached; |
||||
} |
||||
|
||||
public void copyPackAsIs(PackOutputStream out, CachedPack pack, |
||||
boolean validate) throws IOException { |
||||
try { |
||||
wantReadAhead = true; |
||||
((DfsCachedPack) pack).copyAsIs(out, validate, this); |
||||
} finally { |
||||
cancelReadAhead(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Copy bytes from the window to a caller supplied buffer. |
||||
* |
||||
* @param pack |
||||
* the file the desired window is stored within. |
||||
* @param position |
||||
* position within the file to read from. |
||||
* @param dstbuf |
||||
* destination buffer to copy into. |
||||
* @param dstoff |
||||
* offset within <code>dstbuf</code> to start copying into. |
||||
* @param cnt |
||||
* number of bytes to copy. This value may exceed the number of |
||||
* bytes remaining in the window starting at offset |
||||
* <code>pos</code>. |
||||
* @return number of bytes actually copied; this may be less than |
||||
* <code>cnt</code> if <code>cnt</code> exceeded the number of bytes |
||||
* available. |
||||
* @throws IOException |
||||
* this cursor does not match the provider or id and the proper |
||||
* window could not be acquired through the provider's cache. |
||||
*/ |
||||
int copy(DfsPackFile pack, long position, byte[] dstbuf, int dstoff, int cnt) |
||||
throws IOException { |
||||
if (cnt == 0) |
||||
return 0; |
||||
|
||||
long length = pack.length; |
||||
if (0 <= length && length <= position) |
||||
return 0; |
||||
|
||||
int need = cnt; |
||||
do { |
||||
pin(pack, position); |
||||
int r = block.copy(position, dstbuf, dstoff, need); |
||||
position += r; |
||||
dstoff += r; |
||||
need -= r; |
||||
if (length < 0) |
||||
length = pack.length; |
||||
} while (0 < need && position < length); |
||||
return cnt - need; |
||||
} |
||||
|
||||
void copyPackAsIs(DfsPackFile pack, long length, boolean validate, |
||||
PackOutputStream out) throws IOException { |
||||
MessageDigest md = null; |
||||
if (validate) { |
||||
md = Constants.newMessageDigest(); |
||||
byte[] buf = out.getCopyBuffer(); |
||||
pin(pack, 0); |
||||
if (block.copy(0, buf, 0, 12) != 12) { |
||||
pack.setInvalid(); |
||||
throw new IOException(JGitText.get().packfileIsTruncated); |
||||
} |
||||
md.update(buf, 0, 12); |
||||
} |
||||
|
||||
long position = 12; |
||||
long remaining = length - (12 + 20); |
||||
while (0 < remaining) { |
||||
pin(pack, position); |
||||
|
||||
int ptr = (int) (position - block.start); |
||||
int n = (int) Math.min(block.size() - ptr, remaining); |
||||
block.write(out, position, n, md); |
||||
position += n; |
||||
remaining -= n; |
||||
} |
||||
|
||||
if (md != null) { |
||||
byte[] buf = new byte[20]; |
||||
byte[] actHash = md.digest(); |
||||
|
||||
pin(pack, position); |
||||
if (block.copy(position, buf, 0, 20) != 20) { |
||||
pack.setInvalid(); |
||||
throw new IOException(JGitText.get().packfileIsTruncated); |
||||
} |
||||
if (!Arrays.equals(actHash, buf)) { |
||||
pack.setInvalid(); |
||||
throw new IOException(MessageFormat.format( |
||||
JGitText.get().packfileCorruptionDetected, |
||||
pack.getPackDescription().getPackName())); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Inflate a region of the pack starting at {@code position}. |
||||
* |
||||
* @param pack |
||||
* the file the desired window is stored within. |
||||
* @param position |
||||
* position within the file to read from. |
||||
* @param dstbuf |
||||
* destination buffer the inflater should output decompressed |
||||
* data to. |
||||
* @param headerOnly |
||||
* if true the caller wants only {@code dstbuf.length} bytes. |
||||
* @return updated <code>dstoff</code> based on the number of bytes |
||||
* successfully inflated into <code>dstbuf</code>. |
||||
* @throws IOException |
||||
* this cursor does not match the provider or id and the proper |
||||
* window could not be acquired through the provider's cache. |
||||
* @throws DataFormatException |
||||
* the inflater encountered an invalid chunk of data. Data |
||||
* stream corruption is likely. |
||||
*/ |
||||
int inflate(DfsPackFile pack, long position, byte[] dstbuf, |
||||
boolean headerOnly) throws IOException, DataFormatException { |
||||
prepareInflater(); |
||||
pin(pack, position); |
||||
int dstoff = 0; |
||||
for (;;) { |
||||
dstoff = block.inflate(inf, position, dstbuf, dstoff); |
||||
|
||||
if (headerOnly & dstoff == dstbuf.length) |
||||
return dstoff; |
||||
if (inf.needsInput()) { |
||||
position += block.remaining(position); |
||||
pin(pack, position); |
||||
} else if (inf.finished()) |
||||
return dstoff; |
||||
else |
||||
throw new DataFormatException(); |
||||
} |
||||
} |
||||
|
||||
DfsBlock quickCopy(DfsPackFile p, long pos, long cnt) |
||||
throws IOException { |
||||
pin(p, pos); |
||||
if (block.contains(p.key, pos + (cnt - 1))) |
||||
return block; |
||||
return null; |
||||
} |
||||
|
||||
Inflater inflater() { |
||||
prepareInflater(); |
||||
return inf; |
||||
} |
||||
|
||||
private void prepareInflater() { |
||||
if (inf == null) |
||||
inf = InflaterCache.get(); |
||||
else |
||||
inf.reset(); |
||||
} |
||||
|
||||
void pin(DfsPackFile pack, long position) throws IOException { |
||||
DfsBlock b = block; |
||||
if (b == null || !b.contains(pack.key, position)) { |
||||
// If memory is low, we may need what is in our window field to
|
||||
// be cleaned up by the GC during the get for the next window.
|
||||
// So we always clear it, even though we are just going to set
|
||||
// it again.
|
||||
//
|
||||
block = null; |
||||
|
||||
if (pendingReadAhead != null) |
||||
waitForBlock(pack.key, position); |
||||
block = pack.getOrLoadBlock(position, this); |
||||
} |
||||
} |
||||
|
||||
boolean wantReadAhead() { |
||||
return wantReadAhead; |
||||
} |
||||
|
||||
void startedReadAhead(List<ReadAheadTask.BlockFuture> blocks) { |
||||
if (pendingReadAhead == null) |
||||
pendingReadAhead = new LinkedList<ReadAheadTask.BlockFuture>(); |
||||
pendingReadAhead.addAll(blocks); |
||||
} |
||||
|
||||
private void cancelReadAhead() { |
||||
if (pendingReadAhead != null) { |
||||
for (ReadAheadTask.BlockFuture f : pendingReadAhead) |
||||
f.cancel(true); |
||||
pendingReadAhead = null; |
||||
} |
||||
wantReadAhead = false; |
||||
} |
||||
|
||||
private void waitForBlock(DfsPackKey key, long position) |
||||
throws InterruptedIOException { |
||||
Iterator<ReadAheadTask.BlockFuture> itr = pendingReadAhead.iterator(); |
||||
while (itr.hasNext()) { |
||||
ReadAheadTask.BlockFuture f = itr.next(); |
||||
if (f.contains(key, position)) { |
||||
try { |
||||
f.get(); |
||||
} catch (InterruptedException e) { |
||||
throw new InterruptedIOException(); |
||||
} catch (ExecutionException e) { |
||||
// Exceptions should never be thrown by get(). Ignore
|
||||
// this and let the normal load paths identify any error.
|
||||
} |
||||
itr.remove(); |
||||
if (pendingReadAhead.isEmpty()) |
||||
pendingReadAhead = null; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** Release the current window cursor. */ |
||||
@Override |
||||
public void release() { |
||||
cancelReadAhead(); |
||||
last = null; |
||||
block = null; |
||||
baseCache = null; |
||||
try { |
||||
InflaterCache.release(inf); |
||||
} finally { |
||||
inf = null; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,135 @@
|
||||
/* |
||||
* Copyright (C) 2011, 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.storage.dfs; |
||||
|
||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_CORE_SECTION; |
||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DFS_SECTION; |
||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_DELTA_BASE_CACHE_LIMIT; |
||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_STREAM_FILE_TRESHOLD; |
||||
|
||||
import org.eclipse.jgit.lib.Config; |
||||
import org.eclipse.jgit.storage.pack.PackConfig; |
||||
|
||||
/** Options controlling how objects are read from a DHT stored repository. */ |
||||
public class DfsReaderOptions { |
||||
/** 1024 (number of bytes in one kibibyte/kilobyte) */ |
||||
public static final int KiB = 1024; |
||||
|
||||
/** 1024 {@link #KiB} (number of bytes in one mebibyte/megabyte) */ |
||||
public static final int MiB = 1024 * KiB; |
||||
|
||||
private int deltaBaseCacheLimit; |
||||
|
||||
private int streamFileThreshold; |
||||
|
||||
/** Create a default reader configuration. */ |
||||
public DfsReaderOptions() { |
||||
setDeltaBaseCacheLimit(10 * MiB); |
||||
setStreamFileThreshold(PackConfig.DEFAULT_BIG_FILE_THRESHOLD); |
||||
} |
||||
|
||||
/** @return maximum number of bytes to hold in per-reader DeltaBaseCache. */ |
||||
public int getDeltaBaseCacheLimit() { |
||||
return deltaBaseCacheLimit; |
||||
} |
||||
|
||||
/** |
||||
* Set the maximum number of bytes in the DeltaBaseCache. |
||||
* |
||||
* @param maxBytes |
||||
* the new limit. |
||||
* @return {@code this} |
||||
*/ |
||||
public DfsReaderOptions setDeltaBaseCacheLimit(int maxBytes) { |
||||
deltaBaseCacheLimit = Math.max(0, maxBytes); |
||||
return this; |
||||
} |
||||
|
||||
/** @return the size threshold beyond which objects must be streamed. */ |
||||
public int getStreamFileThreshold() { |
||||
return streamFileThreshold; |
||||
} |
||||
|
||||
/** |
||||
* @param newLimit |
||||
* new byte limit for objects that must be streamed. Objects |
||||
* smaller than this size can be obtained as a contiguous byte |
||||
* array, while objects bigger than this size require using an |
||||
* {@link org.eclipse.jgit.lib.ObjectStream}. |
||||
* @return {@code this} |
||||
*/ |
||||
public DfsReaderOptions setStreamFileThreshold(final int newLimit) { |
||||
streamFileThreshold = Math.max(0, newLimit); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Update properties by setting fields from the configuration. |
||||
* <p> |
||||
* If a property is not defined in the configuration, then it is left |
||||
* unmodified. |
||||
* |
||||
* @param rc |
||||
* configuration to read properties from. |
||||
* @return {@code this} |
||||
*/ |
||||
public DfsReaderOptions fromConfig(Config rc) { |
||||
setDeltaBaseCacheLimit(rc.getInt( |
||||
CONFIG_CORE_SECTION, |
||||
CONFIG_DFS_SECTION, |
||||
CONFIG_KEY_DELTA_BASE_CACHE_LIMIT, |
||||
getDeltaBaseCacheLimit())); |
||||
|
||||
long maxMem = Runtime.getRuntime().maxMemory(); |
||||
long sft = rc.getLong( |
||||
CONFIG_CORE_SECTION, |
||||
CONFIG_DFS_SECTION, |
||||
CONFIG_KEY_STREAM_FILE_TRESHOLD, |
||||
getStreamFileThreshold()); |
||||
sft = Math.min(sft, maxMem / 4); // don't use more than 1/4 of the heap
|
||||
sft = Math.min(sft, Integer.MAX_VALUE); // cannot exceed array length
|
||||
setStreamFileThreshold((int) sft); |
||||
return this; |
||||
} |
||||
} |
@ -0,0 +1,450 @@
|
||||
/* |
||||
* Copyright (C) 2011, 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.storage.dfs; |
||||
|
||||
import static org.eclipse.jgit.lib.Ref.Storage.NEW; |
||||
|
||||
import java.io.IOException; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.concurrent.atomic.AtomicReference; |
||||
|
||||
import org.eclipse.jgit.errors.MissingObjectException; |
||||
import org.eclipse.jgit.lib.ObjectIdRef; |
||||
import org.eclipse.jgit.lib.Ref; |
||||
import org.eclipse.jgit.lib.RefDatabase; |
||||
import org.eclipse.jgit.lib.RefRename; |
||||
import org.eclipse.jgit.lib.SymbolicRef; |
||||
import org.eclipse.jgit.revwalk.RevObject; |
||||
import org.eclipse.jgit.revwalk.RevTag; |
||||
import org.eclipse.jgit.revwalk.RevWalk; |
||||
import org.eclipse.jgit.util.RefList; |
||||
import org.eclipse.jgit.util.RefMap; |
||||
|
||||
/** */ |
||||
public abstract class DfsRefDatabase extends RefDatabase { |
||||
private final DfsRepository repository; |
||||
|
||||
private final AtomicReference<RefCache> cache; |
||||
|
||||
/** |
||||
* Initialize the reference database for a repository. |
||||
* |
||||
* @param repository |
||||
* the repository this database instance manages references for. |
||||
*/ |
||||
protected DfsRefDatabase(DfsRepository repository) { |
||||
this.repository = repository; |
||||
this.cache = new AtomicReference<RefCache>(); |
||||
} |
||||
|
||||
/** @return the repository the database holds the references of. */ |
||||
protected DfsRepository getRepository() { |
||||
return repository; |
||||
} |
||||
|
||||
boolean exists() throws IOException { |
||||
return 0 < read().size(); |
||||
} |
||||
|
||||
@Override |
||||
public Ref getRef(String needle) throws IOException { |
||||
RefCache curr = read(); |
||||
for (String prefix : SEARCH_PATH) { |
||||
Ref ref = curr.ids.get(prefix + needle); |
||||
if (ref != null) { |
||||
ref = resolve(ref, 0, curr.ids); |
||||
return ref; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
private Ref getOneRef(String refName) throws IOException { |
||||
RefCache curr = read(); |
||||
Ref ref = curr.ids.get(refName); |
||||
if (ref != null) |
||||
return resolve(ref, 0, curr.ids); |
||||
return ref; |
||||
} |
||||
|
||||
@Override |
||||
public List<Ref> getAdditionalRefs() { |
||||
return Collections.emptyList(); |
||||
} |
||||
|
||||
@Override |
||||
public Map<String, Ref> getRefs(String prefix) throws IOException { |
||||
RefCache curr = read(); |
||||
RefList<Ref> packed = RefList.emptyList(); |
||||
RefList<Ref> loose = curr.ids; |
||||
RefList.Builder<Ref> sym = new RefList.Builder<Ref>(curr.sym.size()); |
||||
|
||||
for (int idx = 0; idx < curr.sym.size(); idx++) { |
||||
Ref ref = curr.sym.get(idx); |
||||
String name = ref.getName(); |
||||
ref = resolve(ref, 0, loose); |
||||
if (ref != null && ref.getObjectId() != null) { |
||||
sym.add(ref); |
||||
} else { |
||||
// A broken symbolic reference, we have to drop it from the
|
||||
// collections the client is about to receive. Should be a
|
||||
// rare occurrence so pay a copy penalty.
|
||||
int toRemove = loose.find(name); |
||||
if (0 <= toRemove) |
||||
loose = loose.remove(toRemove); |
||||
} |
||||
} |
||||
|
||||
return new RefMap(prefix, packed, loose, sym.toRefList()); |
||||
} |
||||
|
||||
private Ref resolve(Ref ref, int depth, RefList<Ref> loose) |
||||
throws IOException { |
||||
if (!ref.isSymbolic()) |
||||
return ref; |
||||
|
||||
Ref dst = ref.getTarget(); |
||||
|
||||
if (MAX_SYMBOLIC_REF_DEPTH <= depth) |
||||
return null; // claim it doesn't exist
|
||||
|
||||
dst = loose.get(dst.getName()); |
||||
if (dst == null) |
||||
return ref; |
||||
|
||||
dst = resolve(dst, depth + 1, loose); |
||||
if (dst == null) |
||||
return null; |
||||
return new SymbolicRef(ref.getName(), dst); |
||||
} |
||||
|
||||
@Override |
||||
public Ref peel(Ref ref) throws IOException { |
||||
final Ref oldLeaf = ref.getLeaf(); |
||||
if (oldLeaf.isPeeled() || oldLeaf.getObjectId() == null) |
||||
return ref; |
||||
|
||||
Ref newLeaf = doPeel(oldLeaf); |
||||
|
||||
RefCache cur = read(); |
||||
int idx = cur.ids.find(oldLeaf.getName()); |
||||
if (0 <= idx && cur.ids.get(idx) == oldLeaf) { |
||||
RefList<Ref> newList = cur.ids.set(idx, newLeaf); |
||||
cache.compareAndSet(cur, new RefCache(newList, cur)); |
||||
cachePeeledState(oldLeaf, newLeaf); |
||||
} |
||||
|
||||
return recreate(ref, newLeaf); |
||||
} |
||||
|
||||
private Ref doPeel(final Ref leaf) throws MissingObjectException, |
||||
IOException { |
||||
RevWalk rw = new RevWalk(repository); |
||||
try { |
||||
RevObject obj = rw.parseAny(leaf.getObjectId()); |
||||
if (obj instanceof RevTag) { |
||||
return new ObjectIdRef.PeeledTag( |
||||
leaf.getStorage(), |
||||
leaf.getName(), |
||||
leaf.getObjectId(), |
||||
rw.peel(obj).copy()); |
||||
} else { |
||||
return new ObjectIdRef.PeeledNonTag( |
||||
leaf.getStorage(), |
||||
leaf.getName(), |
||||
leaf.getObjectId()); |
||||
} |
||||
} finally { |
||||
rw.release(); |
||||
} |
||||
} |
||||
|
||||
private static Ref recreate(Ref old, Ref leaf) { |
||||
if (old.isSymbolic()) { |
||||
Ref dst = recreate(old.getTarget(), leaf); |
||||
return new SymbolicRef(old.getName(), dst); |
||||
} |
||||
return leaf; |
||||
} |
||||
|
||||
@Override |
||||
public DfsRefUpdate newUpdate(String refName, boolean detach) |
||||
throws IOException { |
||||
boolean detachingSymbolicRef = false; |
||||
Ref ref = getOneRef(refName); |
||||
if (ref == null) |
||||
ref = new ObjectIdRef.Unpeeled(NEW, refName, null); |
||||
else |
||||
detachingSymbolicRef = detach && ref.isSymbolic(); |
||||
|
||||
if (detachingSymbolicRef) { |
||||
ref = new ObjectIdRef.Unpeeled(NEW, refName, ref.getObjectId()); |
||||
} |
||||
|
||||
DfsRefUpdate update = new DfsRefUpdate(this, ref); |
||||
if (detachingSymbolicRef) |
||||
update.setDetachingSymbolicRef(); |
||||
return update; |
||||
} |
||||
|
||||
@Override |
||||
public RefRename newRename(String fromName, String toName) |
||||
throws IOException { |
||||
DfsRefUpdate src = newUpdate(fromName, true); |
||||
DfsRefUpdate dst = newUpdate(toName, true); |
||||
return new DfsRefRename(src, dst); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isNameConflicting(String refName) throws IOException { |
||||
RefList<Ref> all = read().ids; |
||||
|
||||
// Cannot be nested within an existing reference.
|
||||
int lastSlash = refName.lastIndexOf('/'); |
||||
while (0 < lastSlash) { |
||||
String needle = refName.substring(0, lastSlash); |
||||
if (all.contains(needle)) |
||||
return true; |
||||
lastSlash = refName.lastIndexOf('/', lastSlash - 1); |
||||
} |
||||
|
||||
// Cannot be the container of an existing reference.
|
||||
String prefix = refName + '/'; |
||||
int idx = -(all.find(prefix) + 1); |
||||
if (idx < all.size() && all.get(idx).getName().startsWith(prefix)) |
||||
return true; |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public void create() { |
||||
// Nothing to do.
|
||||
} |
||||
|
||||
@Override |
||||
public void close() { |
||||
clearCache(); |
||||
} |
||||
|
||||
void clearCache() { |
||||
cache.set(null); |
||||
} |
||||
|
||||
void stored(Ref ref) { |
||||
RefCache oldCache, newCache; |
||||
do { |
||||
oldCache = cache.get(); |
||||
if (oldCache == null) |
||||
return; |
||||
newCache = oldCache.put(ref); |
||||
} while (!cache.compareAndSet(oldCache, newCache)); |
||||
} |
||||
|
||||
void removed(String refName) { |
||||
RefCache oldCache, newCache; |
||||
do { |
||||
oldCache = cache.get(); |
||||
if (oldCache == null) |
||||
return; |
||||
newCache = oldCache.remove(refName); |
||||
} while (!cache.compareAndSet(oldCache, newCache)); |
||||
} |
||||
|
||||
private RefCache read() throws IOException { |
||||
RefCache c = cache.get(); |
||||
if (c == null) { |
||||
c = scanAllRefs(); |
||||
cache.set(c); |
||||
} |
||||
return c; |
||||
} |
||||
|
||||
/** |
||||
* Read all known references in the repository. |
||||
* |
||||
* @return all current references of the repository. |
||||
* @throws IOException |
||||
* references cannot be accessed. |
||||
*/ |
||||
protected abstract RefCache scanAllRefs() throws IOException; |
||||
|
||||
/** |
||||
* Compare a reference, and put if it matches. |
||||
* |
||||
* @param oldRef |
||||
* old value to compare to. If the reference is expected to not |
||||
* exist the old value has a storage of |
||||
* {@link org.eclipse.jgit.lib.Ref.Storage#NEW} and an ObjectId |
||||
* value of {@code null}. |
||||
* @param newRef |
||||
* new reference to store. |
||||
* @return true if the put was successful; false otherwise. |
||||
* @throws IOException |
||||
* the reference cannot be put due to a system error. |
||||
*/ |
||||
protected abstract boolean compareAndPut(Ref oldRef, Ref newRef) |
||||
throws IOException; |
||||
|
||||
/** |
||||
* Compare a reference, and delete if it matches. |
||||
* |
||||
* @param oldRef |
||||
* the old reference information that was previously read. |
||||
* @return true if the remove was successful; false otherwise. |
||||
* @throws IOException |
||||
* the reference could not be removed due to a system error. |
||||
*/ |
||||
protected abstract boolean compareAndRemove(Ref oldRef) throws IOException; |
||||
|
||||
/** |
||||
* Update the cached peeled state of a reference |
||||
* <p> |
||||
* The ref database invokes this method after it peels a reference that had |
||||
* not been peeled before. This allows the storage to cache the peel state |
||||
* of the reference, and if it is actually peelable, the target that it |
||||
* peels to, so that on-the-fly peeling doesn't have to happen on the next |
||||
* reference read. |
||||
* |
||||
* @param oldLeaf |
||||
* the old reference. |
||||
* @param newLeaf |
||||
* the new reference, with peel information. |
||||
*/ |
||||
protected void cachePeeledState(Ref oldLeaf, Ref newLeaf) { |
||||
try { |
||||
compareAndPut(oldLeaf, newLeaf); |
||||
} catch (IOException e) { |
||||
// Ignore an exception during caching.
|
||||
} |
||||
} |
||||
|
||||
/** Collection of references managed by this database. */ |
||||
public static class RefCache { |
||||
final RefList<Ref> ids; |
||||
|
||||
final RefList<Ref> sym; |
||||
|
||||
/** |
||||
* Initialize a new reference cache. |
||||
* <p> |
||||
* The two reference lists supplied must be sorted in correct order |
||||
* (string compare order) by name. |
||||
* |
||||
* @param ids |
||||
* references that carry an ObjectId, and all of {@code sym}. |
||||
* @param sym |
||||
* references that are symbolic references to others. |
||||
*/ |
||||
public RefCache(RefList<Ref> ids, RefList<Ref> sym) { |
||||
this.ids = ids; |
||||
this.sym = sym; |
||||
} |
||||
|
||||
RefCache(RefList<Ref> ids, RefCache old) { |
||||
this(ids, old.sym); |
||||
} |
||||
|
||||
/** @return number of references in this cache. */ |
||||
public int size() { |
||||
return ids.size(); |
||||
} |
||||
|
||||
/** |
||||
* Find a reference by name. |
||||
* |
||||
* @param name |
||||
* full name of the reference. |
||||
* @return the reference, if it exists, otherwise null. |
||||
*/ |
||||
public Ref get(String name) { |
||||
return ids.get(name); |
||||
} |
||||
|
||||
/** |
||||
* Obtain a modified copy of the cache with a ref stored. |
||||
* <p> |
||||
* This cache instance is not modified by this method. |
||||
* |
||||
* @param ref |
||||
* reference to add or replace. |
||||
* @return a copy of this cache, with the reference added or replaced. |
||||
*/ |
||||
public RefCache put(Ref ref) { |
||||
RefList<Ref> newIds = this.ids.put(ref); |
||||
RefList<Ref> newSym = this.sym; |
||||
if (ref.isSymbolic()) { |
||||
newSym = newSym.put(ref); |
||||
} else { |
||||
int p = newSym.find(ref.getName()); |
||||
if (0 <= p) |
||||
newSym = newSym.remove(p); |
||||
} |
||||
return new RefCache(newIds, newSym); |
||||
} |
||||
|
||||
/** |
||||
* Obtain a modified copy of the cache with the ref removed. |
||||
* <p> |
||||
* This cache instance is not modified by this method. |
||||
* |
||||
* @param refName |
||||
* reference to remove, if it exists. |
||||
* @return a copy of this cache, with the reference removed. |
||||
*/ |
||||
public RefCache remove(String refName) { |
||||
RefList<Ref> newIds = this.ids; |
||||
int p = newIds.find(refName); |
||||
if (0 <= p) |
||||
newIds = newIds.remove(p); |
||||
|
||||
RefList<Ref> newSym = this.sym; |
||||
p = newSym.find(refName); |
||||
if (0 <= p) |
||||
newSym = newSym.remove(p); |
||||
return new RefCache(newIds, newSym); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,73 @@
|
||||
/* |
||||
* Copyright (C) 2011, 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.storage.dfs; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
import org.eclipse.jgit.lib.ObjectId; |
||||
import org.eclipse.jgit.lib.RefRename; |
||||
import org.eclipse.jgit.lib.RefUpdate.Result; |
||||
|
||||
final class DfsRefRename extends RefRename { |
||||
DfsRefRename(DfsRefUpdate src, DfsRefUpdate dst) { |
||||
super(src, dst); |
||||
} |
||||
|
||||
@Override |
||||
protected Result doRename() throws IOException { |
||||
// TODO Correctly handle renaming foo/bar to foo.
|
||||
// TODO Batch these together into one log update.
|
||||
|
||||
destination.setExpectedOldObjectId(ObjectId.zeroId()); |
||||
destination.setNewObjectId(source.getRef().getObjectId()); |
||||
switch (destination.update()) { |
||||
case NEW: |
||||
source.delete(); |
||||
return Result.RENAMED; |
||||
|
||||
default: |
||||
return destination.getResult(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,157 @@
|
||||
/* |
||||
* Copyright (C) 2011, 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.storage.dfs; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
import org.eclipse.jgit.lib.ObjectIdRef; |
||||
import org.eclipse.jgit.lib.Ref; |
||||
import org.eclipse.jgit.lib.RefUpdate; |
||||
import org.eclipse.jgit.lib.SymbolicRef; |
||||
import org.eclipse.jgit.lib.Ref.Storage; |
||||
import org.eclipse.jgit.revwalk.RevObject; |
||||
import org.eclipse.jgit.revwalk.RevTag; |
||||
import org.eclipse.jgit.revwalk.RevWalk; |
||||
|
||||
final class DfsRefUpdate extends RefUpdate { |
||||
private final DfsRefDatabase refdb; |
||||
|
||||
private Ref dstRef; |
||||
|
||||
private RevWalk rw; |
||||
|
||||
DfsRefUpdate(DfsRefDatabase refdb, Ref ref) { |
||||
super(ref); |
||||
this.refdb = refdb; |
||||
} |
||||
|
||||
@Override |
||||
protected DfsRefDatabase getRefDatabase() { |
||||
return refdb; |
||||
} |
||||
|
||||
@Override |
||||
protected DfsRepository getRepository() { |
||||
return refdb.getRepository(); |
||||
} |
||||
|
||||
@Override |
||||
protected boolean tryLock(boolean deref) throws IOException { |
||||
dstRef = getRef(); |
||||
if (deref) |
||||
dstRef = dstRef.getLeaf(); |
||||
|
||||
if (dstRef.isSymbolic()) |
||||
setOldObjectId(null); |
||||
else |
||||
setOldObjectId(dstRef.getObjectId()); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
protected void unlock() { |
||||
// No state is held while "locked".
|
||||
} |
||||
|
||||
@Override |
||||
public Result update(RevWalk walk) throws IOException { |
||||
try { |
||||
rw = walk; |
||||
return super.update(walk); |
||||
} finally { |
||||
rw = null; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected Result doUpdate(Result desiredResult) throws IOException { |
||||
ObjectIdRef newRef; |
||||
RevObject obj = rw.parseAny(getNewObjectId()); |
||||
if (obj instanceof RevTag) { |
||||
newRef = new ObjectIdRef.PeeledTag( |
||||
Storage.PACKED, |
||||
dstRef.getName(), |
||||
getNewObjectId(), |
||||
rw.peel(obj).copy()); |
||||
} else { |
||||
newRef = new ObjectIdRef.PeeledNonTag( |
||||
Storage.PACKED, |
||||
dstRef.getName(), |
||||
getNewObjectId()); |
||||
} |
||||
|
||||
if (getRefDatabase().compareAndPut(dstRef, newRef)) { |
||||
getRefDatabase().stored(newRef); |
||||
return desiredResult; |
||||
} |
||||
return Result.LOCK_FAILURE; |
||||
} |
||||
|
||||
@Override |
||||
protected Result doDelete(Result desiredResult) throws IOException { |
||||
if (getRefDatabase().compareAndRemove(dstRef)) { |
||||
getRefDatabase().removed(dstRef.getName()); |
||||
return desiredResult; |
||||
} |
||||
return Result.LOCK_FAILURE; |
||||
} |
||||
|
||||
@Override |
||||
protected Result doLink(String target) throws IOException { |
||||
final SymbolicRef newRef = new SymbolicRef( |
||||
dstRef.getName(), |
||||
new ObjectIdRef.Unpeeled( |
||||
Storage.NEW, |
||||
target, |
||||
null)); |
||||
if (getRefDatabase().compareAndPut(dstRef, newRef)) { |
||||
getRefDatabase().stored(newRef); |
||||
if (dstRef.getStorage() == Ref.Storage.NEW) |
||||
return Result.NEW; |
||||
return Result.FORCED; |
||||
} |
||||
return Result.LOCK_FAILURE; |
||||
} |
||||
} |
@ -0,0 +1,129 @@
|
||||
/* |
||||
* Copyright (C) 2011, 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.storage.dfs; |
||||
|
||||
import java.io.IOException; |
||||
import java.text.MessageFormat; |
||||
|
||||
import org.eclipse.jgit.JGitText; |
||||
import org.eclipse.jgit.lib.Constants; |
||||
import org.eclipse.jgit.lib.RefUpdate; |
||||
import org.eclipse.jgit.lib.Repository; |
||||
import org.eclipse.jgit.lib.StoredConfig; |
||||
import org.eclipse.jgit.storage.file.ReflogReader; |
||||
|
||||
/** A Git repository on a DFS. */ |
||||
public abstract class DfsRepository extends Repository { |
||||
private final DfsConfig config; |
||||
|
||||
private final DfsRepositoryDescription description; |
||||
|
||||
/** |
||||
* Initialize a DFS repository. |
||||
* |
||||
* @param builder |
||||
* description of the repository. |
||||
*/ |
||||
protected DfsRepository(DfsRepositoryBuilder builder) { |
||||
super(builder); |
||||
this.config = new DfsConfig(); |
||||
this.description = builder.getRepositoryDescription(); |
||||
} |
||||
|
||||
@Override |
||||
public abstract DfsObjDatabase getObjectDatabase(); |
||||
|
||||
@Override |
||||
public abstract DfsRefDatabase getRefDatabase(); |
||||
|
||||
/** @return a description of this repository. */ |
||||
public DfsRepositoryDescription getDescription() { |
||||
return description; |
||||
} |
||||
|
||||
/** |
||||
* Check if the repository already exists. |
||||
* |
||||
* @return true if the repository exists; false if it is new. |
||||
* @throws IOException |
||||
* the repository cannot be checked. |
||||
*/ |
||||
public boolean exists() throws IOException { |
||||
return getRefDatabase().exists(); |
||||
} |
||||
|
||||
@Override |
||||
public void create(boolean bare) throws IOException { |
||||
if (exists()) |
||||
throw new IOException(MessageFormat.format( |
||||
JGitText.get().repositoryAlreadyExists, "")); |
||||
|
||||
String master = Constants.R_HEADS + Constants.MASTER; |
||||
RefUpdate.Result result = updateRef(Constants.HEAD, true).link(master); |
||||
if (result != RefUpdate.Result.NEW) |
||||
throw new IOException(result.name()); |
||||
} |
||||
|
||||
@Override |
||||
public StoredConfig getConfig() { |
||||
return config; |
||||
} |
||||
|
||||
@Override |
||||
public void scanForRepoChanges() throws IOException { |
||||
getRefDatabase().clearCache(); |
||||
getObjectDatabase().clearCache(); |
||||
} |
||||
|
||||
@Override |
||||
public void notifyIndexChanged() { |
||||
// Do not send notifications.
|
||||
// There is no index, as there is no working tree.
|
||||
} |
||||
|
||||
@Override |
||||
public ReflogReader getReflogReader(String refName) throws IOException { |
||||
throw new UnsupportedOperationException(); |
||||
} |
||||
} |
@ -0,0 +1,161 @@
|
||||
/* |
||||
* Copyright (C) 2011, 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.storage.dfs; |
||||
|
||||
import java.io.File; |
||||
import java.io.IOException; |
||||
|
||||
import org.eclipse.jgit.lib.BaseRepositoryBuilder; |
||||
|
||||
/** |
||||
* Constructs a {@link DfsRepository}. |
||||
* |
||||
* @param <B> |
||||
* type of the builder class. |
||||
* @param <R> |
||||
* type of the repository class. |
||||
*/ |
||||
public abstract class DfsRepositoryBuilder<B extends DfsRepositoryBuilder, R extends DfsRepository> |
||||
extends BaseRepositoryBuilder<B, R> { |
||||
private DfsReaderOptions readerOptions; |
||||
|
||||
private DfsRepositoryDescription repoDesc; |
||||
|
||||
/** @return options used by readers accessing the repository. */ |
||||
public DfsReaderOptions getReaderOptions() { |
||||
return readerOptions; |
||||
} |
||||
|
||||
/** |
||||
* Set the reader options. |
||||
* |
||||
* @param opt |
||||
* new reader options object. |
||||
* @return {@code this} |
||||
*/ |
||||
public B setReaderOptions(DfsReaderOptions opt) { |
||||
readerOptions = opt; |
||||
return self(); |
||||
} |
||||
|
||||
/** @return a description of the repository. */ |
||||
public DfsRepositoryDescription getRepositoryDescription() { |
||||
return repoDesc; |
||||
} |
||||
|
||||
/** |
||||
* Set the repository description. |
||||
* |
||||
* @param desc |
||||
* new repository description object. |
||||
* @return {@code this} |
||||
*/ |
||||
public B setRepositoryDescription(DfsRepositoryDescription desc) { |
||||
repoDesc = desc; |
||||
return self(); |
||||
} |
||||
|
||||
@Override |
||||
public B setup() throws IllegalArgumentException, IOException { |
||||
super.setup(); |
||||
if (getReaderOptions() == null) |
||||
setReaderOptions(new DfsReaderOptions()); |
||||
if (getRepositoryDescription() == null) |
||||
setRepositoryDescription(new DfsRepositoryDescription()); |
||||
return self(); |
||||
} |
||||
|
||||
/** |
||||
* Create a repository matching the configuration in this builder. |
||||
* <p> |
||||
* If an option was not set, the build method will try to default the option |
||||
* based on other options. If insufficient information is available, an |
||||
* exception is thrown to the caller. |
||||
* |
||||
* @return a repository matching this configuration. |
||||
* @throws IllegalArgumentException |
||||
* insufficient parameters were set. |
||||
* @throws IOException |
||||
* the repository could not be accessed to configure the rest of |
||||
* the builder's parameters. |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
@Override |
||||
public abstract R build() throws IOException; |
||||
|
||||
// We don't support local file IO and thus shouldn't permit these to set.
|
||||
|
||||
@Override |
||||
public B setGitDir(File gitDir) { |
||||
if (gitDir != null) |
||||
throw new IllegalArgumentException(); |
||||
return self(); |
||||
} |
||||
|
||||
@Override |
||||
public B setObjectDirectory(File objectDirectory) { |
||||
if (objectDirectory != null) |
||||
throw new IllegalArgumentException(); |
||||
return self(); |
||||
} |
||||
|
||||
@Override |
||||
public B addAlternateObjectDirectory(File other) { |
||||
throw new UnsupportedOperationException("Alternates not supported"); |
||||
} |
||||
|
||||
@Override |
||||
public B setWorkTree(File workTree) { |
||||
if (workTree != null) |
||||
throw new IllegalArgumentException(); |
||||
return self(); |
||||
} |
||||
|
||||
@Override |
||||
public B setIndexFile(File indexFile) { |
||||
if (indexFile != null) |
||||
throw new IllegalArgumentException(); |
||||
return self(); |
||||
} |
||||
} |
@ -0,0 +1,91 @@
|
||||
/* |
||||
* Copyright (C) 2011, 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.storage.dfs; |
||||
|
||||
/** A description of a Git repository on a DFS. */ |
||||
public class DfsRepositoryDescription { |
||||
private final String repositoryName; |
||||
|
||||
/** Initialize a new, empty repository description. */ |
||||
public DfsRepositoryDescription() { |
||||
this(null); |
||||
} |
||||
|
||||
/** |
||||
* Initialize a new repository description. |
||||
* |
||||
* @param repositoryName |
||||
* the name of the repository. |
||||
*/ |
||||
public DfsRepositoryDescription(String repositoryName) { |
||||
this.repositoryName = repositoryName; |
||||
} |
||||
|
||||
/** @return the name of the repository. */ |
||||
public String getRepositoryName() { |
||||
return repositoryName; |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
if (getRepositoryName() != null) |
||||
return getRepositoryName().hashCode(); |
||||
return System.identityHashCode(this); |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object b) { |
||||
if (b instanceof DfsRepositoryDescription){ |
||||
String name = getRepositoryName(); |
||||
String otherName = ((DfsRepositoryDescription) b).getRepositoryName(); |
||||
return name != null ? name.equals(otherName) : this == b; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "DfsRepositoryDescription[" + getRepositoryName() + "]"; |
||||
} |
||||
} |
@ -0,0 +1,60 @@
|
||||
/* |
||||
* Copyright (C) 2011, 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.storage.dfs; |
||||
|
||||
import org.eclipse.jgit.nls.NLS; |
||||
import org.eclipse.jgit.nls.TranslationBundle; |
||||
|
||||
/** Translation bundle for the DFS storage implementation. */ |
||||
public class DfsText extends TranslationBundle { |
||||
/** @return instance of this translation bundle */ |
||||
public static DfsText get() { |
||||
return NLS.getBundleFor(DfsText.class); |
||||
} |
||||
|
||||
/***/ public String cannotReadIndex; |
||||
/***/ public String shortReadOfBlock; |
||||
/***/ public String shortReadOfIndex; |
||||
/***/ public String willNotStoreEmptyPack; |
||||
} |
@ -0,0 +1,285 @@
|
||||
package org.eclipse.jgit.storage.dfs; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.FileNotFoundException; |
||||
import java.io.IOException; |
||||
import java.nio.ByteBuffer; |
||||
import java.util.ArrayList; |
||||
import java.util.Collection; |
||||
import java.util.List; |
||||
import java.util.concurrent.ConcurrentHashMap; |
||||
import java.util.concurrent.ConcurrentMap; |
||||
import java.util.concurrent.atomic.AtomicInteger; |
||||
|
||||
import org.eclipse.jgit.lib.Ref; |
||||
import org.eclipse.jgit.lib.Ref.Storage; |
||||
import org.eclipse.jgit.util.RefList; |
||||
|
||||
/** |
||||
* Git repository stored entirely in the local process memory. |
||||
* <p> |
||||
* This implementation builds on the DFS repository by storing all reference and |
||||
* object data in the local process. It is not very efficient and exists only |
||||
* for unit testing and small experiments. |
||||
* <p> |
||||
* The repository is thread-safe. Memory used is released only when this object |
||||
* is garbage collected. Closing the repository has no impact on its memory. |
||||
*/ |
||||
public class InMemoryRepository extends DfsRepository { |
||||
private final DfsObjDatabase objdb; |
||||
|
||||
private final DfsRefDatabase refdb; |
||||
|
||||
/** |
||||
* Initialize a new in-memory repository. |
||||
* |
||||
* @param repoDesc |
||||
* description of the repository. |
||||
*/ |
||||
public InMemoryRepository(DfsRepository repoDesc) { |
||||
super(new DfsRepositoryBuilder<DfsRepositoryBuilder, InMemoryRepository>() { |
||||
@Override |
||||
public InMemoryRepository build() throws IOException { |
||||
throw new UnsupportedOperationException(); |
||||
} |
||||
}); |
||||
|
||||
objdb = new MemObjDatabase(this); |
||||
refdb = new MemRefDatabase(); |
||||
} |
||||
|
||||
@Override |
||||
public DfsObjDatabase getObjectDatabase() { |
||||
return objdb; |
||||
} |
||||
|
||||
@Override |
||||
public DfsRefDatabase getRefDatabase() { |
||||
return refdb; |
||||
} |
||||
|
||||
private class MemObjDatabase extends DfsObjDatabase { |
||||
private final AtomicInteger packId = new AtomicInteger(); |
||||
private List<DfsPackDescription> packs = new ArrayList<DfsPackDescription>(); |
||||
|
||||
MemObjDatabase(DfsRepository repo) { |
||||
super(repo, new DfsReaderOptions()); |
||||
} |
||||
|
||||
@Override |
||||
protected synchronized List<DfsPackDescription> listPacks() { |
||||
return packs; |
||||
} |
||||
|
||||
@Override |
||||
protected DfsPackDescription newPack(PackSource source) { |
||||
int id = packId.incrementAndGet(); |
||||
return new MemPack("pack-" + id + "-" + source.name(), |
||||
getRepository().getDescription()); |
||||
} |
||||
|
||||
@Override |
||||
protected synchronized void commitPack( |
||||
Collection<DfsPackDescription> desc, |
||||
Collection<DfsPackDescription> replace) { |
||||
List<DfsPackDescription> n; |
||||
n = new ArrayList<DfsPackDescription>(desc.size() + packs.size()); |
||||
n.addAll(desc); |
||||
n.addAll(packs); |
||||
if (replace != null) |
||||
n.removeAll(replace); |
||||
packs = n; |
||||
} |
||||
|
||||
@Override |
||||
protected void rollbackPack(Collection<DfsPackDescription> desc) { |
||||
// Do nothing. Pack is not recorded until commitPack.
|
||||
} |
||||
|
||||
@Override |
||||
protected ReadableChannel openPackFile(DfsPackDescription desc) |
||||
throws FileNotFoundException { |
||||
MemPack memPack = (MemPack) desc; |
||||
if (memPack.packFile == null) |
||||
throw new FileNotFoundException(desc.getPackName()); |
||||
return new ByteArrayReadableChannel(memPack.packFile); |
||||
} |
||||
|
||||
@Override |
||||
protected ReadableChannel openPackIndex(DfsPackDescription desc) |
||||
throws FileNotFoundException { |
||||
MemPack memPack = (MemPack) desc; |
||||
if (memPack.packIndex == null) |
||||
throw new FileNotFoundException(desc.getIndexName()); |
||||
return new ByteArrayReadableChannel(memPack.packIndex); |
||||
} |
||||
|
||||
@Override |
||||
protected DfsOutputStream writePackFile(DfsPackDescription desc) { |
||||
final MemPack memPack = (MemPack) desc; |
||||
return new Out() { |
||||
@Override |
||||
public void flush() { |
||||
memPack.packFile = getData(); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
@Override |
||||
protected DfsOutputStream writePackIndex(DfsPackDescription desc) { |
||||
final MemPack memPack = (MemPack) desc; |
||||
return new Out() { |
||||
@Override |
||||
public void flush() { |
||||
memPack.packIndex = getData(); |
||||
} |
||||
}; |
||||
} |
||||
} |
||||
|
||||
private static class MemPack extends DfsPackDescription { |
||||
private byte[] packFile; |
||||
|
||||
private byte[] packIndex; |
||||
|
||||
MemPack(String name, DfsRepositoryDescription repoDesc) { |
||||
super(repoDesc, name); |
||||
} |
||||
} |
||||
|
||||
private abstract static class Out extends DfsOutputStream { |
||||
private final ByteArrayOutputStream dst = new ByteArrayOutputStream(); |
||||
|
||||
private byte[] data; |
||||
|
||||
@Override |
||||
public void write(byte[] buf, int off, int len) { |
||||
data = null; |
||||
dst.write(buf, off, len); |
||||
} |
||||
|
||||
@Override |
||||
public int read(long position, ByteBuffer buf) { |
||||
byte[] d = getData(); |
||||
int n = Math.min(buf.remaining(), d.length - (int) position); |
||||
if (n == 0) |
||||
return -1; |
||||
buf.put(d, (int) position, n); |
||||
return n; |
||||
} |
||||
|
||||
byte[] getData() { |
||||
if (data == null) |
||||
data = dst.toByteArray(); |
||||
return data; |
||||
} |
||||
|
||||
@Override |
||||
public abstract void flush(); |
||||
|
||||
@Override |
||||
public void close() { |
||||
flush(); |
||||
} |
||||
|
||||
} |
||||
|
||||
private static class ByteArrayReadableChannel implements ReadableChannel { |
||||
private final byte[] data; |
||||
|
||||
private int position; |
||||
|
||||
private boolean open = true; |
||||
|
||||
ByteArrayReadableChannel(byte[] buf) { |
||||
data = buf; |
||||
} |
||||
|
||||
public int read(ByteBuffer dst) { |
||||
int n = Math.min(dst.remaining(), data.length - position); |
||||
if (n == 0) |
||||
return -1; |
||||
dst.put(data, position, n); |
||||
position += n; |
||||
return n; |
||||
} |
||||
|
||||
public void close() { |
||||
open = false; |
||||
} |
||||
|
||||
public boolean isOpen() { |
||||
return open; |
||||
} |
||||
|
||||
public long position() { |
||||
return position; |
||||
} |
||||
|
||||
public void position(long newPosition) { |
||||
position = (int) newPosition; |
||||
} |
||||
|
||||
public long size() { |
||||
return data.length; |
||||
} |
||||
|
||||
public int blockSize() { |
||||
return 0; |
||||
} |
||||
} |
||||
|
||||
private class MemRefDatabase extends DfsRefDatabase { |
||||
private final ConcurrentMap<String, Ref> refs = new ConcurrentHashMap<String, Ref>(); |
||||
|
||||
MemRefDatabase() { |
||||
super(InMemoryRepository.this); |
||||
} |
||||
|
||||
@Override |
||||
protected RefCache scanAllRefs() throws IOException { |
||||
RefList.Builder<Ref> ids = new RefList.Builder<Ref>(); |
||||
RefList.Builder<Ref> sym = new RefList.Builder<Ref>(); |
||||
for (Ref ref : refs.values()) { |
||||
if (ref.isSymbolic()) |
||||
sym.add(ref); |
||||
ids.add(ref); |
||||
} |
||||
ids.sort(); |
||||
sym.sort(); |
||||
return new RefCache(ids.toRefList(), sym.toRefList()); |
||||
} |
||||
|
||||
@Override |
||||
protected boolean compareAndPut(Ref oldRef, Ref newRef) |
||||
throws IOException { |
||||
String name = newRef.getName(); |
||||
if (oldRef == null || oldRef.getStorage() == Storage.NEW) |
||||
return refs.putIfAbsent(name, newRef) == null; |
||||
Ref cur = refs.get(name); |
||||
if (cur != null && eq(cur, oldRef)) |
||||
return refs.replace(name, cur, newRef); |
||||
else |
||||
return false; |
||||
|
||||
} |
||||
|
||||
@Override |
||||
protected boolean compareAndRemove(Ref oldRef) throws IOException { |
||||
String name = oldRef.getName(); |
||||
Ref cur = refs.get(name); |
||||
if (cur != null && eq(cur, oldRef)) |
||||
return refs.remove(name, cur); |
||||
else |
||||
return false; |
||||
} |
||||
|
||||
private boolean eq(Ref a, Ref b) { |
||||
if (a.getObjectId() == null && b.getObjectId() == null) |
||||
return true; |
||||
if (a.getObjectId() != null) |
||||
return a.getObjectId().equals(b.getObjectId()); |
||||
return false; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,128 @@
|
||||
/* |
||||
* Copyright (C) 2010, 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.storage.dfs; |
||||
|
||||
import java.io.BufferedInputStream; |
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.util.zip.InflaterInputStream; |
||||
|
||||
import org.eclipse.jgit.errors.LargeObjectException; |
||||
import org.eclipse.jgit.errors.MissingObjectException; |
||||
import org.eclipse.jgit.lib.ObjectId; |
||||
import org.eclipse.jgit.lib.ObjectLoader; |
||||
import org.eclipse.jgit.lib.ObjectStream; |
||||
|
||||
final class LargePackedWholeObject extends ObjectLoader { |
||||
private final int type; |
||||
|
||||
private final long size; |
||||
|
||||
private final long objectOffset; |
||||
|
||||
private final int headerLength; |
||||
|
||||
private final DfsPackFile pack; |
||||
|
||||
private final DfsObjDatabase db; |
||||
|
||||
LargePackedWholeObject(int type, long size, long objectOffset, |
||||
int headerLength, DfsPackFile pack, DfsObjDatabase db) { |
||||
this.type = type; |
||||
this.size = size; |
||||
this.objectOffset = objectOffset; |
||||
this.headerLength = headerLength; |
||||
this.pack = pack; |
||||
this.db = db; |
||||
} |
||||
|
||||
@Override |
||||
public int getType() { |
||||
return type; |
||||
} |
||||
|
||||
@Override |
||||
public long getSize() { |
||||
return size; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isLarge() { |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public byte[] getCachedBytes() throws LargeObjectException { |
||||
throw new LargeObjectException(); |
||||
} |
||||
|
||||
@Override |
||||
public ObjectStream openStream() throws MissingObjectException, IOException { |
||||
DfsReader ctx = new DfsReader(db); |
||||
InputStream in; |
||||
try { |
||||
in = new PackInputStream(pack, objectOffset + headerLength, ctx); |
||||
} catch (IOException packGone) { |
||||
// If the pack file cannot be pinned into the cursor, it
|
||||
// probably was repacked recently. Go find the object
|
||||
// again and open the stream from that location instead.
|
||||
//
|
||||
try { |
||||
ObjectId obj = pack.findObjectForOffset(ctx, objectOffset); |
||||
return ctx.open(obj, type).openStream(); |
||||
} finally { |
||||
ctx.release(); |
||||
} |
||||
} |
||||
|
||||
// Align buffer to inflater size, at a larger than default block.
|
||||
// This reduces the number of context switches from the
|
||||
// caller down into the pack stream inflation.
|
||||
int bufsz = 8192; |
||||
in = new BufferedInputStream( |
||||
new InflaterInputStream(in, ctx.inflater(), bufsz), |
||||
bufsz); |
||||
return new ObjectStream.Filter(type, size, in); |
||||
} |
||||
} |
@ -0,0 +1,85 @@
|
||||
/* |
||||
* Copyright (C) 2010, 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.storage.dfs; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
|
||||
final class PackInputStream extends InputStream { |
||||
private final DfsReader ctx; |
||||
|
||||
private final DfsPackFile pack; |
||||
|
||||
private long pos; |
||||
|
||||
PackInputStream(DfsPackFile pack, long pos, DfsReader ctx) |
||||
throws IOException { |
||||
this.pack = pack; |
||||
this.pos = pos; |
||||
this.ctx = ctx; |
||||
|
||||
// Pin the first window, to ensure the pack is open and valid.
|
||||
//
|
||||
ctx.pin(pack, pos); |
||||
} |
||||
|
||||
@Override |
||||
public int read(byte[] b, int off, int len) throws IOException { |
||||
int n = ctx.copy(pack, pos, b, off, len); |
||||
pos += n; |
||||
return n; |
||||
} |
||||
|
||||
@Override |
||||
public int read() throws IOException { |
||||
byte[] buf = new byte[1]; |
||||
int n = read(buf, 0, 1); |
||||
return n == 1 ? buf[0] & 0xff : -1; |
||||
} |
||||
|
||||
@Override |
||||
public void close() { |
||||
ctx.release(); |
||||
} |
||||
} |
@ -0,0 +1,61 @@
|
||||
/* |
||||
* Copyright (C) 2011, 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.storage.dfs; |
||||
|
||||
import java.util.concurrent.RejectedExecutionHandler; |
||||
import java.util.concurrent.ThreadPoolExecutor; |
||||
|
||||
/** This handler aborts a {@link ReadAheadTask} when the queue is full. */ |
||||
final class ReadAheadRejectedExecutionHandler implements |
||||
RejectedExecutionHandler { |
||||
static final ReadAheadRejectedExecutionHandler INSTANCE = new ReadAheadRejectedExecutionHandler(); |
||||
|
||||
private ReadAheadRejectedExecutionHandler() { |
||||
// Singleton, do not create more instances.
|
||||
} |
||||
|
||||
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { |
||||
((ReadAheadTask.TaskFuture) r).task.abort(); |
||||
} |
||||
} |
@ -0,0 +1,241 @@
|
||||
/* |
||||
* Copyright (C) 2011, 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.storage.dfs; |
||||
|
||||
import java.io.EOFException; |
||||
import java.io.IOException; |
||||
import java.util.List; |
||||
import java.util.concurrent.Callable; |
||||
import java.util.concurrent.CountDownLatch; |
||||
import java.util.concurrent.ExecutionException; |
||||
import java.util.concurrent.Future; |
||||
import java.util.concurrent.TimeUnit; |
||||
import java.util.concurrent.TimeoutException; |
||||
|
||||
import org.eclipse.jgit.util.IO; |
||||
|
||||
final class ReadAheadTask implements Callable<Void> { |
||||
private final DfsBlockCache cache; |
||||
|
||||
private final ReadableChannel channel; |
||||
|
||||
private final List<BlockFuture> futures; |
||||
|
||||
private boolean running; |
||||
|
||||
ReadAheadTask(DfsBlockCache cache, ReadableChannel channel, |
||||
List<BlockFuture> futures) { |
||||
this.cache = cache; |
||||
this.channel = channel; |
||||
this.futures = futures; |
||||
} |
||||
|
||||
public Void call() { |
||||
int idx = 0; |
||||
try { |
||||
synchronized (this) { |
||||
if (channel.isOpen()) |
||||
running = true; |
||||
else |
||||
return null; |
||||
} |
||||
|
||||
long position = channel.position(); |
||||
for (; idx < futures.size() && !Thread.interrupted(); idx++) { |
||||
BlockFuture f = futures.get(idx); |
||||
if (cache.contains(f.pack, f.start)) { |
||||
f.done(); |
||||
continue; |
||||
} |
||||
|
||||
if (position != f.start) |
||||
channel.position(f.start); |
||||
|
||||
int size = (int) (f.end - f.start); |
||||
byte[] buf = new byte[size]; |
||||
if (IO.read(channel, buf, 0, size) != size) |
||||
throw new EOFException(); |
||||
|
||||
cache.put(new DfsBlock(f.pack, f.start, buf)); |
||||
f.done(); |
||||
position = f.end; |
||||
} |
||||
} catch (IOException err) { |
||||
// Ignore read-ahead errors. These will be caught later on.
|
||||
} finally { |
||||
for (; idx < futures.size(); idx++) |
||||
futures.get(idx).abort(); |
||||
close(); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
void abort() { |
||||
for (BlockFuture f : futures) |
||||
f.abort(); |
||||
|
||||
synchronized (this) { |
||||
if (!running) |
||||
close(); |
||||
} |
||||
} |
||||
|
||||
private synchronized void close() { |
||||
try { |
||||
if (channel.isOpen()) |
||||
channel.close(); |
||||
} catch (IOException err) { |
||||
// Ignore close errors on a read-only channel.
|
||||
} |
||||
} |
||||
|
||||
static final class TaskFuture extends java.util.concurrent.FutureTask<Void> { |
||||
final ReadAheadTask task; |
||||
|
||||
TaskFuture(ReadAheadTask task) { |
||||
super(task); |
||||
this.task = task; |
||||
} |
||||
|
||||
@Override |
||||
public boolean cancel(boolean mayInterruptIfRunning) { |
||||
if (super.cancel(mayInterruptIfRunning)) { |
||||
task.abort(); |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
/** A scheduled read-ahead block load. */ |
||||
static final class BlockFuture implements Future<Void> { |
||||
private static enum State { |
||||
PENDING, DONE, CANCELLED; |
||||
} |
||||
|
||||
private volatile State state; |
||||
|
||||
private volatile Future<?> task; |
||||
|
||||
private final CountDownLatch latch; |
||||
|
||||
final DfsPackKey pack; |
||||
|
||||
final long start; |
||||
|
||||
final long end; |
||||
|
||||
BlockFuture(DfsPackKey key, long start, long end) { |
||||
this.state = State.PENDING; |
||||
this.latch = new CountDownLatch(1); |
||||
this.pack = key; |
||||
this.start = start; |
||||
this.end = end; |
||||
} |
||||
|
||||
synchronized void setTask(Future<?> task) { |
||||
if (state == State.PENDING) |
||||
this.task = task; |
||||
} |
||||
|
||||
boolean contains(DfsPackKey want, long pos) { |
||||
return pack == want && start <= pos && pos < end; |
||||
} |
||||
|
||||
synchronized void done() { |
||||
if (state == State.PENDING) { |
||||
latch.countDown(); |
||||
state = State.DONE; |
||||
task = null; |
||||
} |
||||
} |
||||
|
||||
synchronized void abort() { |
||||
if (state == State.PENDING) { |
||||
latch.countDown(); |
||||
state = State.CANCELLED; |
||||
task = null; |
||||
} |
||||
} |
||||
|
||||
public boolean cancel(boolean mayInterruptIfRunning) { |
||||
Future<?> t = task; |
||||
if (t == null) |
||||
return false; |
||||
|
||||
boolean r = t.cancel(mayInterruptIfRunning); |
||||
abort(); |
||||
return r; |
||||
} |
||||
|
||||
public Void get() throws InterruptedException, ExecutionException { |
||||
latch.await(); |
||||
return null; |
||||
} |
||||
|
||||
public Void get(long timeout, TimeUnit unit) |
||||
throws InterruptedException, ExecutionException, |
||||
TimeoutException { |
||||
if (latch.await(timeout, unit)) |
||||
return null; |
||||
else |
||||
throw new TimeoutException(); |
||||
} |
||||
|
||||
public boolean isCancelled() { |
||||
State s = state; |
||||
if (s == State.DONE) |
||||
return false; |
||||
if (s == State.CANCELLED) |
||||
return true; |
||||
|
||||
Future<?> t = task; |
||||
return t != null ? t.isCancelled() : true; |
||||
} |
||||
|
||||
public boolean isDone() { |
||||
return state == State.DONE; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,103 @@
|
||||
/* |
||||
* Copyright (C) 2011, 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.storage.dfs; |
||||
|
||||
import java.io.IOException; |
||||
import java.nio.channels.ReadableByteChannel; |
||||
|
||||
/** Readable random access byte channel from a file. */ |
||||
public interface ReadableChannel extends ReadableByteChannel { |
||||
/** |
||||
* Get the current position of the channel. |
||||
* |
||||
* @return r current offset. |
||||
* @throws IOException |
||||
* the channel's current position cannot be obtained. |
||||
*/ |
||||
public long position() throws IOException; |
||||
|
||||
/** |
||||
* Seek the current position of the channel to a new offset. |
||||
* |
||||
* @param newPosition |
||||
* position to move the channel to. The next read will start from |
||||
* here. This should be a multiple of the {@link #blockSize()}. |
||||
* @throws IOException |
||||
* the position cannot be updated. This may be because the |
||||
* channel only supports block aligned IO and the current |
||||
* position is not block aligned. |
||||
*/ |
||||
public void position(long newPosition) throws IOException; |
||||
|
||||
/** |
||||
* Get the total size of the channel. |
||||
* <p> |
||||
* Prior to reading from a channel the size might not yet be known. |
||||
* Implementors may return -1 until after the first read method call. Once a |
||||
* read has been completed, the underlying file size should be available. |
||||
* |
||||
* @return r total size of the channel; -1 if not yet available. |
||||
* @throws IOException |
||||
* the size cannot be determined. |
||||
*/ |
||||
public long size() throws IOException; |
||||
|
||||
/** |
||||
* Get the recommended alignment for reads. |
||||
* <p> |
||||
* Starting a read at multiples of the blockSize is more efficient than |
||||
* starting a read at any other position. If 0 or -1 the channel does not |
||||
* have any specific block size recommendation. |
||||
* <p> |
||||
* Channels should not recommend large block sizes. Sizes up to 1-4 MiB may |
||||
* be reasonable, but sizes above that may be horribly inefficient. The |
||||
* {@link DfsBlockCache} favors the alignment suggested by the channel |
||||
* rather than the configured size under the assumption that reads are very |
||||
* expensive and the channel knows what size is best to access it with. |
||||
* |
||||
* @return recommended alignment size for randomly positioned reads. Does |
||||
* not need to be a power of 2. |
||||
*/ |
||||
public int blockSize(); |
||||
} |
@ -0,0 +1,90 @@
|
||||
/* |
||||
* Copyright (C) 2011, 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.util.io; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.OutputStream; |
||||
|
||||
/** Counts the number of bytes written. */ |
||||
public class CountingOutputStream extends OutputStream { |
||||
private final OutputStream out; |
||||
private long cnt; |
||||
|
||||
/** |
||||
* Initialize a new counting stream. |
||||
* |
||||
* @param out |
||||
* stream to output all writes to. |
||||
*/ |
||||
public CountingOutputStream(OutputStream out) { |
||||
this.out = out; |
||||
} |
||||
|
||||
/** @return current number of bytes written. */ |
||||
public long getCount() { |
||||
return cnt; |
||||
} |
||||
|
||||
@Override |
||||
public void write(int val) throws IOException { |
||||
out.write(val); |
||||
cnt++; |
||||
} |
||||
|
||||
@Override |
||||
public void write(byte[] buf, int off, int len) throws IOException { |
||||
out.write(buf, off, len); |
||||
cnt += len; |
||||
} |
||||
|
||||
@Override |
||||
public void flush() throws IOException { |
||||
out.flush(); |
||||
} |
||||
|
||||
@Override |
||||
public void close() throws IOException { |
||||
out.close(); |
||||
} |
||||
} |
Loading…
Reference in new issue