/* * Copyright (C) 2011, Google Inc. * 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.storage.dht; import static org.eclipse.jgit.lib.Ref.Storage.LOOSE; import static org.eclipse.jgit.lib.Ref.Storage.NEW; import static org.eclipse.jgit.storage.dht.RefDataUtil.NONE; import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.generated.storage.dht.proto.GitStore.RefData; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdSubclassMap; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefDatabase; import org.eclipse.jgit.lib.RefRename; 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.storage.dht.RefDataUtil.IdWithChunk; import org.eclipse.jgit.storage.dht.spi.Context; import org.eclipse.jgit.storage.dht.spi.Database; import org.eclipse.jgit.util.RefList; import org.eclipse.jgit.util.RefMap; /** Repository references stored on top of a DHT database. */ public class DhtRefDatabase extends RefDatabase { private final DhtRepository repository; private final Database db; private final AtomicReference cache; DhtRefDatabase(DhtRepository repository, Database db) { this.repository = repository; this.db = db; this.cache = new AtomicReference(); } DhtRepository getRepository() { return repository; } ChunkKey findChunk(AnyObjectId id) { RefCache c = cache.get(); if (c != null) { IdWithChunk i = c.hints.get(id); if (i != null) return i.getChunkKey(); } return null; } @Override public Ref getRef(String needle) throws IOException { RefCache curr = readRefs(); for (String prefix : SEARCH_PATH) { DhtRef ref = curr.ids.get(prefix + needle); if (ref != null) { ref = resolve(ref, 0, curr.ids); return ref; } } return null; } private DhtRef getOneRef(String refName) throws IOException { RefCache curr = readRefs(); DhtRef ref = curr.ids.get(refName); if (ref != null) return resolve(ref, 0, curr.ids); return ref; } @Override public List getAdditionalRefs() { return Collections.emptyList(); } @Override public Map getRefs(String prefix) throws IOException { RefCache curr = readRefs(); 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++) { DhtRef 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 DhtRef resolve(DhtRef ref, int depth, RefList loose) throws IOException { if (!ref.isSymbolic()) return ref; DhtRef dst = (DhtRef) 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 DhtSymbolicRef( ref.getName(), dst, ((DhtSymbolicRef) ref).getRefData()); } @Override public Ref peel(Ref ref) throws IOException { final Ref oldLeaf = ref.getLeaf(); if (oldLeaf.isPeeled() || oldLeaf.getObjectId() == null) return ref; DhtRef newLeaf = doPeel(oldLeaf); RefCache cur = readRefs(); int idx = cur.ids.find(oldLeaf.getName()); if (0 <= idx && cur.ids.get(idx) == oldLeaf) { RefList newList = cur.ids.set(idx, newLeaf); if (cache.compareAndSet(cur, new RefCache(newList, cur))) cachePeeledState(oldLeaf, newLeaf); } return recreate(ref, newLeaf); } private void cachePeeledState(Ref oldLeaf, Ref newLeaf) { // TODO(spearce) Use an ExecutorService here try { RepositoryKey repo = repository.getRepositoryKey(); RefKey key = RefKey.create(repo, newLeaf.getName()); RefData oldData = ((DhtRef) oldLeaf).getRefData(); RefData newData = ((DhtRef) newLeaf).getRefData(); db.ref().compareAndPut(key, oldData, newData); } catch (TimeoutException e) { // Ignore a timeout here, we were only trying to update // a cached value to save peeling costs in the future. } catch (DhtException e) { // Ignore a database error, this was only an attempt to // fix a value that could be cached to save time later. } } private DhtRef doPeel(final Ref leaf) throws MissingObjectException, IOException { RevWalk rw = new RevWalk(getRepository()); try { DhtReader ctx = (DhtReader) rw.getObjectReader(); RevObject obj = rw.parseAny(leaf.getObjectId()); RefData.Builder d = RefData.newBuilder(((DhtRef) leaf).getRefData()); ChunkKey oKey = ctx.findChunk(leaf.getObjectId()); if (oKey != null) d.getTargetBuilder().setChunkKey(oKey.asString()); else d.getTargetBuilder().clearChunkKey(); if (obj instanceof RevTag) { ObjectId pId = rw.peel(obj); d.getPeeledBuilder().setObjectName(pId.name()); ChunkKey pKey = ctx.findChunk(pId); if (pKey != null) d.getPeeledBuilder().setChunkKey(pKey.asString()); else d.getPeeledBuilder().clearChunkKey(); } else { d.clearPeeled(); } d.setIsPeeled(true); d.setSequence(d.getSequence() + 1); return new DhtObjectIdRef(leaf.getName(), d.build()); } finally { rw.release(); } } private static Ref recreate(final Ref old, final Ref leaf) { if (old.isSymbolic()) { Ref dst = recreate(old.getTarget(), leaf); return new SymbolicRef(old.getName(), dst); } return leaf; } @Override public DhtRefUpdate newUpdate(String refName, boolean detach) throws IOException { boolean detachingSymbolicRef = false; DhtRef ref = getOneRef(refName); if (ref == null) ref = new DhtObjectIdRef(refName, NONE); else detachingSymbolicRef = detach && ref.isSymbolic(); if (detachingSymbolicRef) { RefData src = ((DhtRef) ref.getLeaf()).getRefData(); RefData.Builder b = RefData.newBuilder(ref.getRefData()); b.clearSymref(); b.setTarget(src.getTarget()); ref = new DhtObjectIdRef(refName, b.build()); } RepositoryKey repo = repository.getRepositoryKey(); DhtRefUpdate update = new DhtRefUpdate(this, repo, db, ref); if (detachingSymbolicRef) update.setDetachingSymbolicRef(); return update; } @Override public RefRename newRename(String fromName, String toName) throws IOException { DhtRefUpdate src = newUpdate(fromName, true); DhtRefUpdate dst = newUpdate(toName, true); return new DhtRefRename(src, dst); } @Override public boolean isNameConflicting(String refName) throws IOException { RefList all = readRefs().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 close() { clearCache(); } void clearCache() { cache.set(null); } void stored(String refName, RefData newData) { DhtRef ref = fromData(refName, newData); RefCache oldCache, newCache; do { oldCache = cache.get(); if (oldCache == null) return; RefList ids = oldCache.ids.put(ref); RefList sym = oldCache.sym; if (ref.isSymbolic()) { sym = sym.put(ref); } else { int p = sym.find(refName); if (0 <= p) sym = sym.remove(p); } newCache = new RefCache(ids, sym, oldCache.hints); } while (!cache.compareAndSet(oldCache, newCache)); } void removed(String refName) { RefCache oldCache, newCache; do { oldCache = cache.get(); if (oldCache == null) return; int p; RefList ids = oldCache.ids; p = ids.find(refName); if (0 <= p) ids = ids.remove(p); RefList sym = oldCache.sym; p = sym.find(refName); if (0 <= p) sym = sym.remove(p); newCache = new RefCache(ids, sym, oldCache.hints); } while (!cache.compareAndSet(oldCache, newCache)); } private RefCache readRefs() throws DhtException { RefCache c = cache.get(); if (c == null) { try { c = read(); } catch (TimeoutException e) { throw new DhtTimeoutException(e); } cache.set(c); } return c; } private RefCache read() throws DhtException, TimeoutException { RefList.Builder id = new RefList.Builder(); RefList.Builder sym = new RefList.Builder(); ObjectIdSubclassMap hints = new ObjectIdSubclassMap(); for (Map.Entry e : scan()) { DhtRef ref = fromData(e.getKey().getName(), e.getValue()); if (ref.isSymbolic()) sym.add(ref); id.add(ref); if (ref.getObjectId() instanceof IdWithChunk && !hints.contains(ref.getObjectId())) hints.add((IdWithChunk) ref.getObjectId()); if (ref.getPeeledObjectId() instanceof IdWithChunk && !hints.contains(ref.getPeeledObjectId())) hints.add((IdWithChunk) ref.getPeeledObjectId()); } id.sort(); sym.sort(); return new RefCache(id.toRefList(), sym.toRefList(), hints); } static DhtRef fromData(String name, RefData data) { if (data.hasSymref()) return new DhtSymbolicRef(name, data); else return new DhtObjectIdRef(name, data); } private static ObjectId idFrom(RefData.Id src) { ObjectId id = ObjectId.fromString(src.getObjectName()); if (!src.hasChunkKey()) return id; return new IdWithChunk(id, ChunkKey.fromString(src.getChunkKey())); } private Set> scan() throws DhtException, TimeoutException { // TODO(spearce) Do we need to perform READ_REPAIR here? RepositoryKey repo = repository.getRepositoryKey(); return db.ref().getAll(Context.LOCAL, repo).entrySet(); } private static class RefCache { final RefList ids; final RefList sym; final ObjectIdSubclassMap hints; RefCache(RefList ids, RefList sym, ObjectIdSubclassMap hints) { this.ids = ids; this.sym = sym; this.hints = hints; } RefCache(RefList ids, RefCache old) { this(ids, old.sym, old.hints); } } static interface DhtRef extends Ref { RefData getRefData(); } private static class DhtSymbolicRef extends SymbolicRef implements DhtRef { private final RefData data; DhtSymbolicRef(String refName,RefData data) { super(refName, new DhtObjectIdRef(data.getSymref(), NONE)); this.data = data; } DhtSymbolicRef(String refName, Ref target, RefData data) { super(refName, target); this.data = data; } public RefData getRefData() { return data; } } private static class DhtObjectIdRef implements DhtRef { private final String name; private final RefData data; private final ObjectId objectId; private final ObjectId peeledId; DhtObjectIdRef(String name, RefData data) { this.name = name; this.data = data; this.objectId = data.hasTarget() ? idFrom(data.getTarget()) : null; this.peeledId = data.hasPeeled() ? idFrom(data.getPeeled()) : null; } public String getName() { return name; } public boolean isSymbolic() { return false; } public Ref getLeaf() { return this; } public Ref getTarget() { return this; } public ObjectId getObjectId() { return objectId; } public Ref.Storage getStorage() { return data.hasTarget() ? LOOSE : NEW; } public boolean isPeeled() { return data.getIsPeeled(); } public ObjectId getPeeledObjectId() { return peeledId; } public RefData getRefData() { return data; } } }