Split out loose object handling from ObjectDirectory

The ObjectDirectory class manages the interactions for the entire object
database, this includes loose objects, packfiles, alternates, and
shallow commits. To help reduce the complexity of this class, abstract
some of the loose object specific details into a class which understands
just this, leaving the ObjectDirectory to focus more on the interactions
between the different mechanisms.

Change-Id: I39f3a74d6308f042a2a2baa57769f4acde5ba5e0
Signed-off-by: Martin Fick <mfick@codeaurora.org>
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
This commit is contained in:
Martin Fick 2018-04-26 10:53:57 -06:00 committed by Matthias Sohn
parent 415788df28
commit ecad2dc0a6
2 changed files with 245 additions and 120 deletions

View File

@ -0,0 +1,227 @@
/*
* Copyright (C) 2009, Google Inc. and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
* https://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.internal.storage.file;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.StandardCopyOption;
import java.util.Set;
import org.eclipse.jgit.internal.storage.file.FileObjectDatabase.InsertLooseObjectResult;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.util.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Traditional file system based loose objects handler.
* <p>
* This is the loose object representation for a Git object database,
* where objects are stored loose by hashing them into directories by their
* {@link org.eclipse.jgit.lib.ObjectId}.
*/
class LooseObjects {
private static final Logger LOG = LoggerFactory
.getLogger(LooseObjects.class);
private final File directory;
private final UnpackedObjectCache unpackedObjectCache;
/**
* Initialize a reference to an on-disk object directory.
*
* @param dir
* the location of the <code>objects</code> directory.
*/
LooseObjects(File dir) {
directory = dir;
unpackedObjectCache = new UnpackedObjectCache();
}
/**
* <p>Getter for the field <code>directory</code>.</p>
*
* @return the location of the <code>objects</code> directory.
*/
File getDirectory() {
return directory;
}
void create() throws IOException {
FileUtils.mkdirs(directory);
}
void close() {
unpackedObjectCache.clear();
}
/** {@inheritDoc} */
@Override
public String toString() {
return "LooseObjects[" + directory + "]"; //$NON-NLS-1$ //$NON-NLS-2$
}
boolean hasCached(AnyObjectId id) {
return unpackedObjectCache.isUnpacked(id);
}
/**
* Does the requested object exist as a loose object?
*
* @param objectId
* identity of the object to test for existence of.
* @return {@code true} if the specified object is stored as a loose object.
*/
boolean has(AnyObjectId objectId) {
return fileFor(objectId).exists();
}
/**
* Find objects matching the prefix abbreviation.
*
* @param matches
* set to add any located ObjectIds to. This is an output
* parameter.
* @param id
* prefix to search for.
* @param matchLimit
* maximum number of results to return. At most this many
* ObjectIds should be added to matches before returning.
* @return {@code true} if the matches were exhausted before reaching
* {@code maxLimit}.
*/
boolean resolve(Set<ObjectId> matches, AbbreviatedObjectId id,
int matchLimit) {
String fanOut = id.name().substring(0, 2);
String[] entries = new File(directory, fanOut).list();
if (entries != null) {
for (String e : entries) {
if (e.length() != Constants.OBJECT_ID_STRING_LENGTH - 2)
continue;
try {
ObjectId entId = ObjectId.fromString(fanOut + e);
if (id.prefixCompare(entId) == 0)
matches.add(entId);
} catch (IllegalArgumentException notId) {
continue;
}
if (matches.size() > matchLimit)
return false;
}
}
return true;
}
ObjectLoader open(WindowCursor curs, AnyObjectId id)
throws IOException {
File path = fileFor(id);
try (FileInputStream in = new FileInputStream(path)) {
unpackedObjectCache.add(id);
return UnpackedObject.open(in, path, id, curs);
} catch (FileNotFoundException noFile) {
if (path.exists()) {
throw noFile;
}
unpackedObjectCache.remove(id);
return null;
}
}
long getSize(WindowCursor curs, AnyObjectId id)
throws IOException {
File f = fileFor(id);
try (FileInputStream in = new FileInputStream(f)) {
unpackedObjectCache.add(id);
return UnpackedObject.getSize(in, id, curs);
} catch (FileNotFoundException noFile) {
if (f.exists()) {
throw noFile;
}
unpackedObjectCache.remove(id);
return -1;
}
}
InsertLooseObjectResult insert(File tmp, ObjectId id) throws IOException {
final File dst = fileFor(id);
if (dst.exists()) {
// We want to be extra careful and avoid replacing an object
// that already exists. We can't be sure renameTo() would
// fail on all platforms if dst exists, so we check first.
//
FileUtils.delete(tmp, FileUtils.RETRY);
return InsertLooseObjectResult.EXISTS_LOOSE;
}
try {
return tryMove(tmp, dst, id);
} catch (NoSuchFileException e) {
// It's possible the directory doesn't exist yet as the object
// directories are always lazily created. Note that we try the
// rename/move first as the directory likely does exist.
//
// Create the directory.
//
FileUtils.mkdir(dst.getParentFile(), true);
} catch (IOException e) {
// Any other IO error is considered a failure.
//
LOG.error(e.getMessage(), e);
FileUtils.delete(tmp, FileUtils.RETRY);
return InsertLooseObjectResult.FAILURE;
}
try {
return tryMove(tmp, dst, id);
} catch (IOException e) {
// The object failed to be renamed into its proper location and
// it doesn't exist in the repository either. We really don't
// know what went wrong, so fail.
//
LOG.error(e.getMessage(), e);
FileUtils.delete(tmp, FileUtils.RETRY);
return InsertLooseObjectResult.FAILURE;
}
}
private InsertLooseObjectResult tryMove(File tmp, File dst,
ObjectId id)
throws IOException {
Files.move(FileUtils.toPath(tmp), FileUtils.toPath(dst),
StandardCopyOption.ATOMIC_MOVE);
dst.setReadOnly();
unpackedObjectCache.add(id);
return InsertLooseObjectResult.INSERTED;
}
/**
* Compute the location of a loose object file.
*
* @param objectId
* identity of the object to get the File location for.
* @return {@link java.io.File} location of the specified loose object.
*/
File fileFor(AnyObjectId objectId) {
String n = objectId.name();
String d = n.substring(0, 2);
String f = n.substring(2);
return new File(new File(getDirectory(), d), f);
}
}

