Not every object storage system will have the concept of alternate object databases to search, and even if they do, they may not have the notion of fast-access / slow-access split like we do within the ObjectDirectory code for pack files and loose objects. Push all of that down below the generic API so that it is a hidden detail of the ObjectDirectory and its related supporting classes. Change-Id: I54bc1ca5ff2ac94dfffad1f9a9dad7af202b9523 Signed-off-by: Shawn O. Pearce <spearce@spearce.org>tags/v0.9.1
@@ -1,147 +0,0 @@ | |||
/* | |||
* Copyright (C) 2010, Constantine Plotnikov <constantine.plotnikov@gmail.com> | |||
* Copyright (C) 2009, 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.lib; | |||
import java.io.IOException; | |||
import java.util.Collection; | |||
/** | |||
* An ObjectDatabase of another {@link Repository}. | |||
* <p> | |||
* This {@code ObjectDatabase} wraps around another {@code Repository}'s object | |||
* database, providing its contents to the caller, and closing the Repository | |||
* when this database is closed. The primary user of this class is | |||
* {@link ObjectDirectory}, when the {@code info/alternates} file points at the | |||
* {@code objects/} directory of another repository. | |||
*/ | |||
public final class AlternateRepositoryDatabase extends ObjectDatabase { | |||
private final Repository repository; | |||
private final ObjectDatabase odb; | |||
/** | |||
* @param alt | |||
* the alternate repository to wrap and export. | |||
*/ | |||
public AlternateRepositoryDatabase(final Repository alt) { | |||
repository = alt; | |||
odb = repository.getObjectDatabase(); | |||
} | |||
/** @return the alternate repository objects are borrowed from. */ | |||
public Repository getRepository() { | |||
return repository; | |||
} | |||
@Override | |||
public void closeSelf() { | |||
repository.close(); | |||
} | |||
@Override | |||
public void create() throws IOException { | |||
repository.create(); | |||
} | |||
@Override | |||
public ObjectInserter newInserter() { | |||
return odb.newInserter(); | |||
} | |||
@Override | |||
public boolean exists() { | |||
return odb.exists(); | |||
} | |||
@Override | |||
protected boolean hasObject1(final AnyObjectId objectId) { | |||
return odb.hasObject1(objectId); | |||
} | |||
@Override | |||
protected boolean tryAgain1() { | |||
return odb.tryAgain1(); | |||
} | |||
@Override | |||
protected boolean hasObject2(final String objectName) { | |||
return odb.hasObject2(objectName); | |||
} | |||
@Override | |||
protected ObjectLoader openObject1(final WindowCursor curs, | |||
final AnyObjectId objectId) throws IOException { | |||
return odb.openObject1(curs, objectId); | |||
} | |||
@Override | |||
protected ObjectLoader openObject2(final WindowCursor curs, | |||
final String objectName, final AnyObjectId objectId) | |||
throws IOException { | |||
return odb.openObject2(curs, objectName, objectId); | |||
} | |||
@Override | |||
void openObjectInAllPacks1(final Collection<PackedObjectLoader> out, | |||
final WindowCursor curs, final AnyObjectId objectId) | |||
throws IOException { | |||
odb.openObjectInAllPacks1(out, curs, objectId); | |||
} | |||
@Override | |||
protected ObjectDatabase[] loadAlternates() throws IOException { | |||
return odb.getAlternates(); | |||
} | |||
@Override | |||
protected void closeAlternates(final ObjectDatabase[] alt) { | |||
// Do nothing; these belong to odb to close, not us. | |||
} | |||
@Override | |||
public ObjectDatabase newCachedDatabase() { | |||
return odb.newCachedDatabase(); | |||
} | |||
} |
@@ -1,137 +0,0 @@ | |||
/* | |||
* Copyright (C) 2010, Constantine Plotnikov <constantine.plotnikov@gmail.com> | |||
* Copyright (C) 2010, JetBrains s.r.o. | |||
* 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.lib; | |||
import java.io.IOException; | |||
import java.util.Collection; | |||
/** | |||
* {@link ObjectDatabase} wrapper providing temporary lookup caching. | |||
* <p> | |||
* The base class for {@code ObjectDatabase}s that wrap other database instances | |||
* and optimize querying for objects by caching some database dependent | |||
* information. Instances of this class (or any of its subclasses) can be | |||
* returned from the method {@link ObjectDatabase#newCachedDatabase()}. This | |||
* class can be used in scenarios where the database does not change, or when | |||
* changes in the database while some operation is in progress is an acceptable | |||
* risk. | |||
* <p> | |||
* The default implementation delegates all requests to the wrapped database. | |||
* The instance might be indirectly invalidated if the wrapped instance is | |||
* closed. Closing the delegating instance does not implies closing the wrapped | |||
* instance. For alternative databases, cached instances are used as well. | |||
*/ | |||
public class CachedObjectDatabase extends ObjectDatabase { | |||
/** | |||
* The wrapped database instance | |||
*/ | |||
protected final ObjectDatabase wrapped; | |||
/** | |||
* Create the delegating database instance | |||
* | |||
* @param wrapped | |||
* the wrapped object database | |||
*/ | |||
public CachedObjectDatabase(ObjectDatabase wrapped) { | |||
this.wrapped = wrapped; | |||
} | |||
@Override | |||
protected boolean hasObject1(AnyObjectId objectId) { | |||
return wrapped.hasObject1(objectId); | |||
} | |||
@Override | |||
protected ObjectLoader openObject1(WindowCursor curs, AnyObjectId objectId) | |||
throws IOException { | |||
return wrapped.openObject1(curs, objectId); | |||
} | |||
@Override | |||
protected boolean hasObject2(String objectName) { | |||
return wrapped.hasObject2(objectName); | |||
} | |||
@Override | |||
protected ObjectDatabase[] loadAlternates() throws IOException { | |||
ObjectDatabase[] loaded = wrapped.getAlternates(); | |||
ObjectDatabase[] result = new ObjectDatabase[loaded.length]; | |||
for (int i = 0; i < loaded.length; i++) { | |||
result[i] = loaded[i].newCachedDatabase(); | |||
} | |||
return result; | |||
} | |||
@Override | |||
protected ObjectLoader openObject2(WindowCursor curs, String objectName, | |||
AnyObjectId objectId) throws IOException { | |||
return wrapped.openObject2(curs, objectName, objectId); | |||
} | |||
@Override | |||
void openObjectInAllPacks1(Collection<PackedObjectLoader> out, | |||
WindowCursor curs, AnyObjectId objectId) throws IOException { | |||
wrapped.openObjectInAllPacks1(out, curs, objectId); | |||
} | |||
@Override | |||
protected boolean tryAgain1() { | |||
return wrapped.tryAgain1(); | |||
} | |||
@Override | |||
public ObjectDatabase newCachedDatabase() { | |||
// Note that "this" is not returned since subclasses might actually do something, | |||
// on closeSelf() (for example closing database connections or open repositories). | |||
// The situation might become even more tricky if we will consider alternates. | |||
return wrapped.newCachedDatabase(); | |||
} | |||
@Override | |||
public ObjectInserter newInserter() { | |||
return wrapped.newInserter(); | |||
} | |||
} |
@@ -46,6 +46,7 @@ package org.eclipse.jgit.lib; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.util.Collection; | |||
/** | |||
* The cached instance of an {@link ObjectDirectory}. | |||
@@ -53,21 +54,26 @@ import java.io.IOException; | |||
* This class caches the list of loose objects in memory, so the file system is | |||
* not queried with stat calls. | |||
*/ | |||
public class CachedObjectDirectory extends CachedObjectDatabase { | |||
class CachedObjectDirectory extends FileObjectDatabase { | |||
/** | |||
* The set that contains unpacked objects identifiers, it is created when | |||
* the cached instance is created. | |||
*/ | |||
private final ObjectIdSubclassMap<ObjectId> unpackedObjects = new ObjectIdSubclassMap<ObjectId>(); | |||
private final ObjectDirectory wrapped; | |||
private AlternateHandle[] alts; | |||
/** | |||
* The constructor | |||
* | |||
* @param wrapped | |||
* the wrapped database | |||
*/ | |||
public CachedObjectDirectory(ObjectDirectory wrapped) { | |||
super(wrapped); | |||
CachedObjectDirectory(ObjectDirectory wrapped) { | |||
this.wrapped = wrapped; | |||
File objects = wrapped.getDirectory(); | |||
String[] fanout = objects.list(); | |||
if (fanout == null) | |||
@@ -91,22 +97,89 @@ public class CachedObjectDirectory extends CachedObjectDatabase { | |||
} | |||
@Override | |||
protected ObjectLoader openObject2(WindowCursor curs, String objectName, | |||
AnyObjectId objectId) throws IOException { | |||
if (unpackedObjects.get(objectId) == null) | |||
return null; | |||
return super.openObject2(curs, objectName, objectId); | |||
public void close() { | |||
// Don't close anything. | |||
} | |||
@Override | |||
public ObjectInserter newInserter() { | |||
return wrapped.newInserter(); | |||
} | |||
@Override | |||
public ObjectDatabase newCachedDatabase() { | |||
return this; | |||
} | |||
@Override | |||
FileObjectDatabase newCachedFileObjectDatabase() { | |||
return this; | |||
} | |||
@Override | |||
void openObjectInAllPacks(Collection<PackedObjectLoader> out, | |||
WindowCursor curs, AnyObjectId objectId) throws IOException { | |||
wrapped.openObjectInAllPacks(out, curs, objectId); | |||
} | |||
@Override | |||
File getDirectory() { | |||
return wrapped.getDirectory(); | |||
} | |||
@Override | |||
AlternateHandle[] myAlternates() { | |||
if (alts == null) { | |||
AlternateHandle[] src = wrapped.myAlternates(); | |||
alts = new AlternateHandle[src.length]; | |||
for (int i = 0; i < alts.length; i++) { | |||
FileObjectDatabase s = src[i].db; | |||
alts[i] = new AlternateHandle(s.newCachedFileObjectDatabase()); | |||
} | |||
} | |||
return alts; | |||
} | |||
@Override | |||
boolean tryAgain1() { | |||
return wrapped.tryAgain1(); | |||
} | |||
@Override | |||
public boolean hasObject(final AnyObjectId objectId) { | |||
return hasObjectImpl1(objectId); | |||
} | |||
@Override | |||
protected boolean hasObject1(AnyObjectId objectId) { | |||
if (unpackedObjects.get(objectId) != null) | |||
return true; // known to be loose | |||
return super.hasObject1(objectId); | |||
boolean hasObject1(AnyObjectId objectId) { | |||
return unpackedObjects.contains(objectId) | |||
|| wrapped.hasObject1(objectId); | |||
} | |||
@Override | |||
protected boolean hasObject2(String name) { | |||
return false; // loose objects were tested by hasObject1 | |||
public ObjectLoader openObject(final WindowCursor curs, | |||
final AnyObjectId objectId) throws IOException { | |||
return openObjectImpl1(curs, objectId); | |||
} | |||
@Override | |||
ObjectLoader openObject1(WindowCursor curs, AnyObjectId objectId) | |||
throws IOException { | |||
if (unpackedObjects.contains(objectId)) | |||
return wrapped.openObject2(curs, objectId.name(), objectId); | |||
return wrapped.openObject1(curs, objectId); | |||
} | |||
@Override | |||
boolean hasObject2(String objectId) { | |||
// This method should never be invoked. | |||
throw new UnsupportedOperationException(); | |||
} | |||
@Override | |||
ObjectLoader openObject2(WindowCursor curs, String objectName, | |||
AnyObjectId objectId) throws IOException { | |||
// This method should never be invoked. | |||
throw new UnsupportedOperationException(); | |||
} | |||
} |
@@ -0,0 +1,199 @@ | |||
/* | |||
* 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.lib; | |||
import java.io.File; | |||
import java.io.IOException; | |||
abstract class FileObjectDatabase extends ObjectDatabase { | |||
/** | |||
* Does the requested object exist in this database? | |||
* <p> | |||
* Alternates (if present) are searched automatically. | |||
* | |||
* @param objectId | |||
* identity of the object to test for existence of. | |||
* @return true if the specified object is stored in this database, or any | |||
* of the alternate databases. | |||
*/ | |||
public boolean hasObject(final AnyObjectId objectId) { | |||
return hasObjectImpl1(objectId) || hasObjectImpl2(objectId.name()); | |||
} | |||
final boolean hasObjectImpl1(final AnyObjectId objectId) { | |||
if (hasObject1(objectId)) | |||
return true; | |||
for (final AlternateHandle alt : myAlternates()) { | |||
if (alt.db.hasObjectImpl1(objectId)) | |||
return true; | |||
} | |||
return tryAgain1() && hasObject1(objectId); | |||
} | |||
final boolean hasObjectImpl2(final String objectId) { | |||
if (hasObject2(objectId)) | |||
return true; | |||
for (final AlternateHandle alt : myAlternates()) { | |||
if (alt.db.hasObjectImpl2(objectId)) | |||
return true; | |||
} | |||
return false; | |||
} | |||
/** | |||
* Open an object from this database. | |||
* <p> | |||
* Alternates (if present) are searched automatically. | |||
* | |||
* @param curs | |||
* temporary working space associated with the calling thread. | |||
* @param objectId | |||
* identity of the object to open. | |||
* @return a {@link ObjectLoader} for accessing the data of the named | |||
* object, or null if the object does not exist. | |||
* @throws IOException | |||
*/ | |||
public ObjectLoader openObject(final WindowCursor curs, | |||
final AnyObjectId objectId) throws IOException { | |||
ObjectLoader ldr; | |||
ldr = openObjectImpl1(curs, objectId); | |||
if (ldr != null) | |||
return ldr; | |||
ldr = openObjectImpl2(curs, objectId.name(), objectId); | |||
if (ldr != null) | |||
return ldr; | |||
return null; | |||
} | |||
final ObjectLoader openObjectImpl1(final WindowCursor curs, | |||
final AnyObjectId objectId) throws IOException { | |||
ObjectLoader ldr; | |||
ldr = openObject1(curs, objectId); | |||
if (ldr != null) | |||
return ldr; | |||
for (final AlternateHandle alt : myAlternates()) { | |||
ldr = alt.db.openObjectImpl1(curs, objectId); | |||
if (ldr != null) | |||
return ldr; | |||
} | |||
if (tryAgain1()) { | |||
ldr = openObject1(curs, objectId); | |||
if (ldr != null) | |||
return ldr; | |||
} | |||
return null; | |||
} | |||
final ObjectLoader openObjectImpl2(final WindowCursor curs, | |||
final String objectName, final AnyObjectId objectId) | |||
throws IOException { | |||
ObjectLoader ldr; | |||
ldr = openObject2(curs, objectName, objectId); | |||
if (ldr != null) | |||
return ldr; | |||
for (final AlternateHandle alt : myAlternates()) { | |||
ldr = alt.db.openObjectImpl2(curs, objectName, objectId); | |||
if (ldr != null) | |||
return ldr; | |||
} | |||
return null; | |||
} | |||
abstract File getDirectory(); | |||
abstract AlternateHandle[] myAlternates(); | |||
abstract boolean tryAgain1(); | |||
abstract boolean hasObject1(AnyObjectId objectId); | |||
abstract boolean hasObject2(String objectId); | |||
abstract ObjectLoader openObject1(WindowCursor curs, AnyObjectId objectId) | |||
throws IOException; | |||
abstract ObjectLoader openObject2(WindowCursor curs, String objectName, | |||
AnyObjectId objectId) throws IOException; | |||
abstract FileObjectDatabase newCachedFileObjectDatabase(); | |||
static class AlternateHandle { | |||
final FileObjectDatabase db; | |||
AlternateHandle(FileObjectDatabase db) { | |||
this.db = db; | |||
} | |||
void close() { | |||
db.close(); | |||
} | |||
} | |||
static class AlternateRepository extends AlternateHandle { | |||
final FileRepository repository; | |||
AlternateRepository(FileRepository r) { | |||
super(r.getObjectDatabase()); | |||
repository = r; | |||
} | |||
void close() { | |||
repository.close(); | |||
} | |||
} | |||
} |
@@ -56,6 +56,8 @@ import java.util.Set; | |||
import org.eclipse.jgit.JGitText; | |||
import org.eclipse.jgit.errors.ConfigInvalidException; | |||
import org.eclipse.jgit.lib.FileObjectDatabase.AlternateHandle; | |||
import org.eclipse.jgit.lib.FileObjectDatabase.AlternateRepository; | |||
import org.eclipse.jgit.util.FS; | |||
import org.eclipse.jgit.util.SystemReader; | |||
@@ -443,11 +445,11 @@ public class FileRepository extends Repository { | |||
*/ | |||
public Set<ObjectId> getAdditionalHaves() { | |||
HashSet<ObjectId> r = new HashSet<ObjectId>(); | |||
for (ObjectDatabase d : getObjectDatabase().getAlternates()) { | |||
if (d instanceof AlternateRepositoryDatabase) { | |||
for (AlternateHandle d : objectDatabase. myAlternates()) { | |||
if (d instanceof AlternateRepository) { | |||
Repository repo; | |||
repo = ((AlternateRepositoryDatabase) d).getRepository(); | |||
repo = ((AlternateRepository) d).repository; | |||
for (Ref ref : repo.getAllRefs().values()) | |||
r.add(ref.getObjectId()); | |||
r.addAll(repo.getAdditionalHaves()); |
@@ -45,30 +45,17 @@ package org.eclipse.jgit.lib; | |||
import java.io.IOException; | |||
import java.util.Collection; | |||
import java.util.concurrent.atomic.AtomicReference; | |||
/** | |||
* Abstraction of arbitrary object storage. | |||
* <p> | |||
* An object database stores one or more Git objects, indexed by their unique | |||
* {@link ObjectId}. Optionally an object database can reference one or more | |||
* alternates; other ObjectDatabase instances that are searched in addition to | |||
* the current database. | |||
* <p> | |||
* Databases are usually divided into two halves: a half that is considered to | |||
* be fast to search, and a half that is considered to be slow to search. When | |||
* alternates are present the fast half is fully searched (recursively through | |||
* all alternates) before the slow half is considered. | |||
* {@link ObjectId}. | |||
*/ | |||
public abstract class ObjectDatabase { | |||
/** Constant indicating no alternate databases exist. */ | |||
protected static final ObjectDatabase[] NO_ALTERNATES = {}; | |||
private final AtomicReference<ObjectDatabase[]> alternates; | |||
/** Initialize a new database instance for access. */ | |||
protected ObjectDatabase() { | |||
alternates = new AtomicReference<ObjectDatabase[]>(); | |||
// Protected to force extension. | |||
} | |||
/** | |||
@@ -103,163 +90,21 @@ public abstract class ObjectDatabase { | |||
public abstract ObjectInserter newInserter(); | |||
/** | |||
* Close any resources held by this database and its active alternates. | |||
* Close any resources held by this database. | |||
*/ | |||
public final void close() { | |||
closeSelf(); | |||
closeAlternates(); | |||
} | |||
/** | |||
* Close any resources held by this database only; ignoring alternates. | |||
* <p> | |||
* To fully close this database and its referenced alternates, the caller | |||
* should instead invoke {@link #close()}. | |||
*/ | |||
public void closeSelf() { | |||
// Assume no action is required. | |||
} | |||
/** Fully close all loaded alternates and clear the alternate list. */ | |||
public final void closeAlternates() { | |||
ObjectDatabase[] alt = alternates.get(); | |||
if (alt != null) { | |||
alternates.set(null); | |||
closeAlternates(alt); | |||
} | |||
} | |||
public abstract void close(); | |||
/** | |||
* Does the requested object exist in this database? | |||
* <p> | |||
* Alternates (if present) are searched automatically. | |||
* | |||
* @param objectId | |||
* identity of the object to test for existence of. | |||
* @return true if the specified object is stored in this database, or any | |||
* of the alternate databases. | |||
*/ | |||
public final boolean hasObject(final AnyObjectId objectId) { | |||
return hasObjectImpl1(objectId) || hasObjectImpl2(objectId.name()); | |||
} | |||
private final boolean hasObjectImpl1(final AnyObjectId objectId) { | |||
if (hasObject1(objectId)) { | |||
return true; | |||
} | |||
for (final ObjectDatabase alt : getAlternates()) { | |||
if (alt.hasObjectImpl1(objectId)) { | |||
return true; | |||
} | |||
} | |||
return tryAgain1() && hasObject1(objectId); | |||
} | |||
private final boolean hasObjectImpl2(final String objectId) { | |||
if (hasObject2(objectId)) { | |||
return true; | |||
} | |||
for (final ObjectDatabase alt : getAlternates()) { | |||
if (alt.hasObjectImpl2(objectId)) { | |||
return true; | |||
} | |||
} | |||
return false; | |||
} | |||
/** | |||
* Fast half of {@link #hasObject(AnyObjectId)}. | |||
* | |||
* @param objectId | |||
* identity of the object to test for existence of. | |||
* @return true if the specified object is stored in this database. | |||
*/ | |||
protected abstract boolean hasObject1(AnyObjectId objectId); | |||
/** | |||
* Slow half of {@link #hasObject(AnyObjectId)}. | |||
* | |||
* @param objectName | |||
* identity of the object to test for existence of. | |||
* @return true if the specified object is stored in this database. | |||
*/ | |||
protected boolean hasObject2(String objectName) { | |||
// Assume the search took place during hasObject1. | |||
return false; | |||
} | |||
public abstract boolean hasObject(AnyObjectId objectId); | |||
/** | |||
* Open an object from this database. | |||
* <p> | |||
* Alternates (if present) are searched automatically. | |||
* | |||
* @param curs | |||
* temporary working space associated with the calling thread. | |||
* @param objectId | |||
* identity of the object to open. | |||
* @return a {@link ObjectLoader} for accessing the data of the named | |||
* object, or null if the object does not exist. | |||
* @throws IOException | |||
*/ | |||
public final ObjectLoader openObject(final WindowCursor curs, | |||
final AnyObjectId objectId) throws IOException { | |||
ObjectLoader ldr; | |||
ldr = openObjectImpl1(curs, objectId); | |||
if (ldr != null) { | |||
return ldr; | |||
} | |||
ldr = openObjectImpl2(curs, objectId.name(), objectId); | |||
if (ldr != null) { | |||
return ldr; | |||
} | |||
return null; | |||
} | |||
private ObjectLoader openObjectImpl1(final WindowCursor curs, | |||
final AnyObjectId objectId) throws IOException { | |||
ObjectLoader ldr; | |||
ldr = openObject1(curs, objectId); | |||
if (ldr != null) { | |||
return ldr; | |||
} | |||
for (final ObjectDatabase alt : getAlternates()) { | |||
ldr = alt.openObjectImpl1(curs, objectId); | |||
if (ldr != null) { | |||
return ldr; | |||
} | |||
} | |||
if (tryAgain1()) { | |||
ldr = openObject1(curs, objectId); | |||
if (ldr != null) { | |||
return ldr; | |||
} | |||
} | |||
return null; | |||
} | |||
private ObjectLoader openObjectImpl2(final WindowCursor curs, | |||
final String objectName, final AnyObjectId objectId) | |||
throws IOException { | |||
ObjectLoader ldr; | |||
ldr = openObject2(curs, objectName, objectId); | |||
if (ldr != null) { | |||
return ldr; | |||
} | |||
for (final ObjectDatabase alt : getAlternates()) { | |||
ldr = alt.openObjectImpl2(curs, objectName, objectId); | |||
if (ldr != null) { | |||
return ldr; | |||
} | |||
} | |||
return null; | |||
} | |||
/** | |||
* Fast half of {@link #openObject(WindowCursor, AnyObjectId)}. | |||
* | |||
* @param curs | |||
* temporary working space associated with the calling thread. | |||
@@ -269,32 +114,11 @@ public abstract class ObjectDatabase { | |||
* object, or null if the object does not exist. | |||
* @throws IOException | |||
*/ | |||
protected abstract ObjectLoader openObject1(WindowCursor curs, | |||
public abstract ObjectLoader openObject(WindowCursor curs, | |||
AnyObjectId objectId) throws IOException; | |||
/** | |||
* Slow half of {@link #openObject(WindowCursor, AnyObjectId)}. | |||
* | |||
* @param curs | |||
* temporary working space associated with the calling thread. | |||
* @param objectName | |||
* name of the object to open. | |||
* @param objectId | |||
* identity of the object to open. | |||
* @return a {@link ObjectLoader} for accessing the data of the named | |||
* object, or null if the object does not exist. | |||
* @throws IOException | |||
*/ | |||
protected ObjectLoader openObject2(WindowCursor curs, String objectName, | |||
AnyObjectId objectId) throws IOException { | |||
// Assume the search took place during openObject1. | |||
return null; | |||
} | |||
/** | |||
* Open the object from all packs containing it. | |||
* <p> | |||
* If any alternates are present, their packs are also considered. | |||
* | |||
* @param out | |||
* result collection of loaders for this object, filled with | |||
@@ -305,92 +129,9 @@ public abstract class ObjectDatabase { | |||
* id of object to search for | |||
* @throws IOException | |||
*/ | |||
final void openObjectInAllPacks(final Collection<PackedObjectLoader> out, | |||
abstract void openObjectInAllPacks(final Collection<PackedObjectLoader> out, | |||
final WindowCursor curs, final AnyObjectId objectId) | |||
throws IOException { | |||
openObjectInAllPacks1(out, curs, objectId); | |||
for (final ObjectDatabase alt : getAlternates()) { | |||
alt.openObjectInAllPacks1(out, curs, objectId); | |||
} | |||
} | |||
/** | |||
* Open the object from all packs containing it. | |||
* | |||
* @param out | |||
* result collection of loaders for this object, filled with | |||
* loaders from all packs containing specified object | |||
* @param curs | |||
* temporary working space associated with the calling thread. | |||
* @param objectId | |||
* id of object to search for | |||
* @throws IOException | |||
*/ | |||
void openObjectInAllPacks1(Collection<PackedObjectLoader> out, | |||
WindowCursor curs, AnyObjectId objectId) throws IOException { | |||
// Assume no pack support | |||
} | |||
/** | |||
* @return true if the fast-half search should be tried again. | |||
*/ | |||
protected boolean tryAgain1() { | |||
return false; | |||
} | |||
/** | |||
* Get the alternate databases known to this database. | |||
* | |||
* @return the alternate list. Never null, but may be an empty array. | |||
*/ | |||
public final ObjectDatabase[] getAlternates() { | |||
ObjectDatabase[] r = alternates.get(); | |||
if (r == null) { | |||
synchronized (alternates) { | |||
r = alternates.get(); | |||
if (r == null) { | |||
try { | |||
r = loadAlternates(); | |||
} catch (IOException e) { | |||
r = NO_ALTERNATES; | |||
} | |||
alternates.set(r); | |||
} | |||
} | |||
} | |||
return r; | |||
} | |||
/** | |||
* Load the list of alternate databases into memory. | |||
* <p> | |||
* This method is invoked by {@link #getAlternates()} if the alternate list | |||
* has not yet been populated, or if {@link #closeAlternates()} has been | |||
* called on this instance and the alternate list is needed again. | |||
* <p> | |||
* If the alternate array is empty, implementors should consider using the | |||
* constant {@link #NO_ALTERNATES}. | |||
* | |||
* @return the alternate list for this database. | |||
* @throws IOException | |||
* the alternate list could not be accessed. The empty alternate | |||
* array {@link #NO_ALTERNATES} will be assumed by the caller. | |||
*/ | |||
protected ObjectDatabase[] loadAlternates() throws IOException { | |||
return NO_ALTERNATES; | |||
} | |||
/** | |||
* Close the list of alternates returned by {@link #loadAlternates()}. | |||
* | |||
* @param alt | |||
* the alternate list, from {@link #loadAlternates()}. | |||
*/ | |||
protected void closeAlternates(ObjectDatabase[] alt) { | |||
for (final ObjectDatabase d : alt) { | |||
d.close(); | |||
} | |||
} | |||
throws IOException; | |||
/** | |||
* Create a new cached database instance over this database. This instance might | |||
@@ -398,9 +139,8 @@ public abstract class ObjectDatabase { | |||
* done after instance creation might fail to be noticed. | |||
* | |||
* @return new cached database instance | |||
* @see CachedObjectDatabase | |||
*/ | |||
public ObjectDatabase newCachedDatabase() { | |||
return new CachedObjectDatabase(this); | |||
return this; | |||
} | |||
} |
@@ -72,8 +72,18 @@ import org.eclipse.jgit.util.FS; | |||
* where objects are stored loose by hashing them into directories by their | |||
* {@link ObjectId}, or are stored in compressed containers known as | |||
* {@link PackFile}s. | |||
* <p> | |||
* Optionally an object database can reference one or more alternates; other | |||
* ObjectDatabase instances that are searched in addition to the current | |||
* database. | |||
* <p> | |||
* Databases are divided into two halves: a half that is considered to be fast | |||
* to search (the {@code PackFile}s), and a half that is considered to be slow | |||
* to search (loose objects). When alternates are present the fast half is fully | |||
* searched (recursively through all alternates) before the slow half is | |||
* considered. | |||
*/ | |||
public class ObjectDirectory extends ObjectDatabase { | |||
public class ObjectDirectory extends FileObjectDatabase { | |||
private static final PackList NO_PACKS = new PackList(-1, -1, new PackFile[0]); | |||
private final Config config; | |||
@@ -88,10 +98,10 @@ public class ObjectDirectory extends ObjectDatabase { | |||
private final AtomicReference<PackList> packList; | |||
private final File[] alternateObjectDir; | |||
private final FS fs; | |||
private final AtomicReference<AlternateHandle[]> alternates; | |||
/** | |||
* Initialize a reference to an on-disk object directory. | |||
* | |||
@@ -99,21 +109,33 @@ public class ObjectDirectory extends ObjectDatabase { | |||
* configuration this directory consults for write settings. | |||
* @param dir | |||
* the location of the <code>objects</code> directory. | |||
* @param alternateObjectDir | |||
* @param alternatePaths | |||
* a list of alternate object directories | |||
* @param fs | |||
* the file system abstraction which will be necessary to | |||
* perform certain file system operations. | |||
* the file system abstraction which will be necessary to perform | |||
* certain file system operations. | |||
* @throws IOException | |||
* an alternate object cannot be opened. | |||
*/ | |||
public ObjectDirectory(final Config cfg, final File dir, File[] alternateObjectDir, FS fs) { | |||
public ObjectDirectory(final Config cfg, final File dir, | |||
File[] alternatePaths, FS fs) throws IOException { | |||
config = cfg; | |||
objects = dir; | |||
this.alternateObjectDir = alternateObjectDir; | |||
infoDirectory = new File(objects, "info"); | |||
packDirectory = new File(objects, "pack"); | |||
alternatesFile = new File(infoDirectory, "alternates"); | |||
packList = new AtomicReference<PackList>(NO_PACKS); | |||
this.fs = fs; | |||
alternates = new AtomicReference<AlternateHandle[]>(); | |||
if (alternatePaths != null) { | |||
AlternateHandle[] alt; | |||
alt = new AlternateHandle[alternatePaths.length]; | |||
for (int i = 0; i < alternatePaths.length; i++) | |||
alt[i] = openAlternate(alternatePaths[i]); | |||
alternates.set(alt); | |||
} | |||
} | |||
/** | |||
@@ -141,11 +163,19 @@ public class ObjectDirectory extends ObjectDatabase { | |||
} | |||
@Override | |||
public void closeSelf() { | |||
public void close() { | |||
final PackList packs = packList.get(); | |||
packList.set(NO_PACKS); | |||
for (final PackFile p : packs.packs) | |||
p.close(); | |||
// Fully close all loaded alternates and clear the alternate list. | |||
AlternateHandle[] alt = alternates.get(); | |||
if (alt != null) { | |||
alternates.set(null); | |||
for(final AlternateHandle od : alt) | |||
od.close(); | |||
} | |||
} | |||
/** | |||
@@ -209,8 +239,7 @@ public class ObjectDirectory extends ObjectDatabase { | |||
return "ObjectDirectory[" + getDirectory() + "]"; | |||
} | |||
@Override | |||
protected boolean hasObject1(final AnyObjectId objectId) { | |||
boolean hasObject1(final AnyObjectId objectId) { | |||
for (final PackFile p : packList.get().packs) { | |||
try { | |||
if (p.hasObject(objectId)) { | |||
@@ -228,8 +257,7 @@ public class ObjectDirectory extends ObjectDatabase { | |||
return false; | |||
} | |||
@Override | |||
protected ObjectLoader openObject1(final WindowCursor curs, | |||
ObjectLoader openObject1(final WindowCursor curs, | |||
final AnyObjectId objectId) throws IOException { | |||
PackList pList = packList.get(); | |||
SEARCH: for (;;) { | |||
@@ -256,7 +284,7 @@ public class ObjectDirectory extends ObjectDatabase { | |||
} | |||
@Override | |||
void openObjectInAllPacks1(final Collection<PackedObjectLoader> out, | |||
void openObjectInAllPacks(final Collection<PackedObjectLoader> out, | |||
final WindowCursor curs, final AnyObjectId objectId) | |||
throws IOException { | |||
PackList pList = packList.get(); | |||
@@ -282,13 +310,11 @@ public class ObjectDirectory extends ObjectDatabase { | |||
} | |||
} | |||
@Override | |||
protected boolean hasObject2(final String objectName) { | |||
boolean hasObject2(final String objectName) { | |||
return fileFor(objectName).exists(); | |||
} | |||
@Override | |||
protected ObjectLoader openObject2(final WindowCursor curs, | |||
ObjectLoader openObject2(final WindowCursor curs, | |||
final String objectName, final AnyObjectId objectId) | |||
throws IOException { | |||
try { | |||
@@ -298,8 +324,7 @@ public class ObjectDirectory extends ObjectDatabase { | |||
} | |||
} | |||
@Override | |||
protected boolean tryAgain1() { | |||
boolean tryAgain1() { | |||
final PackList old = packList.get(); | |||
if (old.tryAgain(packDirectory.lastModified())) | |||
return old != scanPacks(old); | |||
@@ -469,29 +494,36 @@ public class ObjectDirectory extends ObjectDatabase { | |||
return nameSet; | |||
} | |||
@Override | |||
protected ObjectDatabase[] loadAlternates() throws IOException { | |||
final List<ObjectDatabase> l = new ArrayList<ObjectDatabase>(4); | |||
if (alternateObjectDir != null) { | |||
for (File d : alternateObjectDir) { | |||
l.add(openAlternate(d)); | |||
} | |||
} else { | |||
final BufferedReader br = open(alternatesFile); | |||
try { | |||
String line; | |||
while ((line = br.readLine()) != null) { | |||
l.add(openAlternate(line)); | |||
AlternateHandle[] myAlternates() { | |||
AlternateHandle[] alt = alternates.get(); | |||
if (alt == null) { | |||
synchronized (alternates) { | |||
alt = alternates.get(); | |||
if (alt == null) { | |||
try { | |||
alt = loadAlternates(); | |||
} catch (IOException e) { | |||
alt = new AlternateHandle[0]; | |||
} | |||
alternates.set(alt); | |||
} | |||
} finally { | |||
br.close(); | |||
} | |||
} | |||
return alt; | |||
} | |||
if (l.isEmpty()) { | |||
return NO_ALTERNATES; | |||
private AlternateHandle[] loadAlternates() throws IOException { | |||
final List<AlternateHandle> l = new ArrayList<AlternateHandle>(4); | |||
final BufferedReader br = open(alternatesFile); | |||
try { | |||
String line; | |||
while ((line = br.readLine()) != null) { | |||
l.add(openAlternate(line)); | |||
} | |||
} finally { | |||
br.close(); | |||
} | |||
return l.toArray(new ObjectDatabase[l.size()]); | |||
return l.toArray(new AlternateHandle[l.size()]); | |||
} | |||
private static BufferedReader open(final File f) | |||
@@ -499,19 +531,22 @@ public class ObjectDirectory extends ObjectDatabase { | |||
return new BufferedReader(new FileReader(f)); | |||
} | |||
private ObjectDatabase openAlternate(final String location) | |||
private AlternateHandle openAlternate(final String location) | |||
throws IOException { | |||
final File objdir = fs.resolve(objects, location); | |||
return openAlternate(objdir); | |||
} | |||
private ObjectDatabase openAlternate(File objdir) throws IOException { | |||
private AlternateHandle openAlternate(File objdir) throws IOException { | |||
final File parent = objdir.getParentFile(); | |||
if (FileKey.isGitRepository(parent, fs)) { | |||
final Repository db = RepositoryCache.open(FileKey.exact(parent, fs)); | |||
return new AlternateRepositoryDatabase(db); | |||
FileKey key = FileKey.exact(parent, fs); | |||
FileRepository db = (FileRepository) RepositoryCache.open(key); | |||
return new AlternateRepository(db); | |||
} | |||
return new ObjectDirectory(config, objdir, null, fs); | |||
ObjectDirectory db = new ObjectDirectory(config, objdir, null, fs); | |||
return new AlternateHandle(db); | |||
} | |||
private static final class PackList { | |||
@@ -576,6 +611,10 @@ public class ObjectDirectory extends ObjectDatabase { | |||
@Override | |||
public ObjectDatabase newCachedDatabase() { | |||
return newCachedFileObjectDatabase(); | |||
} | |||
FileObjectDatabase newCachedFileObjectDatabase() { | |||
return new CachedObjectDirectory(this); | |||
} | |||
} |