--- /dev/null
+/*
+ * 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);
+ }
+}
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;
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}.
* 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;
private final File infoDirectory;
+ private final LooseObjects loose;
+
private final PackDirectory packed;
private final File preservedDirectory;
private final AtomicReference<AlternateHandle[]> alternates;
- private final UnpackedObjectCache unpackedObjectCache;
-
private final File shallowFile;
private FileSnapshot shallowFileSnapshot = FileSnapshot.DIRTY;
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;
/** {@inheritDoc} */
@Override
public final File getDirectory() {
- return objects;
+ return loose.getDirectory();
}
/**
/** {@inheritDoc} */
@Override
public void create() throws IOException {
- FileUtils.mkdirs(objects);
+ loose.create();
FileUtils.mkdir(infoDirectory);
packed.create();
}
/** {@inheritDoc} */
@Override
public void close() {
- unpackedObjectCache.clear();
+ loose.close();
packed.close();
/** {@inheritDoc} */
@Override
public boolean has(AnyObjectId objectId) {
- return unpackedObjectCache.isUnpacked(objectId)
+ return loose.hasCached(objectId)
|| hasPackedInSelfOrAlternate(objectId, null)
|| hasLooseInSelfOrAlternate(objectId, null);
}
private boolean hasLooseInSelfOrAlternate(AnyObjectId objectId,
Set<AlternateHandle.Id> skips) {
- if (fileFor(objectId).exists()) {
+ if (loose.has(objectId)) {
return true;
}
skips = addMe(skips);
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()) {
@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;
@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;
}
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;
}
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 {
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;
}
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
}
/**
- * {@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 {