ReftableDatabase.java

  1. /*
  2.  * Copyright (C) 2017, Google LLC 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.reftable;

  11. import java.io.IOException;
  12. import java.util.ArrayList;
  13. import java.util.Collections;
  14. import java.util.HashSet;
  15. import java.util.Iterator;
  16. import java.util.List;
  17. import java.util.Set;
  18. import java.util.TreeSet;
  19. import java.util.concurrent.locks.ReentrantLock;
  20. import java.util.stream.Collectors;

  21. import org.eclipse.jgit.annotations.Nullable;
  22. import org.eclipse.jgit.lib.ObjectId;
  23. import org.eclipse.jgit.lib.Ref;
  24. import org.eclipse.jgit.lib.RefDatabase;
  25. import org.eclipse.jgit.lib.ReflogReader;
  26. import org.eclipse.jgit.transport.ReceiveCommand;

  27. /**
  28.  * Operations on {@link MergedReftable} that is common to various reftable-using
  29.  * subclasses of {@link RefDatabase}. See
  30.  * {@link org.eclipse.jgit.internal.storage.dfs.DfsReftableDatabase} for an
  31.  * example.
  32.  */
  33. public abstract class ReftableDatabase {
  34.     // Protects mergedTables.
  35.     private final ReentrantLock lock = new ReentrantLock(true);

  36.     private Reftable mergedTables;

  37.     /**
  38.      * ReftableDatabase lazily initializes its merged reftable on the first read after
  39.      * construction or clearCache() call. This function should always instantiate a new
  40.      * MergedReftable based on the list of reftables specified by the underlying storage.
  41.      *
  42.      * @return the ReftableStack for this instance
  43.      * @throws IOException
  44.      *             on I/O problems.
  45.      */
  46.     protected abstract MergedReftable openMergedReftable() throws IOException;

  47.     /**
  48.      * @return the next available logical timestamp for an additional reftable
  49.      *         in the stack.
  50.      * @throws java.io.IOException
  51.      *             on I/O problems.
  52.      */
  53.     public long nextUpdateIndex() throws IOException {
  54.         lock.lock();
  55.         try {
  56.             return reader().maxUpdateIndex() + 1;
  57.         } finally {
  58.             lock.unlock();
  59.         }
  60.     }

  61.     /**
  62.      * @return a ReflogReader for the given ref
  63.      * @param refname
  64.      *            the name of the ref.
  65.      * @throws IOException
  66.      *             on I/O problems
  67.      */
  68.     public ReflogReader getReflogReader(String refname) throws IOException {
  69.         lock.lock();
  70.         try {
  71.             return new ReftableReflogReader(lock, reader(), refname);
  72.         } finally {
  73.             lock.unlock();
  74.         }
  75.     }

  76.     /**
  77.      * @return a ReceiveCommand for the change from oldRef to newRef
  78.      * @param oldRef
  79.      *            a ref
  80.      * @param newRef
  81.      *            a ref
  82.      */
  83.     public static ReceiveCommand toCommand(Ref oldRef, Ref newRef) {
  84.         ObjectId oldId = toId(oldRef);
  85.         ObjectId newId = toId(newRef);
  86.         String name = oldRef != null ? oldRef.getName() : newRef.getName();

  87.         if (oldRef != null && oldRef.isSymbolic()) {
  88.             if (newRef != null) {
  89.                 if (newRef.isSymbolic()) {
  90.                     return ReceiveCommand.link(oldRef.getTarget().getName(),
  91.                             newRef.getTarget().getName(), name);
  92.                 }
  93.                 // This should pass in oldId for compat with
  94.                 // RefDirectoryUpdate
  95.                 return ReceiveCommand.unlink(oldRef.getTarget().getName(),
  96.                         newId, name);
  97.             }
  98.             return ReceiveCommand.unlink(oldRef.getTarget().getName(),
  99.                     ObjectId.zeroId(), name);
  100.         }

  101.         if (newRef != null && newRef.isSymbolic()) {
  102.             if (oldRef != null) {
  103.                 if (oldRef.isSymbolic()) {
  104.                     return ReceiveCommand.link(oldRef.getTarget().getName(),
  105.                             newRef.getTarget().getName(), name);
  106.                 }
  107.                 return ReceiveCommand.link(oldId,
  108.                         newRef.getTarget().getName(), name);
  109.             }
  110.             return ReceiveCommand.link(ObjectId.zeroId(),
  111.                     newRef.getTarget().getName(), name);
  112.         }

  113.         return new ReceiveCommand(oldId, newId, name);
  114.     }

  115.     private static ObjectId toId(Ref ref) {
  116.         if (ref != null) {
  117.             ObjectId id = ref.getObjectId();
  118.             if (id != null) {
  119.                 return id;
  120.             }
  121.         }
  122.         return ObjectId.zeroId();
  123.     }

  124.     /**
  125.      * @return the lock protecting underlying ReftableReaders against concurrent
  126.      *         reads.
  127.      */
  128.     public ReentrantLock getLock() {
  129.         return lock;
  130.     }

  131.     /**
  132.      * @return the merged reftable that is implemented by the stack of
  133.      *         reftables. Return value must be accessed under lock.
  134.      * @throws IOException
  135.      *             on I/O problems
  136.      */
  137.     private Reftable reader() throws IOException {
  138.         if (!lock.isLocked()) {
  139.             throw new IllegalStateException(
  140.                     "must hold lock to access merged table"); //$NON-NLS-1$
  141.         }
  142.         if (mergedTables == null) {
  143.             mergedTables = openMergedReftable();
  144.         }
  145.         return mergedTables;
  146.     }

  147.     /**
  148.      * @return whether the given refName would be illegal in a repository that
  149.      *         uses loose refs.
  150.      * @param refName
  151.      *            the name to check
  152.      * @param added
  153.      *            a sorted set of refs we pretend have been added to the
  154.      *            database.
  155.      * @param deleted
  156.      *            a set of refs we pretend have been removed from the database.
  157.      * @throws IOException
  158.      *             on I/O problems
  159.      */
  160.     public boolean isNameConflicting(String refName, TreeSet<String> added,
  161.             Set<String> deleted) throws IOException {
  162.         lock.lock();
  163.         try {
  164.             Reftable table = reader();

  165.             // Cannot be nested within an existing reference.
  166.             int lastSlash = refName.lastIndexOf('/');
  167.             while (0 < lastSlash) {
  168.                 String prefix = refName.substring(0, lastSlash);
  169.                 if (!deleted.contains(prefix)
  170.                         && (table.hasRef(prefix) || added.contains(prefix))) {
  171.                     return true;
  172.                 }
  173.                 lastSlash = refName.lastIndexOf('/', lastSlash - 1);
  174.             }

  175.             // Cannot be the container of an existing reference.
  176.             String prefix = refName + '/';
  177.             RefCursor c = table.seekRefsWithPrefix(prefix);
  178.             while (c.next()) {
  179.                 if (!deleted.contains(c.getRef().getName())) {
  180.                     return true;
  181.                 }
  182.             }

  183.             String it = added.ceiling(refName + '/');
  184.             if (it != null && it.startsWith(prefix)) {
  185.                 return true;
  186.             }
  187.             return false;
  188.         } finally {
  189.             lock.unlock();
  190.         }
  191.     }

  192.     /**
  193.      * Read a single reference.
  194.      * <p>
  195.      * This method expects an unshortened reference name and does not search
  196.      * using the standard search path.
  197.      *
  198.      * @param name
  199.      *            the unabbreviated name of the reference.
  200.      * @return the reference (if it exists); else {@code null}.
  201.      * @throws java.io.IOException
  202.      *             the reference space cannot be accessed.
  203.      */
  204.     @Nullable
  205.     public Ref exactRef(String name) throws IOException {
  206.         lock.lock();
  207.         try {
  208.             Reftable table = reader();
  209.             Ref ref = table.exactRef(name);
  210.             if (ref != null && ref.isSymbolic()) {
  211.                 return table.resolve(ref);
  212.             }
  213.             return ref;
  214.         } finally {
  215.             lock.unlock();
  216.         }
  217.     }

  218.     /**
  219.      * Returns refs whose names start with a given prefix.
  220.      *
  221.      * @param prefix
  222.      *            string that names of refs should start with; may be empty (to
  223.      *            return all refs).
  224.      * @return immutable list of refs whose names start with {@code prefix}.
  225.      * @throws java.io.IOException
  226.      *             the reference space cannot be accessed.
  227.      */
  228.     public List<Ref> getRefsByPrefix(String prefix) throws IOException {
  229.         List<Ref> all = new ArrayList<>();
  230.         lock.lock();
  231.         try {
  232.             Reftable table = reader();
  233.             try (RefCursor rc = RefDatabase.ALL.equals(prefix) ? table.allRefs()
  234.                     : table.seekRefsWithPrefix(prefix)) {
  235.                 while (rc.next()) {
  236.                     Ref ref = table.resolve(rc.getRef());
  237.                     if (ref != null && ref.getObjectId() != null) {
  238.                         all.add(ref);
  239.                     }
  240.                 }
  241.             }
  242.         } finally {
  243.             lock.unlock();
  244.         }

  245.         return Collections.unmodifiableList(all);
  246.     }

  247.     /**
  248.      * Returns refs whose names start with a given prefix excluding all refs that
  249.      * start with one of the given prefixes.
  250.      *
  251.      * @param include string that names of refs should start with; may be empty.
  252.      * @param excludes strings that names of refs can't start with; may be empty.
  253.      * @return immutable list of refs whose names start with {@code include} and
  254.      *         none of the strings in {@code exclude}.
  255.      * @throws java.io.IOException the reference space cannot be accessed.
  256.      */
  257.     public List<Ref> getRefsByPrefixWithExclusions(String include, Set<String> excludes) throws IOException {
  258.         if (excludes.isEmpty()) {
  259.             return getRefsByPrefix(include);
  260.         }
  261.         List<Ref> results = new ArrayList<>();
  262.         lock.lock();
  263.         try {
  264.             Reftable table = reader();
  265.             Iterator<String> excludeIterator =
  266.                     excludes.stream().sorted().collect(Collectors.toList()).iterator();
  267.             String currentExclusion = excludeIterator.hasNext() ? excludeIterator.next() : null;
  268.             try (RefCursor rc = RefDatabase.ALL.equals(include) ? table.allRefs() : table.seekRefsWithPrefix(include)) {
  269.                 while (rc.next()) {
  270.                     Ref ref = table.resolve(rc.getRef());
  271.                     if (ref == null || ref.getObjectId() == null) {
  272.                         continue;
  273.                     }
  274.                     // Skip prefixes that will never see since we are already further than those
  275.                     // prefixes lexicographically.
  276.                     while (excludeIterator.hasNext() && !ref.getName().startsWith(currentExclusion)
  277.                             && ref.getName().compareTo(currentExclusion) > 0) {
  278.                         currentExclusion = excludeIterator.next();
  279.                     }

  280.                     if (currentExclusion != null && ref.getName().startsWith(currentExclusion)) {
  281.                         rc.seekPastPrefix(currentExclusion);
  282.                         continue;
  283.                     }
  284.                     results.add(ref);
  285.                 }
  286.             }
  287.         } finally {
  288.             lock.unlock();
  289.         }

  290.         return Collections.unmodifiableList(results);
  291.     }

  292.     /**
  293.      * @return whether there is a fast SHA1 to ref map.
  294.      * @throws IOException in case of I/O problems.
  295.      */
  296.     public boolean hasFastTipsWithSha1() throws IOException {
  297.         lock.lock();
  298.         try {
  299.             return reader().hasObjectMap();
  300.         } finally {
  301.             lock.unlock();
  302.         }
  303.     }

  304.     /**
  305.      * Returns all refs that resolve directly to the given {@link ObjectId}.
  306.      * Includes peeled {@link ObjectId}s.
  307.      *
  308.      * @param id
  309.      *            {@link ObjectId} to resolve
  310.      * @return a {@link Set} of {@link Ref}s whose tips point to the provided
  311.      *         id.
  312.      * @throws java.io.IOException
  313.      *             on I/O errors.
  314.      */
  315.     public Set<Ref> getTipsWithSha1(ObjectId id) throws IOException {
  316.         lock.lock();
  317.         try {
  318.             RefCursor cursor = reader().byObjectId(id);
  319.             Set<Ref> refs = new HashSet<>();
  320.             while (cursor.next()) {
  321.                 refs.add(cursor.getRef());
  322.             }
  323.             return refs;
  324.         } finally {
  325.             lock.unlock();
  326.         }
  327.     }

  328.     /**
  329.      * Drops all data that might be cached in memory.
  330.      */
  331.     public void clearCache() {
  332.         lock.lock();
  333.         try {
  334.             mergedTables = null;
  335.         } finally {
  336.             lock.unlock();
  337.         }
  338.     }
  339. }