CachedObjectDirectory.java

  1. /*
  2.  * Copyright (C) 2010, Constantine Plotnikov <constantine.plotnikov@gmail.com>
  3.  * Copyright (C) 2010, 2022 JetBrains s.r.o. and others
  4.  *
  5.  * This program and the accompanying materials are made available under the
  6.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  7.  * https://www.eclipse.org/org/documents/edl-v10.php.
  8.  *
  9.  * SPDX-License-Identifier: BSD-3-Clause
  10.  */

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

  12. import java.io.File;
  13. import java.io.IOException;
  14. import java.util.Collection;
  15. import java.util.HashSet;
  16. import java.util.Set;

  17. import org.eclipse.jgit.internal.storage.file.ObjectDirectory.AlternateHandle;
  18. import org.eclipse.jgit.internal.storage.pack.ObjectToPack;
  19. import org.eclipse.jgit.internal.storage.pack.PackWriter;
  20. import org.eclipse.jgit.lib.AbbreviatedObjectId;
  21. import org.eclipse.jgit.lib.AnyObjectId;
  22. import org.eclipse.jgit.lib.Config;
  23. import org.eclipse.jgit.lib.Constants;
  24. import org.eclipse.jgit.lib.ObjectDatabase;
  25. import org.eclipse.jgit.lib.ObjectId;
  26. import org.eclipse.jgit.lib.ObjectIdOwnerMap;
  27. import org.eclipse.jgit.lib.ObjectLoader;
  28. import org.eclipse.jgit.util.FS;

  29. /**
  30.  * The cached instance of an {@link ObjectDirectory}.
  31.  * <p>
  32.  * This class caches the list of loose objects in memory, so the file system is
  33.  * not queried with stat calls.
  34.  */
  35. class CachedObjectDirectory extends FileObjectDatabase {
  36.     /**
  37.      * The set that contains unpacked objects identifiers, it is created when
  38.      * the cached instance is created.
  39.      */
  40.     private ObjectIdOwnerMap<UnpackedObjectId> unpackedObjects;

  41.     private final ObjectDirectory wrapped;

  42.     private CachedObjectDirectory[] alts;

  43.     /**
  44.      * The constructor
  45.      *
  46.      * @param wrapped
  47.      *            the wrapped database
  48.      */
  49.     CachedObjectDirectory(ObjectDirectory wrapped) {
  50.         this.wrapped = wrapped;
  51.         this.unpackedObjects = scanLoose();
  52.     }

  53.     private ObjectIdOwnerMap<UnpackedObjectId> scanLoose() {
  54.         ObjectIdOwnerMap<UnpackedObjectId> m = new ObjectIdOwnerMap<>();
  55.         File objects = wrapped.getDirectory();
  56.         String[] fanout = objects.list();
  57.         if (fanout == null)
  58.             return m;
  59.         for (String d : fanout) {
  60.             if (d.length() != 2)
  61.                 continue;
  62.             String[] entries = new File(objects, d).list();
  63.             if (entries == null)
  64.                 continue;
  65.             for (String e : entries) {
  66.                 if (e.length() != Constants.OBJECT_ID_STRING_LENGTH - 2)
  67.                     continue;
  68.                 try {
  69.                     ObjectId id = ObjectId.fromString(d + e);
  70.                     m.add(new UnpackedObjectId(id));
  71.                 } catch (IllegalArgumentException notAnObject) {
  72.                     // ignoring the file that does not represent loose object
  73.                 }
  74.             }
  75.         }
  76.         return m;
  77.     }

  78.     /** {@inheritDoc} */
  79.     @Override
  80.     public void close() {
  81.         // Don't close anything.
  82.     }

  83.     /** {@inheritDoc} */
  84.     @Override
  85.     public ObjectDatabase newCachedDatabase() {
  86.         return this;
  87.     }

  88.     @Override
  89.     File getDirectory() {
  90.         return wrapped.getDirectory();
  91.     }

  92.     @Override
  93.     File fileFor(AnyObjectId id) {
  94.         return wrapped.fileFor(id);
  95.     }

  96.     @Override
  97.     Config getConfig() {
  98.         return wrapped.getConfig();
  99.     }

  100.     @Override
  101.     FS getFS() {
  102.         return wrapped.getFS();
  103.     }

  104.     @Override
  105.     public Set<ObjectId> getShallowCommits() throws IOException {
  106.         return wrapped.getShallowCommits();
  107.     }

  108.     @Override
  109.     public void setShallowCommits(Set<ObjectId> shallowCommits) throws IOException {
  110.         wrapped.setShallowCommits(shallowCommits);
  111.     }

  112.     private CachedObjectDirectory[] myAlternates() {
  113.         if (alts == null) {
  114.             ObjectDirectory.AlternateHandle[] src = wrapped.myAlternates();
  115.             alts = new CachedObjectDirectory[src.length];
  116.             for (int i = 0; i < alts.length; i++)
  117.                 alts[i] = src[i].db.newCachedFileObjectDatabase();
  118.         }
  119.         return alts;
  120.     }

  121.     private Set<AlternateHandle.Id> skipMe(Set<AlternateHandle.Id> skips) {
  122.         Set<AlternateHandle.Id> withMe = new HashSet<>();
  123.         if (skips != null) {
  124.             withMe.addAll(skips);
  125.         }
  126.         withMe.add(getAlternateId());
  127.         return withMe;
  128.     }

  129.     @Override
  130.     void resolve(Set<ObjectId> matches, AbbreviatedObjectId id)
  131.             throws IOException {
  132.         wrapped.resolve(matches, id);
  133.     }

  134.     /** {@inheritDoc} */
  135.     @Override
  136.     public boolean has(AnyObjectId objectId) throws IOException {
  137.         return has(objectId, null);
  138.     }

  139.     private boolean has(AnyObjectId objectId, Set<AlternateHandle.Id> skips)
  140.             throws IOException {
  141.         if (unpackedObjects.contains(objectId)) {
  142.             return true;
  143.         }
  144.         if (wrapped.hasPackedObject(objectId)) {
  145.             return true;
  146.         }
  147.         skips = skipMe(skips);
  148.         for (CachedObjectDirectory alt : myAlternates()) {
  149.             if (!skips.contains(alt.getAlternateId())) {
  150.                 if (alt.has(objectId, skips)) {
  151.                     return true;
  152.                 }
  153.             }
  154.         }
  155.         return false;
  156.     }

  157.     @Override
  158.     ObjectLoader openObject(WindowCursor curs, AnyObjectId objectId)
  159.             throws IOException {
  160.         return openObject(curs, objectId, null);
  161.     }

  162.     private ObjectLoader openObject(final WindowCursor curs,
  163.             final AnyObjectId objectId, Set<AlternateHandle.Id> skips)
  164.             throws IOException {
  165.         ObjectLoader ldr = openLooseObject(curs, objectId);
  166.         if (ldr != null) {
  167.             return ldr;
  168.         }
  169.         ldr = wrapped.openPackedObject(curs, objectId);
  170.         if (ldr != null) {
  171.             return ldr;
  172.         }
  173.         skips = skipMe(skips);
  174.         for (CachedObjectDirectory alt : myAlternates()) {
  175.             if (!skips.contains(alt.getAlternateId())) {
  176.                 ldr = alt.openObject(curs, objectId, skips);
  177.                 if (ldr != null) {
  178.                     return ldr;
  179.                 }
  180.             }
  181.         }
  182.         return null;
  183.     }

  184.     @Override
  185.     long getObjectSize(WindowCursor curs, AnyObjectId objectId)
  186.             throws IOException {
  187.         // Object size is unlikely to be requested from contexts using
  188.         // this type. Don't bother trying to accelerate the lookup.
  189.         return wrapped.getObjectSize(curs, objectId);
  190.     }

  191.     @Override
  192.     ObjectLoader openLooseObject(WindowCursor curs, AnyObjectId id)
  193.             throws IOException {
  194.         if (unpackedObjects.contains(id)) {
  195.             ObjectLoader ldr = wrapped.openLooseObject(curs, id);
  196.             if (ldr != null)
  197.                 return ldr;
  198.             unpackedObjects = scanLoose();
  199.         }
  200.         return null;
  201.     }

  202.     @Override
  203.     InsertLooseObjectResult insertUnpackedObject(File tmp, ObjectId objectId,
  204.             boolean createDuplicate) throws IOException {
  205.         InsertLooseObjectResult result = wrapped.insertUnpackedObject(tmp,
  206.                 objectId, createDuplicate);
  207.         switch (result) {
  208.         case INSERTED:
  209.         case EXISTS_LOOSE:
  210.             unpackedObjects.addIfAbsent(new UnpackedObjectId(objectId));
  211.             break;

  212.         case EXISTS_PACKED:
  213.         case FAILURE:
  214.             break;
  215.         }
  216.         return result;
  217.     }

  218.     @Override
  219.     Pack openPack(File pack) throws IOException {
  220.         return wrapped.openPack(pack);
  221.     }

  222.     @Override
  223.     void selectObjectRepresentation(PackWriter packer, ObjectToPack otp,
  224.             WindowCursor curs) throws IOException {
  225.         wrapped.selectObjectRepresentation(packer, otp, curs);
  226.     }

  227.     @Override
  228.     Collection<Pack> getPacks() {
  229.         return wrapped.getPacks();
  230.     }

  231.     private static class UnpackedObjectId extends ObjectIdOwnerMap.Entry {
  232.         UnpackedObjectId(AnyObjectId id) {
  233.             super(id);
  234.         }
  235.     }

  236.     private AlternateHandle.Id getAlternateId() {
  237.         return wrapped.getAlternateId();
  238.     }

  239.     @Override
  240.     public long getApproximateObjectCount() {
  241.         long count = 0;
  242.         for (Pack p : getPacks()) {
  243.             try {
  244.                 count += p.getObjectCount();
  245.             } catch (IOException e) {
  246.                 return -1;
  247.             }
  248.         }
  249.         return count;
  250.     }
  251. }