Browse Source

Refactor alternate object databases below ObjectDirectory

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
Shawn O. Pearce 14 years ago
parent
commit
133c987f4d

+ 0
- 147
org.eclipse.jgit/src/org/eclipse/jgit/lib/AlternateRepositoryDatabase.java View File

@@ -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();
}
}

+ 0
- 137
org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDatabase.java View File

@@ -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();
}
}

+ 87
- 14
org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDirectory.java View File

@@ -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();
}
}

+ 199
- 0
org.eclipse.jgit/src/org/eclipse/jgit/lib/FileObjectDatabase.java View File

@@ -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();
}
}
}

+ 5
- 3
org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java View File

@@ -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());

+ 9
- 269
org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java View File

@@ -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;
}
}

+ 82
- 43
org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java View File

@@ -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);
}
}

Loading…
Cancel
Save