LooseObjects.java

  1. /*
  2.  * Copyright (C) 2009, Google Inc. and others
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */

  10. package org.eclipse.jgit.internal.storage.file;

  11. import java.io.File;
  12. import java.io.FileInputStream;
  13. import java.io.FileNotFoundException;
  14. import java.io.IOException;
  15. import java.nio.file.Files;
  16. import java.nio.file.NoSuchFileException;
  17. import java.nio.file.StandardCopyOption;
  18. import java.util.Set;

  19. import org.eclipse.jgit.internal.storage.file.FileObjectDatabase.InsertLooseObjectResult;
  20. import org.eclipse.jgit.lib.AbbreviatedObjectId;
  21. import org.eclipse.jgit.lib.AnyObjectId;
  22. import org.eclipse.jgit.lib.Constants;
  23. import org.eclipse.jgit.lib.ObjectId;
  24. import org.eclipse.jgit.lib.ObjectLoader;
  25. import org.eclipse.jgit.util.FileUtils;
  26. import org.slf4j.Logger;
  27. import org.slf4j.LoggerFactory;

  28. /**
  29.  * Traditional file system based loose objects handler.
  30.  * <p>
  31.  * This is the loose object representation for a Git object database, where
  32.  * objects are stored loose by hashing them into directories by their
  33.  * {@link org.eclipse.jgit.lib.ObjectId}.
  34.  */
  35. class LooseObjects {
  36.     private static final Logger LOG = LoggerFactory
  37.             .getLogger(LooseObjects.class);

  38.     private final File directory;

  39.     private final UnpackedObjectCache unpackedObjectCache;

  40.     /**
  41.      * Initialize a reference to an on-disk object directory.
  42.      *
  43.      * @param dir
  44.      *            the location of the <code>objects</code> directory.
  45.      */
  46.     LooseObjects(File dir) {
  47.         directory = dir;
  48.         unpackedObjectCache = new UnpackedObjectCache();
  49.     }

  50.     /**
  51.      * Getter for the field <code>directory</code>.
  52.      *
  53.      * @return the location of the <code>objects</code> directory.
  54.      */
  55.     File getDirectory() {
  56.         return directory;
  57.     }

  58.     void create() throws IOException {
  59.         FileUtils.mkdirs(directory);
  60.     }

  61.     void close() {
  62.         unpackedObjectCache.clear();
  63.     }

  64.     /** {@inheritDoc} */
  65.     @Override
  66.     public String toString() {
  67.         return "LooseObjects[" + directory + "]"; //$NON-NLS-1$ //$NON-NLS-2$
  68.     }

  69.     boolean hasCached(AnyObjectId id) {
  70.         return unpackedObjectCache.isUnpacked(id);
  71.     }

  72.     /**
  73.      * Does the requested object exist as a loose object?
  74.      *
  75.      * @param objectId
  76.      *            identity of the object to test for existence of.
  77.      * @return {@code true} if the specified object is stored as a loose object.
  78.      */
  79.     boolean has(AnyObjectId objectId) {
  80.         return fileFor(objectId).exists();
  81.     }

  82.     /**
  83.      * Find objects matching the prefix abbreviation.
  84.      *
  85.      * @param matches
  86.      *            set to add any located ObjectIds to. This is an output
  87.      *            parameter.
  88.      * @param id
  89.      *            prefix to search for.
  90.      * @param matchLimit
  91.      *            maximum number of results to return. At most this many
  92.      *            ObjectIds should be added to matches before returning.
  93.      * @return {@code true} if the matches were exhausted before reaching
  94.      *         {@code maxLimit}.
  95.      */
  96.     boolean resolve(Set<ObjectId> matches, AbbreviatedObjectId id,
  97.             int matchLimit) {
  98.         String fanOut = id.name().substring(0, 2);
  99.         String[] entries = new File(directory, fanOut).list();
  100.         if (entries != null) {
  101.             for (String e : entries) {
  102.                 if (e.length() != Constants.OBJECT_ID_STRING_LENGTH - 2) {
  103.                     continue;
  104.                 }
  105.                 try {
  106.                     ObjectId entId = ObjectId.fromString(fanOut + e);
  107.                     if (id.prefixCompare(entId) == 0) {
  108.                         matches.add(entId);
  109.                     }
  110.                 } catch (IllegalArgumentException notId) {
  111.                     continue;
  112.                 }
  113.                 if (matches.size() > matchLimit) {
  114.                     return false;
  115.                 }
  116.             }
  117.         }
  118.         return true;
  119.     }

  120.     ObjectLoader open(WindowCursor curs, AnyObjectId id) throws IOException {
  121.         File path = fileFor(id);
  122.         try (FileInputStream in = new FileInputStream(path)) {
  123.             unpackedObjectCache.add(id);
  124.             return UnpackedObject.open(in, path, id, curs);
  125.         } catch (FileNotFoundException noFile) {
  126.             if (path.exists()) {
  127.                 throw noFile;
  128.             }
  129.             unpackedObjectCache.remove(id);
  130.             return null;
  131.         }
  132.     }

  133.     long getSize(WindowCursor curs, AnyObjectId id) throws IOException {
  134.         File f = fileFor(id);
  135.         try (FileInputStream in = new FileInputStream(f)) {
  136.             unpackedObjectCache.add(id);
  137.             return UnpackedObject.getSize(in, id, curs);
  138.         } catch (FileNotFoundException noFile) {
  139.             if (f.exists()) {
  140.                 throw noFile;
  141.             }
  142.             unpackedObjectCache.remove(id);
  143.             return -1;
  144.         }
  145.     }

  146.     InsertLooseObjectResult insert(File tmp, ObjectId id) throws IOException {
  147.         final File dst = fileFor(id);
  148.         if (dst.exists()) {
  149.             // We want to be extra careful and avoid replacing an object
  150.             // that already exists. We can't be sure renameTo() would
  151.             // fail on all platforms if dst exists, so we check first.
  152.             //
  153.             FileUtils.delete(tmp, FileUtils.RETRY);
  154.             return InsertLooseObjectResult.EXISTS_LOOSE;
  155.         }

  156.         try {
  157.             return tryMove(tmp, dst, id);
  158.         } catch (NoSuchFileException e) {
  159.             // It's possible the directory doesn't exist yet as the object
  160.             // directories are always lazily created. Note that we try the
  161.             // rename/move first as the directory likely does exist.
  162.             //
  163.             // Create the directory.
  164.             //
  165.             FileUtils.mkdir(dst.getParentFile(), true);
  166.         } catch (IOException e) {
  167.             // Any other IO error is considered a failure.
  168.             //
  169.             LOG.error(e.getMessage(), e);
  170.             FileUtils.delete(tmp, FileUtils.RETRY);
  171.             return InsertLooseObjectResult.FAILURE;
  172.         }

  173.         try {
  174.             return tryMove(tmp, dst, id);
  175.         } catch (IOException e) {
  176.             // The object failed to be renamed into its proper location and
  177.             // it doesn't exist in the repository either. We really don't
  178.             // know what went wrong, so fail.
  179.             //
  180.             LOG.error(e.getMessage(), e);
  181.             FileUtils.delete(tmp, FileUtils.RETRY);
  182.             return InsertLooseObjectResult.FAILURE;
  183.         }
  184.     }

  185.     private InsertLooseObjectResult tryMove(File tmp, File dst, ObjectId id)
  186.             throws IOException {
  187.         Files.move(FileUtils.toPath(tmp), FileUtils.toPath(dst),
  188.                 StandardCopyOption.ATOMIC_MOVE);
  189.         dst.setReadOnly();
  190.         unpackedObjectCache.add(id);
  191.         return InsertLooseObjectResult.INSERTED;
  192.     }

  193.     /**
  194.      * Compute the location of a loose object file.
  195.      *
  196.      * @param objectId
  197.      *            identity of the object to get the File location for.
  198.      * @return {@link java.io.File} location of the specified loose object.
  199.      */
  200.     File fileFor(AnyObjectId objectId) {
  201.         String n = objectId.name();
  202.         String d = n.substring(0, 2);
  203.         String f = n.substring(2);
  204.         return new File(new File(getDirectory(), d), f);
  205.     }
  206. }