PackDirectory.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 static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX;
  12. import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
  13. import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;

  14. import java.io.File;
  15. import java.io.FileNotFoundException;
  16. import java.io.IOException;
  17. import java.text.MessageFormat;
  18. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.Collection;
  21. import java.util.Collections;
  22. import java.util.EnumMap;
  23. import java.util.HashMap;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.Set;
  27. import java.util.concurrent.atomic.AtomicReference;

  28. import org.eclipse.jgit.annotations.Nullable;
  29. import org.eclipse.jgit.errors.CorruptObjectException;
  30. import org.eclipse.jgit.errors.PackInvalidException;
  31. import org.eclipse.jgit.errors.PackMismatchException;
  32. import org.eclipse.jgit.errors.SearchForReuseTimeout;
  33. import org.eclipse.jgit.internal.JGitText;
  34. import org.eclipse.jgit.internal.storage.pack.ObjectToPack;
  35. import org.eclipse.jgit.internal.storage.pack.PackExt;
  36. import org.eclipse.jgit.internal.storage.pack.PackWriter;
  37. import org.eclipse.jgit.lib.AbbreviatedObjectId;
  38. import org.eclipse.jgit.lib.AnyObjectId;
  39. import org.eclipse.jgit.lib.Config;
  40. import org.eclipse.jgit.lib.ConfigConstants;
  41. import org.eclipse.jgit.lib.ObjectId;
  42. import org.eclipse.jgit.lib.ObjectLoader;
  43. import org.eclipse.jgit.util.FileUtils;
  44. import org.slf4j.Logger;
  45. import org.slf4j.LoggerFactory;

  46. /**
  47.  * Traditional file system packed objects directory handler.
  48.  * <p>
  49.  * This is the {@link org.eclipse.jgit.internal.storage.file.Pack}s object
  50.  * representation for a Git object database, where objects are stored in
  51.  * compressed containers known as
  52.  * {@link org.eclipse.jgit.internal.storage.file.Pack}s.
  53.  */
  54. class PackDirectory {
  55.     private final static Logger LOG = LoggerFactory
  56.             .getLogger(PackDirectory.class);

  57.     private static final PackList NO_PACKS = new PackList(FileSnapshot.DIRTY,
  58.             new Pack[0]);

  59.     private final Config config;

  60.     private final File directory;

  61.     private final AtomicReference<PackList> packList;

  62.     /**
  63.      * Initialize a reference to an on-disk 'pack' directory.
  64.      *
  65.      * @param config
  66.      *            configuration this directory consults for write settings.
  67.      * @param directory
  68.      *            the location of the {@code pack} directory.
  69.      */
  70.     PackDirectory(Config config, File directory) {
  71.         this.config = config;
  72.         this.directory = directory;
  73.         packList = new AtomicReference<>(NO_PACKS);
  74.     }

  75.     /**
  76.      * Getter for the field {@code directory}.
  77.      *
  78.      * @return the location of the {@code pack} directory.
  79.      */
  80.     File getDirectory() {
  81.         return directory;
  82.     }

  83.     void create() throws IOException {
  84.         FileUtils.mkdir(directory);
  85.     }

  86.     void close() {
  87.         PackList packs = packList.get();
  88.         if (packs != NO_PACKS && packList.compareAndSet(packs, NO_PACKS)) {
  89.             for (Pack p : packs.packs) {
  90.                 p.close();
  91.             }
  92.         }
  93.     }

  94.     Collection<Pack> getPacks() {
  95.         PackList list = packList.get();
  96.         if (list == NO_PACKS) {
  97.             list = scanPacks(list);
  98.         }
  99.         Pack[] packs = list.packs;
  100.         return Collections.unmodifiableCollection(Arrays.asList(packs));
  101.     }

  102.     /** {@inheritDoc} */
  103.     @Override
  104.     public String toString() {
  105.         return "PackDirectory[" + getDirectory() + "]"; //$NON-NLS-1$ //$NON-NLS-2$
  106.     }

  107.     /**
  108.      * Does the requested object exist in this PackDirectory?
  109.      *
  110.      * @param objectId
  111.      *            identity of the object to test for existence of.
  112.      * @return {@code true} if the specified object is stored in this PackDirectory.
  113.      */
  114.     boolean has(AnyObjectId objectId) {
  115.         return getPack(objectId) != null;
  116.     }

  117.     /**
  118.      * Get the {@link org.eclipse.jgit.internal.storage.file.Pack} for the
  119.      * specified object if it is stored in this PackDirectory.
  120.      *
  121.      * @param objectId
  122.      *            identity of the object to find the Pack for.
  123.      * @return {@link org.eclipse.jgit.internal.storage.file.Pack} which
  124.      *         contains the specified object or {@code null} if it is not stored
  125.      *         in this PackDirectory.
  126.      */
  127.     @Nullable
  128.     Pack getPack(AnyObjectId objectId) {
  129.         PackList pList;
  130.         do {
  131.             pList = packList.get();
  132.             for (Pack p : pList.packs) {
  133.                 try {
  134.                     if (p.hasObject(objectId)) {
  135.                         return p;
  136.                     }
  137.                 } catch (IOException e) {
  138.                     // The hasObject call should have only touched the index, so
  139.                     // any failure here indicates the index is unreadable by
  140.                     // this process, and the pack is likewise not readable.
  141.                     LOG.warn(MessageFormat.format(
  142.                             JGitText.get().unableToReadPackfile,
  143.                             p.getPackFile().getAbsolutePath()), e);
  144.                     remove(p);
  145.                 }
  146.             }
  147.         } while (searchPacksAgain(pList));
  148.         return null;
  149.     }

  150.     /**
  151.      * Find objects matching the prefix abbreviation.
  152.      *
  153.      * @param matches
  154.      *            set to add any located ObjectIds to. This is an output
  155.      *            parameter.
  156.      * @param id
  157.      *            prefix to search for.
  158.      * @param matchLimit
  159.      *            maximum number of results to return. At most this many
  160.      *            ObjectIds should be added to matches before returning.
  161.      * @return {@code true} if the matches were exhausted before reaching
  162.      *         {@code maxLimit}.
  163.      */
  164.     boolean resolve(Set<ObjectId> matches, AbbreviatedObjectId id,
  165.             int matchLimit) {
  166.         // Go through the packs once. If we didn't find any resolutions
  167.         // scan for new packs and check once more.
  168.         int oldSize = matches.size();
  169.         PackList pList;
  170.         do {
  171.             pList = packList.get();
  172.             for (Pack p : pList.packs) {
  173.                 try {
  174.                     p.resolve(matches, id, matchLimit);
  175.                     p.resetTransientErrorCount();
  176.                 } catch (IOException e) {
  177.                     handlePackError(e, p);
  178.                 }
  179.                 if (matches.size() > matchLimit) {
  180.                     return false;
  181.                 }
  182.             }
  183.         } while (matches.size() == oldSize && searchPacksAgain(pList));
  184.         return true;
  185.     }

  186.     ObjectLoader open(WindowCursor curs, AnyObjectId objectId) {
  187.         PackList pList;
  188.         do {
  189.             SEARCH: for (;;) {
  190.                 pList = packList.get();
  191.                 for (Pack p : pList.packs) {
  192.                     try {
  193.                         ObjectLoader ldr = p.get(curs, objectId);
  194.                         p.resetTransientErrorCount();
  195.                         if (ldr != null)
  196.                             return ldr;
  197.                     } catch (PackMismatchException e) {
  198.                         // Pack was modified; refresh the entire pack list.
  199.                         if (searchPacksAgain(pList)) {
  200.                             continue SEARCH;
  201.                         }
  202.                     } catch (IOException e) {
  203.                         handlePackError(e, p);
  204.                     }
  205.                 }
  206.                 break SEARCH;
  207.             }
  208.         } while (searchPacksAgain(pList));
  209.         return null;
  210.     }

  211.     long getSize(WindowCursor curs, AnyObjectId id) {
  212.         PackList pList;
  213.         do {
  214.             SEARCH: for (;;) {
  215.                 pList = packList.get();
  216.                 for (Pack p : pList.packs) {
  217.                     try {
  218.                         long len = p.getObjectSize(curs, id);
  219.                         p.resetTransientErrorCount();
  220.                         if (0 <= len) {
  221.                             return len;
  222.                         }
  223.                     } catch (PackMismatchException e) {
  224.                         // Pack was modified; refresh the entire pack list.
  225.                         if (searchPacksAgain(pList)) {
  226.                             continue SEARCH;
  227.                         }
  228.                     } catch (IOException e) {
  229.                         handlePackError(e, p);
  230.                     }
  231.                 }
  232.                 break SEARCH;
  233.             }
  234.         } while (searchPacksAgain(pList));
  235.         return -1;
  236.     }

  237.     void selectRepresentation(PackWriter packer, ObjectToPack otp,
  238.             WindowCursor curs) {
  239.         PackList pList = packList.get();
  240.         SEARCH: for (;;) {
  241.             for (Pack p : pList.packs) {
  242.                 try {
  243.                     LocalObjectRepresentation rep = p.representation(curs, otp);
  244.                     p.resetTransientErrorCount();
  245.                     if (rep != null) {
  246.                         packer.select(otp, rep);
  247.                         packer.checkSearchForReuseTimeout();
  248.                     }
  249.                 } catch (SearchForReuseTimeout e) {
  250.                     break SEARCH;
  251.                 } catch (PackMismatchException e) {
  252.                     // Pack was modified; refresh the entire pack list.
  253.                     //
  254.                     pList = scanPacks(pList);
  255.                     continue SEARCH;
  256.                 } catch (IOException e) {
  257.                     handlePackError(e, p);
  258.                 }
  259.             }
  260.             break SEARCH;
  261.         }
  262.     }

  263.     private void handlePackError(IOException e, Pack p) {
  264.         String warnTmpl = null;
  265.         int transientErrorCount = 0;
  266.         String errTmpl = JGitText.get().exceptionWhileReadingPack;
  267.         if ((e instanceof CorruptObjectException)
  268.                 || (e instanceof PackInvalidException)) {
  269.             warnTmpl = JGitText.get().corruptPack;
  270.             LOG.warn(MessageFormat.format(warnTmpl,
  271.                     p.getPackFile().getAbsolutePath()), e);
  272.             // Assume the pack is corrupted, and remove it from the list.
  273.             remove(p);
  274.         } else if (e instanceof FileNotFoundException) {
  275.             if (p.getPackFile().exists()) {
  276.                 errTmpl = JGitText.get().packInaccessible;
  277.                 transientErrorCount = p.incrementTransientErrorCount();
  278.             } else {
  279.                 warnTmpl = JGitText.get().packWasDeleted;
  280.                 remove(p);
  281.             }
  282.         } else if (FileUtils.isStaleFileHandleInCausalChain(e)) {
  283.             warnTmpl = JGitText.get().packHandleIsStale;
  284.             remove(p);
  285.         } else {
  286.             transientErrorCount = p.incrementTransientErrorCount();
  287.         }
  288.         if (warnTmpl != null) {
  289.             LOG.warn(MessageFormat.format(warnTmpl,
  290.                     p.getPackFile().getAbsolutePath()), e);
  291.         } else {
  292.             if (doLogExponentialBackoff(transientErrorCount)) {
  293.                 // Don't remove the pack from the list, as the error may be
  294.                 // transient.
  295.                 LOG.error(MessageFormat.format(errTmpl,
  296.                         p.getPackFile().getAbsolutePath(),
  297.                         Integer.valueOf(transientErrorCount)), e);
  298.             }
  299.         }
  300.     }

  301.     /**
  302.      * @param n
  303.      *            count of consecutive failures
  304.      * @return @{code true} if i is a power of 2
  305.      */
  306.     private boolean doLogExponentialBackoff(int n) {
  307.         return (n & (n - 1)) == 0;
  308.     }

  309.     boolean searchPacksAgain(PackList old) {
  310.         // Whether to trust the pack folder's modification time. If set
  311.         // to false we will always scan the .git/objects/pack folder to
  312.         // check for new pack files. If set to true (default) we use the
  313.         // lastmodified attribute of the folder and assume that no new
  314.         // pack files can be in this folder if his modification time has
  315.         // not changed.
  316.         boolean trustFolderStat = config.getBoolean(
  317.                 ConfigConstants.CONFIG_CORE_SECTION,
  318.                 ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);

  319.         return ((!trustFolderStat) || old.snapshot.isModified(directory))
  320.                 && old != scanPacks(old);
  321.     }

  322.     void insert(Pack pack) {
  323.         PackList o, n;
  324.         do {
  325.             o = packList.get();

  326.             // If the pack in question is already present in the list
  327.             // (picked up by a concurrent thread that did a scan?) we
  328.             // do not want to insert it a second time.
  329.             //
  330.             final Pack[] oldList = o.packs;
  331.             final String name = pack.getPackFile().getName();
  332.             for (Pack p : oldList) {
  333.                 if (name.equals(p.getPackFile().getName())) {
  334.                     return;
  335.                 }
  336.             }

  337.             final Pack[] newList = new Pack[1 + oldList.length];
  338.             newList[0] = pack;
  339.             System.arraycopy(oldList, 0, newList, 1, oldList.length);
  340.             n = new PackList(o.snapshot, newList);
  341.         } while (!packList.compareAndSet(o, n));
  342.     }

  343.     private void remove(Pack deadPack) {
  344.         PackList o, n;
  345.         do {
  346.             o = packList.get();

  347.             final Pack[] oldList = o.packs;
  348.             final int j = indexOf(oldList, deadPack);
  349.             if (j < 0) {
  350.                 break;
  351.             }

  352.             final Pack[] newList = new Pack[oldList.length - 1];
  353.             System.arraycopy(oldList, 0, newList, 0, j);
  354.             System.arraycopy(oldList, j + 1, newList, j, newList.length - j);
  355.             n = new PackList(o.snapshot, newList);
  356.         } while (!packList.compareAndSet(o, n));
  357.         deadPack.close();
  358.     }

  359.     private static int indexOf(Pack[] list, Pack pack) {
  360.         for (int i = 0; i < list.length; i++) {
  361.             if (list[i] == pack) {
  362.                 return i;
  363.             }
  364.         }
  365.         return -1;
  366.     }

  367.     private PackList scanPacks(PackList original) {
  368.         synchronized (packList) {
  369.             PackList o, n;
  370.             do {
  371.                 o = packList.get();
  372.                 if (o != original) {
  373.                     // Another thread did the scan for us, while we
  374.                     // were blocked on the monitor above.
  375.                     //
  376.                     return o;
  377.                 }
  378.                 n = scanPacksImpl(o);
  379.                 if (n == o) {
  380.                     return n;
  381.                 }
  382.             } while (!packList.compareAndSet(o, n));
  383.             return n;
  384.         }
  385.     }

  386.     private PackList scanPacksImpl(PackList old) {
  387.         final Map<String, Pack> forReuse = reuseMap(old);
  388.         final FileSnapshot snapshot = FileSnapshot.save(directory);
  389.         Map<String, Map<PackExt, PackFile>> packFilesByExtById = getPackFilesByExtById();
  390.         List<Pack> list = new ArrayList<>(packFilesByExtById.size());
  391.         boolean foundNew = false;
  392.         for (Map<PackExt, PackFile> packFilesByExt : packFilesByExtById
  393.                 .values()) {
  394.             PackFile packFile = packFilesByExt.get(PACK);
  395.             if (packFile == null || !packFilesByExt.containsKey(INDEX)) {
  396.                 // Sometimes C Git's HTTP fetch transport leaves a
  397.                 // .idx file behind and does not download the .pack.
  398.                 // We have to skip over such useless indexes.
  399.                 // Also skip if we don't have any index for this id
  400.                 continue;
  401.             }

  402.             Pack oldPack = forReuse.get(packFile.getName());
  403.             if (oldPack != null
  404.                     && !oldPack.getFileSnapshot().isModified(packFile)) {
  405.                 forReuse.remove(packFile.getName());
  406.                 list.add(oldPack);
  407.                 continue;
  408.             }

  409.             list.add(new Pack(packFile, packFilesByExt.get(BITMAP_INDEX)));
  410.             foundNew = true;
  411.         }

  412.         // If we did not discover any new files, the modification time was not
  413.         // changed, and we did not remove any files, then the set of files is
  414.         // the same as the set we were given. Instead of building a new object
  415.         // return the same collection.
  416.         //
  417.         if (!foundNew && forReuse.isEmpty() && snapshot.equals(old.snapshot)) {
  418.             old.snapshot.setClean(snapshot);
  419.             return old;
  420.         }

  421.         for (Pack p : forReuse.values()) {
  422.             p.close();
  423.         }

  424.         if (list.isEmpty()) {
  425.             return new PackList(snapshot, NO_PACKS.packs);
  426.         }

  427.         final Pack[] r = list.toArray(new Pack[0]);
  428.         Arrays.sort(r, Pack.SORT);
  429.         return new PackList(snapshot, r);
  430.     }

  431.     private static Map<String, Pack> reuseMap(PackList old) {
  432.         final Map<String, Pack> forReuse = new HashMap<>();
  433.         for (Pack p : old.packs) {
  434.             if (p.invalid()) {
  435.                 // The pack instance is corrupted, and cannot be safely used
  436.                 // again. Do not include it in our reuse map.
  437.                 //
  438.                 p.close();
  439.                 continue;
  440.             }

  441.             final Pack prior = forReuse.put(p.getPackFile().getName(), p);
  442.             if (prior != null) {
  443.                 // This should never occur. It should be impossible for us
  444.                 // to have two pack files with the same name, as all of them
  445.                 // came out of the same directory. If it does, we promised to
  446.                 // close any PackFiles we did not reuse, so close the second,
  447.                 // readers are likely to be actively using the first.
  448.                 //
  449.                 forReuse.put(prior.getPackFile().getName(), prior);
  450.                 p.close();
  451.             }
  452.         }
  453.         return forReuse;
  454.     }

  455.     /**
  456.      * Scans the pack directory for
  457.      * {@link org.eclipse.jgit.internal.storage.file.PackFile}s and returns them
  458.      * organized by their extensions and their pack ids
  459.      *
  460.      * Skips files in the directory that we cannot create a
  461.      * {@link org.eclipse.jgit.internal.storage.file.PackFile} for.
  462.      *
  463.      * @return a map of {@link org.eclipse.jgit.internal.storage.file.PackFile}s
  464.      *         and {@link org.eclipse.jgit.internal.storage.pack.PackExt}s keyed
  465.      *         by pack ids
  466.      */
  467.     private Map<String, Map<PackExt, PackFile>> getPackFilesByExtById() {
  468.         final String[] nameList = directory.list();
  469.         if (nameList == null) {
  470.             return Collections.emptyMap();
  471.         }
  472.         Map<String, Map<PackExt, PackFile>> packFilesByExtById = new HashMap<>(
  473.                 nameList.length / 2); // assume roughly 2 files per id
  474.         for (String name : nameList) {
  475.             try {
  476.                 PackFile pack = new PackFile(directory, name);
  477.                 if (pack.getPackExt() != null) {
  478.                     Map<PackExt, PackFile> packByExt = packFilesByExtById
  479.                             .get(pack.getId());
  480.                     if (packByExt == null) {
  481.                         packByExt = new EnumMap<>(PackExt.class);
  482.                         packFilesByExtById.put(pack.getId(), packByExt);
  483.                     }
  484.                     packByExt.put(pack.getPackExt(), pack);
  485.                 }
  486.             } catch (IllegalArgumentException e) {
  487.                 continue;
  488.             }
  489.         }
  490.         return packFilesByExtById;
  491.     }

  492.     static final class PackList {
  493.         /** State just before reading the pack directory. */
  494.         final FileSnapshot snapshot;

  495.         /** All known packs, sorted by {@link Pack#SORT}. */
  496.         final Pack[] packs;

  497.         PackList(FileSnapshot monitor, Pack[] packs) {
  498.             this.snapshot = monitor;
  499.             this.packs = packs;
  500.         }
  501.     }
  502. }