blob: 696347e8fe3b80f0500a0eaf5d833d1f293ba7bc [file] [log] [blame]
/*
* Copyright (C) 2017, Google LLC
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
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 {@linkObjectId}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();
}
}
}