/* * Copyright (C) 2011, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.internal.storage.dfs; import static org.eclipse.jgit.lib.Ref.UNDEFINED_UPDATE_INDEX; import static org.eclipse.jgit.lib.Ref.Storage.NEW; import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefDatabase; import org.eclipse.jgit.lib.RefRename; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.SymbolicRef; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.util.RefList; import org.eclipse.jgit.util.RefMap; /** * Abstract DfsRefDatabase class. */ public abstract class DfsRefDatabase extends RefDatabase { private final DfsRepository repository; private final AtomicReference cache; /** * Initialize the reference database for a repository. * * @param repository * the repository this database instance manages references for. */ protected DfsRefDatabase(DfsRepository repository) { this.repository = repository; this.cache = new AtomicReference<>(); } /** * Get the repository the database holds the references of. * * @return the repository the database holds the references of. */ protected DfsRepository getRepository() { return repository; } boolean exists() throws IOException { return 0 < read().size(); } @Override public Ref exactRef(String name) throws IOException { RefCache curr = read(); Ref ref = curr.ids.get(name); return ref != null ? resolve(ref, 0, curr.ids) : null; } @Override public List getAdditionalRefs() { return Collections.emptyList(); } @Override public Map getRefs(String prefix) throws IOException { RefCache curr = read(); RefList packed = RefList.emptyList(); RefList loose = curr.ids; RefList.Builder sym = new RefList.Builder<>(curr.sym.size()); for (int idx = 0; idx < curr.sym.size(); idx++) { Ref ref = curr.sym.get(idx); String name = ref.getName(); ref = resolve(ref, 0, loose); if (ref != null && ref.getObjectId() != null) { sym.add(ref); } else { // A broken symbolic reference, we have to drop it from the // collections the client is about to receive. Should be a // rare occurrence so pay a copy penalty. int toRemove = loose.find(name); if (0 <= toRemove) loose = loose.remove(toRemove); } } return new RefMap(prefix, packed, loose, sym.toRefList()); } private Ref resolve(Ref ref, int depth, RefList loose) throws IOException { if (!ref.isSymbolic()) return ref; Ref dst = ref.getTarget(); if (MAX_SYMBOLIC_REF_DEPTH <= depth) return null; // claim it doesn't exist dst = loose.get(dst.getName()); if (dst == null) return ref; dst = resolve(dst, depth + 1, loose); if (dst == null) return null; return new SymbolicRef(ref.getName(), dst); } @Override public Ref peel(Ref ref) throws IOException { final Ref oldLeaf = ref.getLeaf(); if (oldLeaf.isPeeled() || oldLeaf.getObjectId() == null) return ref; Ref newLeaf = doPeel(oldLeaf); RefCache cur = read(); int idx = cur.ids.find(oldLeaf.getName()); if (0 <= idx && cur.ids.get(idx) == oldLeaf) { RefList newList = cur.ids.set(idx, newLeaf); cache.compareAndSet(cur, new RefCache(newList, cur)); cachePeeledState(oldLeaf, newLeaf); } return recreate(ref, newLeaf, hasVersioning()); } Ref doPeel(Ref leaf) throws MissingObjectException, IOException { try (RevWalk rw = new RevWalk(repository)) { RevObject obj = rw.parseAny(leaf.getObjectId()); if (obj instanceof RevTag) { return new ObjectIdRef.PeeledTag( leaf.getStorage(), leaf.getName(), leaf.getObjectId(), rw.peel(obj).copy(), hasVersioning() ? leaf.getUpdateIndex() : UNDEFINED_UPDATE_INDEX); } return new ObjectIdRef.PeeledNonTag(leaf.getStorage(), leaf.getName(), leaf.getObjectId(), hasVersioning() ? leaf.getUpdateIndex() : UNDEFINED_UPDATE_INDEX); } } static Ref recreate(Ref old, Ref leaf, boolean hasVersioning) { if (old.isSymbolic()) { Ref dst = recreate(old.getTarget(), leaf, hasVersioning); return new SymbolicRef(old.getName(), dst, hasVersioning ? old.getUpdateIndex() : UNDEFINED_UPDATE_INDEX); } return leaf; } @Override public RefUpdate newUpdate(String refName, boolean detach) throws IOException { boolean detachingSymbolicRef = false; Ref ref = exactRef(refName); if (ref == null) ref = new ObjectIdRef.Unpeeled(NEW, refName, null); else detachingSymbolicRef = detach && ref.isSymbolic(); DfsRefUpdate update = new DfsRefUpdate(this, ref); if (detachingSymbolicRef) update.setDetachingSymbolicRef(); return update; } @Override public RefRename newRename(String fromName, String toName) throws IOException { RefUpdate src = newUpdate(fromName, true); RefUpdate dst = newUpdate(toName, true); return new DfsRefRename(src, dst); } @Override public boolean isNameConflicting(String refName) throws IOException { RefList all = read().ids; // Cannot be nested within an existing reference. int lastSlash = refName.lastIndexOf('/'); while (0 < lastSlash) { String needle = refName.substring(0, lastSlash); if (all.contains(needle)) return true; lastSlash = refName.lastIndexOf('/', lastSlash - 1); } // Cannot be the container of an existing reference. String prefix = refName + '/'; int idx = -(all.find(prefix) + 1); if (idx < all.size() && all.get(idx).getName().startsWith(prefix)) return true; return false; } @Override public void create() { // Nothing to do. } @Override public void refresh() { clearCache(); } @Override public void close() { clearCache(); } void clearCache() { cache.set(null); } void stored(Ref ref) { RefCache oldCache, newCache; do { oldCache = cache.get(); if (oldCache == null) return; newCache = oldCache.put(ref); } while (!cache.compareAndSet(oldCache, newCache)); } void removed(String refName) { RefCache oldCache, newCache; do { oldCache = cache.get(); if (oldCache == null) return; newCache = oldCache.remove(refName); } while (!cache.compareAndSet(oldCache, newCache)); } private RefCache read() throws IOException { RefCache c = cache.get(); if (c == null) { c = scanAllRefs(); cache.set(c); } return c; } /** * Read all known references in the repository. * * @return all current references of the repository. * @throws java.io.IOException * references cannot be accessed. */ protected abstract RefCache scanAllRefs() throws IOException; /** * Compare a reference, and put if it matches. *

* Two reference match if and only if they satisfy the following: * *

    *
  • If one reference is a symbolic ref, the other one should be a symbolic * ref. *
  • If both are symbolic refs, the target names should be same. *
  • If both are object ID refs, the object IDs should be same. *
* * @param oldRef * old value to compare to. If the reference is expected to not * exist the old value has a storage of * {@link org.eclipse.jgit.lib.Ref.Storage#NEW} and an ObjectId * value of {@code null}. * @param newRef * new reference to store. * @return true if the put was successful; false otherwise. * @throws java.io.IOException * the reference cannot be put due to a system error. */ protected abstract boolean compareAndPut(Ref oldRef, Ref newRef) throws IOException; /** * Compare a reference, and delete if it matches. * * @param oldRef * the old reference information that was previously read. * @return true if the remove was successful; false otherwise. * @throws java.io.IOException * the reference could not be removed due to a system error. */ protected abstract boolean compareAndRemove(Ref oldRef) throws IOException; /** * Update the cached peeled state of a reference *

* The ref database invokes this method after it peels a reference that had * not been peeled before. This allows the storage to cache the peel state * of the reference, and if it is actually peelable, the target that it * peels to, so that on-the-fly peeling doesn't have to happen on the next * reference read. * * @param oldLeaf * the old reference. * @param newLeaf * the new reference, with peel information. */ protected void cachePeeledState(Ref oldLeaf, Ref newLeaf) { try { compareAndPut(oldLeaf, newLeaf); } catch (IOException e) { // Ignore an exception during caching. } } /** Collection of references managed by this database. */ public static class RefCache { final RefList ids; final RefList sym; /** * Initialize a new reference cache. *

* The two reference lists supplied must be sorted in correct order * (string compare order) by name. * * @param ids * references that carry an ObjectId, and all of {@code sym}. * @param sym * references that are symbolic references to others. */ public RefCache(RefList ids, RefList sym) { this.ids = ids; this.sym = sym; } RefCache(RefList ids, RefCache old) { this(ids, old.sym); } /** * Get number of references * * @return number of references in this cache. */ public int size() { return ids.size(); } /** * Find a reference by name. * * @param name * full name of the reference. * @return the reference, if it exists, otherwise null. */ public Ref get(String name) { return ids.get(name); } /** * Obtain a modified copy of the cache with a ref stored. *

* This cache instance is not modified by this method. * * @param ref * reference to add or replace. * @return a copy of this cache, with the reference added or replaced. */ public RefCache put(Ref ref) { RefList newIds = this.ids.put(ref); RefList newSym = this.sym; if (ref.isSymbolic()) { newSym = newSym.put(ref); } else { int p = newSym.find(ref.getName()); if (0 <= p) newSym = newSym.remove(p); } return new RefCache(newIds, newSym); } /** * Obtain a modified copy of the cache with the ref removed. *

* This cache instance is not modified by this method. * * @param refName * reference to remove, if it exists. * @return a copy of this cache, with the reference removed. */ public RefCache remove(String refName) { RefList newIds = this.ids; int p = newIds.find(refName); if (0 <= p) newIds = newIds.remove(p); RefList newSym = this.sym; p = newSym.find(refName); if (0 <= p) newSym = newSym.remove(p); return new RefCache(newIds, newSym); } } }