| /* |
| * Copyright (C) 2017, Google LLC 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.reftable; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import java.util.concurrent.locks.ReentrantLock; |
| |
| import org.eclipse.jgit.annotations.Nullable; |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.eclipse.jgit.lib.Ref; |
| import org.eclipse.jgit.lib.RefDatabase; |
| import org.eclipse.jgit.lib.ReflogReader; |
| import org.eclipse.jgit.transport.ReceiveCommand; |
| |
| /** |
| * Operations on {@link MergedReftable} that is common to various reftable-using |
| * subclasses of {@link RefDatabase}. See |
| * {@link org.eclipse.jgit.internal.storage.dfs.DfsReftableDatabase} for an |
| * example. |
| */ |
| public abstract class ReftableDatabase { |
| // Protects mergedTables. |
| private final ReentrantLock lock = new ReentrantLock(true); |
| |
| private Reftable mergedTables; |
| |
| /** |
| * ReftableDatabase lazily initializes its merged reftable on the first read after |
| * construction or clearCache() call. This function should always instantiate a new |
| * MergedReftable based on the list of reftables specified by the underlying storage. |
| * |
| * @return the ReftableStack for this instance |
| * @throws IOException |
| * on I/O problems. |
| */ |
| protected abstract MergedReftable openMergedReftable() throws IOException; |
| |
| /** |
| * @return the next available logical timestamp for an additional reftable |
| * in the stack. |
| * @throws java.io.IOException |
| * on I/O problems. |
| */ |
| public long nextUpdateIndex() throws IOException { |
| lock.lock(); |
| try { |
| return reader().maxUpdateIndex() + 1; |
| } finally { |
| lock.unlock(); |
| } |
| } |
| |
| /** |
| * @return a ReflogReader for the given ref |
| * @param refname |
| * the name of the ref. |
| * @throws IOException |
| * on I/O problems |
| */ |
| public ReflogReader getReflogReader(String refname) throws IOException { |
| lock.lock(); |
| try { |
| return new ReftableReflogReader(lock, reader(), refname); |
| } finally { |
| lock.unlock(); |
| } |
| } |
| |
| /** |
| * @return a ReceiveCommand for the change from oldRef to newRef |
| * @param oldRef |
| * a ref |
| * @param newRef |
| * a ref |
| */ |
| public static ReceiveCommand toCommand(Ref oldRef, Ref newRef) { |
| ObjectId oldId = toId(oldRef); |
| ObjectId newId = toId(newRef); |
| String name = oldRef != null ? oldRef.getName() : newRef.getName(); |
| |
| if (oldRef != null && oldRef.isSymbolic()) { |
| if (newRef != null) { |
| if (newRef.isSymbolic()) { |
| return ReceiveCommand.link(oldRef.getTarget().getName(), |
| newRef.getTarget().getName(), name); |
| } |
| // This should pass in oldId for compat with |
| // RefDirectoryUpdate |
| return ReceiveCommand.unlink(oldRef.getTarget().getName(), |
| newId, name); |
| } |
| return ReceiveCommand.unlink(oldRef.getTarget().getName(), |
| ObjectId.zeroId(), name); |
| } |
| |
| if (newRef != null && newRef.isSymbolic()) { |
| if (oldRef != null) { |
| if (oldRef.isSymbolic()) { |
| return ReceiveCommand.link(oldRef.getTarget().getName(), |
| newRef.getTarget().getName(), name); |
| } |
| return ReceiveCommand.link(oldId, |
| newRef.getTarget().getName(), name); |
| } |
| return ReceiveCommand.link(ObjectId.zeroId(), |
| newRef.getTarget().getName(), name); |
| } |
| |
| return new ReceiveCommand(oldId, newId, name); |
| } |
| |
| private static ObjectId toId(Ref ref) { |
| if (ref != null) { |
| ObjectId id = ref.getObjectId(); |
| if (id != null) { |
| return id; |
| } |
| } |
| return ObjectId.zeroId(); |
| } |
| |
| /** |
| * @return the lock protecting underlying ReftableReaders against concurrent |
| * reads. |
| */ |
| public ReentrantLock getLock() { |
| return lock; |
| } |
| |
| /** |
| * @return the merged reftable that is implemented by the stack of |
| * reftables. Return value must be accessed under lock. |
| * @throws IOException |
| * on I/O problems |
| */ |
| private Reftable reader() throws IOException { |
| if (!lock.isLocked()) { |
| throw new IllegalStateException( |
| "must hold lock to access merged table"); //$NON-NLS-1$ |
| } |
| if (mergedTables == null) { |
| mergedTables = openMergedReftable(); |
| } |
| return mergedTables; |
| } |
| |
| /** |
| * @return whether the given refName would be illegal in a repository that |
| * uses loose refs. |
| * @param refName |
| * the name to check |
| * @param added |
| * a sorted set of refs we pretend have been added to the |
| * database. |
| * @param deleted |
| * a set of refs we pretend have been removed from the database. |
| * @throws IOException |
| * on I/O problems |
| */ |
| public boolean isNameConflicting(String refName, TreeSet<String> added, |
| Set<String> deleted) throws IOException { |
| lock.lock(); |
| try { |
| Reftable table = reader(); |
| |
| // Cannot be nested within an existing reference. |
| int lastSlash = refName.lastIndexOf('/'); |
| while (0 < lastSlash) { |
| String prefix = refName.substring(0, lastSlash); |
| if (!deleted.contains(prefix) |
| && (table.hasRef(prefix) || added.contains(prefix))) { |
| return true; |
| } |
| lastSlash = refName.lastIndexOf('/', lastSlash - 1); |
| } |
| |
| // Cannot be the container of an existing reference. |
| String prefix = refName + '/'; |
| RefCursor c = table.seekRefsWithPrefix(prefix); |
| while (c.next()) { |
| if (!deleted.contains(c.getRef().getName())) { |
| return true; |
| } |
| } |
| |
| String it = added.ceiling(refName + '/'); |
| if (it != null && it.startsWith(prefix)) { |
| return true; |
| } |
| return false; |
| } finally { |
| lock.unlock(); |
| } |
| } |
| |
| /** |
| * Read a single reference. |
| * <p> |
| * This method expects an unshortened reference name and does not search |
| * using the standard search path. |
| * |
| * @param name |
| * the unabbreviated name of the reference. |
| * @return the reference (if it exists); else {@code null}. |
| * @throws java.io.IOException |
| * the reference space cannot be accessed. |
| */ |
| @Nullable |
| public Ref exactRef(String name) throws IOException { |
| lock.lock(); |
| try { |
| Reftable table = reader(); |
| Ref ref = table.exactRef(name); |
| if (ref != null && ref.isSymbolic()) { |
| return table.resolve(ref); |
| } |
| return ref; |
| } finally { |
| lock.unlock(); |
| } |
| } |
| |
| /** |
| * Returns refs whose names start with a given prefix. |
| * |
| * @param prefix |
| * string that names of refs should start with; may be empty (to |
| * return all refs). |
| * @return immutable list of refs whose names start with {@code prefix}. |
| * @throws java.io.IOException |
| * the reference space cannot be accessed. |
| */ |
| public List<Ref> getRefsByPrefix(String prefix) throws IOException { |
| List<Ref> all = new ArrayList<>(); |
| lock.lock(); |
| try { |
| Reftable table = reader(); |
| try (RefCursor rc = RefDatabase.ALL.equals(prefix) ? table.allRefs() |
| : table.seekRefsWithPrefix(prefix)) { |
| while (rc.next()) { |
| Ref ref = table.resolve(rc.getRef()); |
| if (ref != null && ref.getObjectId() != null) { |
| all.add(ref); |
| } |
| } |
| } |
| } finally { |
| lock.unlock(); |
| } |
| |
| return Collections.unmodifiableList(all); |
| } |
| |
| /** |
| * @return whether there is a fast SHA1 to ref map. |
| * @throws IOException in case of I/O problems. |
| */ |
| public boolean hasFastTipsWithSha1() throws IOException { |
| lock.lock(); |
| try { |
| return reader().hasObjectMap(); |
| } finally { |
| lock.unlock(); |
| } |
| } |
| |
| /** |
| * Returns all refs that resolve directly to the given {@link ObjectId}. |
| * Includes peeled {@link ObjectId}s. |
| * |
| * @param id |
| * {@link ObjectId} to resolve |
| * @return a {@link Set} of {@link Ref}s whose tips point to the provided |
| * id. |
| * @throws java.io.IOException |
| * on I/O errors. |
| */ |
| public Set<Ref> getTipsWithSha1(ObjectId id) throws IOException { |
| lock.lock(); |
| try { |
| RefCursor cursor = reader().byObjectId(id); |
| Set<Ref> refs = new HashSet<>(); |
| while (cursor.next()) { |
| refs.add(cursor.getRef()); |
| } |
| return refs; |
| } finally { |
| lock.unlock(); |
| } |
| } |
| |
| /** |
| * Drops all data that might be cached in memory. |
| */ |
| public void clearCache() { |
| lock.lock(); |
| try { |
| mergedTables = null; |
| } finally { |
| lock.unlock(); |
| } |
| } |
| } |