blob: e7cb285c34ac724d3bb13995d1d3aa93ef2c0202 [file] [log] [blame]
/*
* 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();
}
/**
* Getter for the field <code>directory</code>.
*
* @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);
}
}