View File

@ -16,12 +16,9 @@
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.StandardCopyOption;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
@ -47,8 +44,6 @@
import org.eclipse.jgit.lib.RepositoryCache.FileKey;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Traditional file system based {@link org.eclipse.jgit.lib.ObjectDatabase}.
@ -69,9 +64,6 @@
* considered.
*/
public class ObjectDirectory extends FileObjectDatabase {
private static final Logger LOG = LoggerFactory
.getLogger(ObjectDirectory.class);
/** Maximum number of candidates offered as resolutions of abbreviation. */
private static final int RESOLVE_ABBREV_LIMIT = 256;
@ -83,6 +75,8 @@ public class ObjectDirectory extends FileObjectDatabase {
private final File infoDirectory;
private final LooseObjects loose;
private final PackDirectory packed;
private final File preservedDirectory;
@ -93,8 +87,6 @@ public class ObjectDirectory extends FileObjectDatabase {
private final AtomicReference<AlternateHandle[]> alternates;
private final UnpackedObjectCache unpackedObjectCache;
private final File shallowFile;
private FileSnapshot shallowFileSnapshot = FileSnapshot.DIRTY;
@ -127,8 +119,8 @@ public ObjectDirectory(final Config cfg, final File dir,
File packDirectory = new File(objects, "pack"); //$NON-NLS-1$
preservedDirectory = new File(packDirectory, "preserved"); //$NON-NLS-1$
alternatesFile = new File(objects, Constants.INFO_ALTERNATES);
loose = new LooseObjects(objects);
packed = new PackDirectory(config, packDirectory);
unpackedObjectCache = new UnpackedObjectCache();
this.fs = fs;
this.shallowFile = shallowFile;
@ -146,7 +138,7 @@ public ObjectDirectory(final Config cfg, final File dir,
/** {@inheritDoc} */
@Override
public final File getDirectory() {
return objects;
return loose.getDirectory();
}
/**
@ -176,7 +168,7 @@ public boolean exists() {
/** {@inheritDoc} */
@Override
public void create() throws IOException {
FileUtils.mkdirs(objects);
loose.create();
FileUtils.mkdir(infoDirectory);
packed.create();
}
@ -200,7 +192,7 @@ public PackInserter newPackInserter() {
/** {@inheritDoc} */
@Override
public void close() {
unpackedObjectCache.clear();
loose.close();
packed.close();
@ -257,7 +249,7 @@ public String toString() {
/** {@inheritDoc} */
@Override
public boolean has(AnyObjectId objectId) {
return unpackedObjectCache.isUnpacked(objectId)
return loose.hasCached(objectId)
|| hasPackedInSelfOrAlternate(objectId, null)
|| hasLooseInSelfOrAlternate(objectId, null);
}
@ -280,7 +272,7 @@ private boolean hasPackedInSelfOrAlternate(AnyObjectId objectId,
private boolean hasLooseInSelfOrAlternate(AnyObjectId objectId,
Set<AlternateHandle.Id> skips) {
if (fileFor(objectId).exists()) {
if (loose.has(objectId)) {
return true;
}
skips = addMe(skips);
@ -310,23 +302,8 @@ private void resolve(Set<ObjectId> matches, AbbreviatedObjectId id,
if (!packed.resolve(matches, id, RESOLVE_ABBREV_LIMIT))
return;
String fanOut = id.name().substring(0, 2);
String[] entries = new File(getDirectory(), fanOut).list();
if (entries != null) {
for (String e : entries) {
if (e.length() != Constants.OBJECT_ID_STRING_LENGTH - 2)
continue;
try {
ObjectId entId = ObjectId.fromString(fanOut + e);
if (id.prefixCompare(entId) == 0)
matches.add(entId);
} catch (IllegalArgumentException notId) {
continue;
}
if (matches.size() > RESOLVE_ABBREV_LIMIT)
return;
}
}
if (!loose.resolve(matches, id, RESOLVE_ABBREV_LIMIT))
return;
skips = addMe(skips);
for (AlternateHandle alt : myAlternates()) {
@ -342,7 +319,7 @@ private void resolve(Set<ObjectId> matches, AbbreviatedObjectId id,
@Override
ObjectLoader openObject(WindowCursor curs, AnyObjectId objectId)
throws IOException {
if (unpackedObjectCache.isUnpacked(objectId)) {
if (loose.hasCached(objectId)) {
ObjectLoader ldr = openLooseObject(curs, objectId);
if (ldr != null) {
return ldr;
@ -399,24 +376,14 @@ ObjectLoader openPackedObject(WindowCursor curs, AnyObjectId objectId) {
@Override
ObjectLoader openLooseObject(WindowCursor curs, AnyObjectId id)
throws IOException {
File path = fileFor(id);
try (FileInputStream in = new FileInputStream(path)) {
unpackedObjectCache.add(id);
return UnpackedObject.open(in, path, id, curs);
} catch (FileNotFoundException noFile) {
if (path.exists()) {
throw noFile;
}
unpackedObjectCache.remove(id);
return null;
}
return loose.open(curs, id);
}
@Override
long getObjectSize(WindowCursor curs, AnyObjectId id)
throws IOException {
if (unpackedObjectCache.isUnpacked(id)) {
long len = getLooseObjectSize(curs, id);
if (loose.hasCached(id)) {
long len = loose.getSize(curs, id);
if (0 <= len) {
return len;
}
@ -448,7 +415,7 @@ private long getPackedSizeFromSelfOrAlternate(WindowCursor curs,
private long getLooseSizeFromSelfOrAlternate(WindowCursor curs,
AnyObjectId id, Set<AlternateHandle.Id> skips) throws IOException {
long len = getLooseObjectSize(curs, id);
long len = loose.getSize(curs, id);
if (0 <= len) {
return len;
}
@ -464,21 +431,6 @@ private long getLooseSizeFromSelfOrAlternate(WindowCursor curs,
return -1;
}
private long getLooseObjectSize(WindowCursor curs, AnyObjectId id)
throws IOException {
File f = fileFor(id);
try (FileInputStream in = new FileInputStream(f)) {
unpackedObjectCache.add(id);
return UnpackedObject.getSize(in, id, curs);
} catch (FileNotFoundException noFile) {
if (f.exists()) {
throw noFile;
}
unpackedObjectCache.remove(id);
return -1;
}
}
@Override
void selectObjectRepresentation(PackWriter packer, ObjectToPack otp,
WindowCursor curs) throws IOException {
@ -502,7 +454,7 @@ InsertLooseObjectResult insertUnpackedObject(File tmp, ObjectId id,
boolean createDuplicate) throws IOException {
// If the object is already in the repository, remove temporary file.
//
if (unpackedObjectCache.isUnpacked(id)) {
if (loose.hasCached(id)) {
FileUtils.delete(tmp, FileUtils.RETRY);
return InsertLooseObjectResult.EXISTS_LOOSE;
}
@ -510,56 +462,7 @@ InsertLooseObjectResult insertUnpackedObject(File tmp, ObjectId id,
FileUtils.delete(tmp, FileUtils.RETRY);
return InsertLooseObjectResult.EXISTS_PACKED;
}
final File dst = fileFor(id);
if (dst.exists()) {
// We want to be extra careful and avoid replacing an object
// that already exists. We can't be sure renameTo() would
// fail on all platforms if dst exists, so we check first.
//
FileUtils.delete(tmp, FileUtils.RETRY);
return InsertLooseObjectResult.EXISTS_LOOSE;
}
try {
return tryMove(tmp, dst, id);
} catch (NoSuchFileException e) {
// It's possible the directory doesn't exist yet as the object
// directories are always lazily created. Note that we try the
// rename/move first as the directory likely does exist.
//
// Create the directory.
//
FileUtils.mkdir(dst.getParentFile(), true);
} catch (IOException e) {
// Any other IO error is considered a failure.
//
LOG.error(e.getMessage(), e);
FileUtils.delete(tmp, FileUtils.RETRY);
return InsertLooseObjectResult.FAILURE;
}
try {
return tryMove(tmp, dst, id);
} catch (IOException e) {
// The object failed to be renamed into its proper location and
// it doesn't exist in the repository either. We really don't
// know what went wrong, so fail.
//
LOG.error(e.getMessage(), e);
FileUtils.delete(tmp, FileUtils.RETRY);
return InsertLooseObjectResult.FAILURE;
}
}
private InsertLooseObjectResult tryMove(File tmp, File dst,
ObjectId id)
throws IOException {
Files.move(FileUtils.toPath(tmp), FileUtils.toPath(dst),
StandardCopyOption.ATOMIC_MOVE);
dst.setReadOnly();
unpackedObjectCache.add(id);
return InsertLooseObjectResult.INSERTED;
return loose.insert(tmp, id);
}
@Override
@ -676,16 +579,11 @@ private AlternateHandle openAlternate(File objdir) throws IOException {
}
/**
* {@inheritDoc}
* <p>
* Compute the location of a loose object file.
*/
@Override
public File fileFor(AnyObjectId objectId) {
String n = objectId.name();
String d = n.substring(0, 2);
String f = n.substring(2);
return new File(new File(getDirectory(), d), f);
return loose.fileFor(objectId);
}
static class AlternateHandle {