diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java index 0e33fa6e7..bd8714747 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java @@ -66,6 +66,8 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Collectors; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.io.BlockSource; @@ -76,6 +78,7 @@ import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.ReflogEntry; import org.eclipse.jgit.lib.SymbolicRef; +import org.hamcrest.Matchers; import org.junit.Test; public class ReftableTest { @@ -522,6 +525,47 @@ public void withReflog() throws IOException { } } + @Test + public void reflogReader() throws IOException { + Ref master = ref(MASTER, 1); + Ref next = ref(NEXT, 2); + + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + ReftableWriter writer = new ReftableWriter().setMinUpdateIndex(1) + .setMaxUpdateIndex(1).begin(buffer); + + writer.writeRef(master); + writer.writeRef(next); + + PersonIdent who1 = new PersonIdent("Log", "Ger", 1500079709, -8 * 60); + writer.writeLog(MASTER, 3, who1, ObjectId.zeroId(), id(1), "1"); + PersonIdent who2 = new PersonIdent("Log", "Ger", 1500079710, -8 * 60); + writer.writeLog(MASTER, 2, who2, id(1), id(2), "2"); + PersonIdent who3 = new PersonIdent("Log", "Ger", 1500079711, -8 * 60); + writer.writeLog(MASTER, 1, who3, id(2), id(3), "3"); + + writer.finish(); + byte[] table = buffer.toByteArray(); + + ReentrantLock lock = new ReentrantLock(); + ReftableReader t = read(table); + ReftableReflogReader rlr = new ReftableReflogReader(lock, t, MASTER); + + assertEquals(rlr.getLastEntry().getWho(), who1); + List all = rlr.getReverseEntries().stream() + .map(x -> x.getWho()).collect(Collectors.toList()); + Matchers.contains(all, who3, who2, who1); + + assertEquals(rlr.getReverseEntry(1).getWho(), who2); + + List reverse2 = rlr.getReverseEntries(2); + Matchers.contains(reverse2, who3, who2); + + List more = rlr.getReverseEntries(4).stream() + .map(x -> x.getWho()).collect(Collectors.toList()); + assertEquals(all, more); + } + @Test public void reflogSeek() throws IOException { PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReflogReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReflogReader.java new file mode 100644 index 000000000..c75d3cfa5 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReflogReader.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2019, 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 org.eclipse.jgit.lib.ReflogEntry; +import org.eclipse.jgit.lib.ReflogReader; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.locks.Lock; + +/** + * Implement the ReflogReader interface for a reflog stored in reftable. + */ +public class ReftableReflogReader implements ReflogReader { + private final Lock lock; + + private final Reftable reftable; + + private final String refname; + + ReftableReflogReader(Lock lock, Reftable merged, String refname) { + this.lock = lock; + this.reftable = merged; + this.refname = refname; + } + + /** {@inheritDoc} */ + @Override + public ReflogEntry getLastEntry() throws IOException { + lock.lock(); + try { + LogCursor cursor = reftable.seekLog(refname); + return cursor.next() ? cursor.getReflogEntry() : null; + } finally { + lock.unlock(); + } + } + + /** {@inheritDoc} */ + @Override + public List getReverseEntries() throws IOException { + return getReverseEntries(Integer.MAX_VALUE); + } + + /** {@inheritDoc} */ + @Override + public ReflogEntry getReverseEntry(int number) throws IOException { + lock.lock(); + try { + LogCursor cursor = reftable.seekLog(refname); + while (true) { + if (!cursor.next() || number < 0) { + return null; + } + if (number == 0) { + return cursor.getReflogEntry(); + } + number--; + } + } finally { + lock.unlock(); + } + } + + /** {@inheritDoc} */ + @Override + public List getReverseEntries(int max) throws IOException { + lock.lock(); + try { + LogCursor cursor = reftable.seekLog(refname); + + List result = new ArrayList<>(); + while (cursor.next() && result.size() < max) { + result.add(cursor.getReflogEntry()); + } + + return result; + } finally { + lock.unlock(); + } + } +}