DfsRefDatabase.java

  1. /*
  2.  * Copyright (C) 2011, 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.dfs;

  11. import static org.eclipse.jgit.lib.Ref.UNDEFINED_UPDATE_INDEX;
  12. import static org.eclipse.jgit.lib.Ref.Storage.NEW;

  13. import java.io.IOException;
  14. import java.util.Collections;
  15. import java.util.List;
  16. import java.util.Map;
  17. import java.util.concurrent.atomic.AtomicReference;

  18. import org.eclipse.jgit.errors.MissingObjectException;
  19. import org.eclipse.jgit.lib.ObjectIdRef;
  20. import org.eclipse.jgit.lib.Ref;
  21. import org.eclipse.jgit.lib.RefDatabase;
  22. import org.eclipse.jgit.lib.RefRename;
  23. import org.eclipse.jgit.lib.RefUpdate;
  24. import org.eclipse.jgit.lib.SymbolicRef;
  25. import org.eclipse.jgit.revwalk.RevObject;
  26. import org.eclipse.jgit.revwalk.RevTag;
  27. import org.eclipse.jgit.revwalk.RevWalk;
  28. import org.eclipse.jgit.util.RefList;
  29. import org.eclipse.jgit.util.RefMap;

  30. /**
  31.  * Abstract DfsRefDatabase class.
  32.  *
  33.  */
  34. public abstract class DfsRefDatabase extends RefDatabase {
  35.     private final DfsRepository repository;

  36.     private final AtomicReference<RefCache> cache;

  37.     /**
  38.      * Initialize the reference database for a repository.
  39.      *
  40.      * @param repository
  41.      *            the repository this database instance manages references for.
  42.      */
  43.     protected DfsRefDatabase(DfsRepository repository) {
  44.         this.repository = repository;
  45.         this.cache = new AtomicReference<>();
  46.     }

  47.     /**
  48.      * Get the repository the database holds the references of.
  49.      *
  50.      * @return the repository the database holds the references of.
  51.      */
  52.     protected DfsRepository getRepository() {
  53.         return repository;
  54.     }

  55.     boolean exists() throws IOException {
  56.         return 0 < read().size();
  57.     }

  58.     /** {@inheritDoc} */
  59.     @Override
  60.     public Ref exactRef(String name) throws IOException {
  61.         RefCache curr = read();
  62.         Ref ref = curr.ids.get(name);
  63.         return ref != null ? resolve(ref, 0, curr.ids) : null;
  64.     }

  65.     /** {@inheritDoc} */
  66.     @Override
  67.     public List<Ref> getAdditionalRefs() {
  68.         return Collections.emptyList();
  69.     }

  70.     /** {@inheritDoc} */
  71.     @Override
  72.     public Map<String, Ref> getRefs(String prefix) throws IOException {
  73.         RefCache curr = read();
  74.         RefList<Ref> packed = RefList.emptyList();
  75.         RefList<Ref> loose = curr.ids;
  76.         RefList.Builder<Ref> sym = new RefList.Builder<>(curr.sym.size());

  77.         for (int idx = 0; idx < curr.sym.size(); idx++) {
  78.             Ref ref = curr.sym.get(idx);
  79.             String name = ref.getName();
  80.             ref = resolve(ref, 0, loose);
  81.             if (ref != null && ref.getObjectId() != null) {
  82.                 sym.add(ref);
  83.             } else {
  84.                 // A broken symbolic reference, we have to drop it from the
  85.                 // collections the client is about to receive. Should be a
  86.                 // rare occurrence so pay a copy penalty.
  87.                 int toRemove = loose.find(name);
  88.                 if (0 <= toRemove)
  89.                     loose = loose.remove(toRemove);
  90.             }
  91.         }

  92.         return new RefMap(prefix, packed, loose, sym.toRefList());
  93.     }

  94.     private Ref resolve(Ref ref, int depth, RefList<Ref> loose)
  95.             throws IOException {
  96.         if (!ref.isSymbolic())
  97.             return ref;

  98.         Ref dst = ref.getTarget();

  99.         if (MAX_SYMBOLIC_REF_DEPTH <= depth)
  100.             return null; // claim it doesn't exist

  101.         dst = loose.get(dst.getName());
  102.         if (dst == null)
  103.             return ref;

  104.         dst = resolve(dst, depth + 1, loose);
  105.         if (dst == null)
  106.             return null;
  107.         return new SymbolicRef(ref.getName(), dst);
  108.     }

  109.     /** {@inheritDoc} */
  110.     @Override
  111.     public Ref peel(Ref ref) throws IOException {
  112.         final Ref oldLeaf = ref.getLeaf();
  113.         if (oldLeaf.isPeeled() || oldLeaf.getObjectId() == null)
  114.             return ref;

  115.         Ref newLeaf = doPeel(oldLeaf);

  116.         RefCache cur = read();
  117.         int idx = cur.ids.find(oldLeaf.getName());
  118.         if (0 <= idx && cur.ids.get(idx) == oldLeaf) {
  119.             RefList<Ref> newList = cur.ids.set(idx, newLeaf);
  120.             cache.compareAndSet(cur, new RefCache(newList, cur));
  121.             cachePeeledState(oldLeaf, newLeaf);
  122.         }

  123.         return recreate(ref, newLeaf, hasVersioning());
  124.     }

  125.     Ref doPeel(Ref leaf) throws MissingObjectException,
  126.             IOException {
  127.         try (RevWalk rw = new RevWalk(repository)) {
  128.             RevObject obj = rw.parseAny(leaf.getObjectId());
  129.             if (obj instanceof RevTag) {
  130.                 return new ObjectIdRef.PeeledTag(
  131.                         leaf.getStorage(),
  132.                         leaf.getName(),
  133.                         leaf.getObjectId(),
  134.                         rw.peel(obj).copy(),
  135.                         hasVersioning() ? leaf.getUpdateIndex()
  136.                                 : UNDEFINED_UPDATE_INDEX);
  137.             }
  138.             return new ObjectIdRef.PeeledNonTag(leaf.getStorage(),
  139.                     leaf.getName(), leaf.getObjectId(),
  140.                     hasVersioning() ? leaf.getUpdateIndex()
  141.                             : UNDEFINED_UPDATE_INDEX);
  142.         }
  143.     }

  144.     static Ref recreate(Ref old, Ref leaf, boolean hasVersioning) {
  145.         if (old.isSymbolic()) {
  146.             Ref dst = recreate(old.getTarget(), leaf, hasVersioning);
  147.             return new SymbolicRef(old.getName(), dst,
  148.                     hasVersioning ? old.getUpdateIndex()
  149.                             : UNDEFINED_UPDATE_INDEX);
  150.         }
  151.         return leaf;
  152.     }

  153.     /** {@inheritDoc} */
  154.     @Override
  155.     public RefUpdate newUpdate(String refName, boolean detach)
  156.             throws IOException {
  157.         boolean detachingSymbolicRef = false;
  158.         Ref ref = exactRef(refName);
  159.         if (ref == null)
  160.             ref = new ObjectIdRef.Unpeeled(NEW, refName, null);
  161.         else
  162.             detachingSymbolicRef = detach && ref.isSymbolic();

  163.         DfsRefUpdate update = new DfsRefUpdate(this, ref);
  164.         if (detachingSymbolicRef)
  165.             update.setDetachingSymbolicRef();
  166.         return update;
  167.     }

  168.     /** {@inheritDoc} */
  169.     @Override
  170.     public RefRename newRename(String fromName, String toName)
  171.             throws IOException {
  172.         RefUpdate src = newUpdate(fromName, true);
  173.         RefUpdate dst = newUpdate(toName, true);
  174.         return new DfsRefRename(src, dst);
  175.     }

  176.     /** {@inheritDoc} */
  177.     @Override
  178.     public boolean isNameConflicting(String refName) throws IOException {
  179.         RefList<Ref> all = read().ids;

  180.         // Cannot be nested within an existing reference.
  181.         int lastSlash = refName.lastIndexOf('/');
  182.         while (0 < lastSlash) {
  183.             String needle = refName.substring(0, lastSlash);
  184.             if (all.contains(needle))
  185.                 return true;
  186.             lastSlash = refName.lastIndexOf('/', lastSlash - 1);
  187.         }

  188.         // Cannot be the container of an existing reference.
  189.         String prefix = refName + '/';
  190.         int idx = -(all.find(prefix) + 1);
  191.         if (idx < all.size() && all.get(idx).getName().startsWith(prefix))
  192.             return true;
  193.         return false;
  194.     }

  195.     /** {@inheritDoc} */
  196.     @Override
  197.     public void create() {
  198.         // Nothing to do.
  199.     }

  200.     /** {@inheritDoc} */
  201.     @Override
  202.     public void refresh() {
  203.         clearCache();
  204.     }

  205.     /** {@inheritDoc} */
  206.     @Override
  207.     public void close() {
  208.         clearCache();
  209.     }

  210.     void clearCache() {
  211.         cache.set(null);
  212.     }

  213.     void stored(Ref ref) {
  214.         RefCache oldCache, newCache;
  215.         do {
  216.             oldCache = cache.get();
  217.             if (oldCache == null)
  218.                 return;
  219.             newCache = oldCache.put(ref);
  220.         } while (!cache.compareAndSet(oldCache, newCache));
  221.     }

  222.     void removed(String refName) {
  223.         RefCache oldCache, newCache;
  224.         do {
  225.             oldCache = cache.get();
  226.             if (oldCache == null)
  227.                 return;
  228.             newCache = oldCache.remove(refName);
  229.         } while (!cache.compareAndSet(oldCache, newCache));
  230.     }

  231.     private RefCache read() throws IOException {
  232.         RefCache c = cache.get();
  233.         if (c == null) {
  234.             c = scanAllRefs();
  235.             cache.set(c);
  236.         }
  237.         return c;
  238.     }

  239.     /**
  240.      * Read all known references in the repository.
  241.      *
  242.      * @return all current references of the repository.
  243.      * @throws java.io.IOException
  244.      *             references cannot be accessed.
  245.      */
  246.     protected abstract RefCache scanAllRefs() throws IOException;

  247.     /**
  248.      * Compare a reference, and put if it matches.
  249.      * <p>
  250.      * Two reference match if and only if they satisfy the following:
  251.      *
  252.      * <ul>
  253.      * <li>If one reference is a symbolic ref, the other one should be a symbolic
  254.      * ref.
  255.      * <li>If both are symbolic refs, the target names should be same.
  256.      * <li>If both are object ID refs, the object IDs should be same.
  257.      * </ul>
  258.      *
  259.      * @param oldRef
  260.      *            old value to compare to. If the reference is expected to not
  261.      *            exist the old value has a storage of
  262.      *            {@link org.eclipse.jgit.lib.Ref.Storage#NEW} and an ObjectId
  263.      *            value of {@code null}.
  264.      * @param newRef
  265.      *            new reference to store.
  266.      * @return true if the put was successful; false otherwise.
  267.      * @throws java.io.IOException
  268.      *             the reference cannot be put due to a system error.
  269.      */
  270.     protected abstract boolean compareAndPut(Ref oldRef, Ref newRef)
  271.             throws IOException;

  272.     /**
  273.      * Compare a reference, and delete if it matches.
  274.      *
  275.      * @param oldRef
  276.      *            the old reference information that was previously read.
  277.      * @return true if the remove was successful; false otherwise.
  278.      * @throws java.io.IOException
  279.      *             the reference could not be removed due to a system error.
  280.      */
  281.     protected abstract boolean compareAndRemove(Ref oldRef) throws IOException;

  282.     /**
  283.      * Update the cached peeled state of a reference
  284.      * <p>
  285.      * The ref database invokes this method after it peels a reference that had
  286.      * not been peeled before. This allows the storage to cache the peel state
  287.      * of the reference, and if it is actually peelable, the target that it
  288.      * peels to, so that on-the-fly peeling doesn't have to happen on the next
  289.      * reference read.
  290.      *
  291.      * @param oldLeaf
  292.      *            the old reference.
  293.      * @param newLeaf
  294.      *            the new reference, with peel information.
  295.      */
  296.     protected void cachePeeledState(Ref oldLeaf, Ref newLeaf) {
  297.         try {
  298.             compareAndPut(oldLeaf, newLeaf);
  299.         } catch (IOException e) {
  300.             // Ignore an exception during caching.
  301.         }
  302.     }

  303.     /** Collection of references managed by this database. */
  304.     public static class RefCache {
  305.         final RefList<Ref> ids;

  306.         final RefList<Ref> sym;

  307.         /**
  308.          * Initialize a new reference cache.
  309.          * <p>
  310.          * The two reference lists supplied must be sorted in correct order
  311.          * (string compare order) by name.
  312.          *
  313.          * @param ids
  314.          *            references that carry an ObjectId, and all of {@code sym}.
  315.          * @param sym
  316.          *            references that are symbolic references to others.
  317.          */
  318.         public RefCache(RefList<Ref> ids, RefList<Ref> sym) {
  319.             this.ids = ids;
  320.             this.sym = sym;
  321.         }

  322.         RefCache(RefList<Ref> ids, RefCache old) {
  323.             this(ids, old.sym);
  324.         }

  325.         /** @return number of references in this cache. */
  326.         public int size() {
  327.             return ids.size();
  328.         }

  329.         /**
  330.          * Find a reference by name.
  331.          *
  332.          * @param name
  333.          *            full name of the reference.
  334.          * @return the reference, if it exists, otherwise null.
  335.          */
  336.         public Ref get(String name) {
  337.             return ids.get(name);
  338.         }

  339.         /**
  340.          * Obtain a modified copy of the cache with a ref stored.
  341.          * <p>
  342.          * This cache instance is not modified by this method.
  343.          *
  344.          * @param ref
  345.          *            reference to add or replace.
  346.          * @return a copy of this cache, with the reference added or replaced.
  347.          */
  348.         public RefCache put(Ref ref) {
  349.             RefList<Ref> newIds = this.ids.put(ref);
  350.             RefList<Ref> newSym = this.sym;
  351.             if (ref.isSymbolic()) {
  352.                 newSym = newSym.put(ref);
  353.             } else {
  354.                 int p = newSym.find(ref.getName());
  355.                 if (0 <= p)
  356.                     newSym = newSym.remove(p);
  357.             }
  358.             return new RefCache(newIds, newSym);
  359.         }

  360.         /**
  361.          * Obtain a modified copy of the cache with the ref removed.
  362.          * <p>
  363.          * This cache instance is not modified by this method.
  364.          *
  365.          * @param refName
  366.          *            reference to remove, if it exists.
  367.          * @return a copy of this cache, with the reference removed.
  368.          */
  369.         public RefCache remove(String refName) {
  370.             RefList<Ref> newIds = this.ids;
  371.             int p = newIds.find(refName);
  372.             if (0 <= p)
  373.                 newIds = newIds.remove(p);

  374.             RefList<Ref> newSym = this.sym;
  375.             p = newSym.find(refName);
  376.             if (0 <= p)
  377.                 newSym = newSym.remove(p);
  378.             return new RefCache(newIds, newSym);
  379.         }
  380.     }
  381. }