- /*
- * Copyright (C) 2017, 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.internal.storage.reftable;
-
- import static org.eclipse.jgit.lib.Constants.HEAD;
- import static org.eclipse.jgit.lib.Constants.MASTER;
- import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
- import static org.eclipse.jgit.lib.Constants.R_HEADS;
- import static org.eclipse.jgit.lib.Ref.Storage.NEW;
- import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
- import static org.junit.Assert.assertEquals;
- import static org.junit.Assert.assertFalse;
- import static org.junit.Assert.assertNull;
- import static org.junit.Assert.assertTrue;
-
- import java.io.ByteArrayOutputStream;
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Collection;
- import java.util.Collections;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
-
- import org.eclipse.jgit.internal.storage.io.BlockSource;
- import org.eclipse.jgit.lib.ObjectId;
- import org.eclipse.jgit.lib.ObjectIdRef;
- import org.eclipse.jgit.lib.Ref;
- import org.eclipse.jgit.lib.RefComparator;
- import org.eclipse.jgit.lib.SymbolicRef;
- import org.junit.Test;
-
- public class MergedReftableTest {
- @Test
- public void noTables() throws IOException {
- MergedReftable mr = merge(new byte[0][]);
- try (RefCursor rc = mr.allRefs()) {
- assertFalse(rc.next());
- }
- try (RefCursor rc = mr.seekRef(HEAD)) {
- assertFalse(rc.next());
- }
- try (RefCursor rc = mr.seekRefsWithPrefix(R_HEADS)) {
- assertFalse(rc.next());
- }
- }
-
- @Test
- public void oneEmptyTable() throws IOException {
- MergedReftable mr = merge(write());
- try (RefCursor rc = mr.allRefs()) {
- assertFalse(rc.next());
- }
- try (RefCursor rc = mr.seekRef(HEAD)) {
- assertFalse(rc.next());
- }
- try (RefCursor rc = mr.seekRefsWithPrefix(R_HEADS)) {
- assertFalse(rc.next());
- }
- }
-
- @Test
- public void twoEmptyTables() throws IOException {
- MergedReftable mr = merge(write(), write());
- try (RefCursor rc = mr.allRefs()) {
- assertFalse(rc.next());
- }
- try (RefCursor rc = mr.seekRef(HEAD)) {
- assertFalse(rc.next());
- }
- try (RefCursor rc = mr.seekRefsWithPrefix(R_HEADS)) {
- assertFalse(rc.next());
- }
- }
-
- @SuppressWarnings("boxing")
- @Test
- public void oneTableScan() throws IOException {
- List<Ref> refs = new ArrayList<>();
- for (int i = 1; i <= 567; i++) {
- refs.add(ref(String.format("refs/heads/%03d", i), i));
- }
-
- MergedReftable mr = merge(write(refs));
- try (RefCursor rc = mr.allRefs()) {
- for (Ref exp : refs) {
- assertTrue("has " + exp.getName(), rc.next());
- Ref act = rc.getRef();
- assertEquals(exp.getName(), act.getName());
- assertEquals(exp.getObjectId(), act.getObjectId());
- assertEquals(1, act.getUpdateIndex());
- }
- assertFalse(rc.next());
- }
- }
-
- @Test
- public void deleteIsHidden() throws IOException {
- List<Ref> delta1 = Arrays.asList(
- ref("refs/heads/apple", 1),
- ref("refs/heads/master", 2));
- List<Ref> delta2 = Arrays.asList(delete("refs/heads/apple"));
-
- MergedReftable mr = merge(write(delta1), write(delta2));
- try (RefCursor rc = mr.allRefs()) {
- assertTrue(rc.next());
- assertEquals("refs/heads/master", rc.getRef().getName());
- assertEquals(id(2), rc.getRef().getObjectId());
- assertEquals(1, rc.getRef().getUpdateIndex());
- assertFalse(rc.next());
- }
- }
-
- @Test
- public void twoTableSeek() throws IOException {
- List<Ref> delta1 = Arrays.asList(
- ref("refs/heads/apple", 1),
- ref("refs/heads/master", 2));
- List<Ref> delta2 = Arrays.asList(ref("refs/heads/banana", 3));
-
- MergedReftable mr = merge(write(delta1), write(delta2));
- try (RefCursor rc = mr.seekRef("refs/heads/master")) {
- assertTrue(rc.next());
- assertEquals("refs/heads/master", rc.getRef().getName());
- assertEquals(id(2), rc.getRef().getObjectId());
- assertFalse(rc.next());
- assertEquals(1, rc.getRef().getUpdateIndex());
- }
- }
-
- @Test
- public void twoTableById() throws IOException {
- List<Ref> delta1 = Arrays.asList(
- ref("refs/heads/apple", 1),
- ref("refs/heads/master", 2));
- List<Ref> delta2 = Arrays.asList(ref("refs/heads/banana", 3));
-
- MergedReftable mr = merge(write(delta1), write(delta2));
- try (RefCursor rc = mr.byObjectId(id(2))) {
- assertTrue(rc.next());
- assertEquals("refs/heads/master", rc.getRef().getName());
- assertEquals(id(2), rc.getRef().getObjectId());
- assertEquals(1, rc.getRef().getUpdateIndex());
- assertFalse(rc.next());
- }
- }
-
- @Test
- public void tableByIDDeletion() throws IOException {
- List<Ref> delta1 = Arrays.asList(
- ref("refs/heads/apple", 1),
- ref("refs/heads/master", 2));
- List<Ref> delta2 = Arrays.asList(ref("refs/heads/master", 3));
-
- MergedReftable mr = merge(write(delta1), write(delta2));
- try (RefCursor rc = mr.byObjectId(id(2))) {
- assertFalse(rc.next());
- }
- }
-
- @SuppressWarnings("boxing")
- @Test
- public void fourTableScan() throws IOException {
- List<Ref> base = new ArrayList<>();
- for (int i = 1; i <= 567; i++) {
- base.add(ref(String.format("refs/heads/%03d", i), i));
- }
-
- List<Ref> delta1 = Arrays.asList(
- ref("refs/heads/next", 4),
- ref(String.format("refs/heads/%03d", 55), 4096));
- List<Ref> delta2 = Arrays.asList(
- delete("refs/heads/next"),
- ref(String.format("refs/heads/%03d", 55), 8192));
- List<Ref> delta3 = Arrays.asList(
- ref("refs/heads/master", 4242),
- ref(String.format("refs/heads/%03d", 42), 5120),
- ref(String.format("refs/heads/%03d", 98), 6120));
-
- List<Ref> expected = merge(base, delta1, delta2, delta3);
- MergedReftable mr = merge(
- write(base),
- write(delta1),
- write(delta2),
- write(delta3));
- try (RefCursor rc = mr.allRefs()) {
- for (Ref exp : expected) {
- assertTrue("has " + exp.getName(), rc.next());
- Ref act = rc.getRef();
- assertEquals(exp.getName(), act.getName());
- assertEquals(exp.getObjectId(), act.getObjectId());
- assertEquals(1, rc.getRef().getUpdateIndex());
- }
- assertFalse(rc.next());
- }
- }
-
- @Test
- public void scanIncludeDeletes() throws IOException {
- List<Ref> delta1 = Arrays.asList(ref("refs/heads/next", 4));
- List<Ref> delta2 = Arrays.asList(delete("refs/heads/next"));
- List<Ref> delta3 = Arrays.asList(ref("refs/heads/master", 8));
-
- MergedReftable mr = merge(write(delta1), write(delta2), write(delta3));
- mr.setIncludeDeletes(true);
- try (RefCursor rc = mr.allRefs()) {
- assertTrue(rc.next());
- Ref r = rc.getRef();
- assertEquals("refs/heads/master", r.getName());
- assertEquals(id(8), r.getObjectId());
- assertEquals(1, rc.getRef().getUpdateIndex());
-
- assertTrue(rc.next());
- r = rc.getRef();
- assertEquals("refs/heads/next", r.getName());
- assertEquals(NEW, r.getStorage());
- assertNull(r.getObjectId());
- assertEquals(1, rc.getRef().getUpdateIndex());
-
- assertFalse(rc.next());
- }
- }
-
- @SuppressWarnings("boxing")
- @Test
- public void oneTableSeek() throws IOException {
- List<Ref> refs = new ArrayList<>();
- for (int i = 1; i <= 567; i++) {
- refs.add(ref(String.format("refs/heads/%03d", i), i));
- }
-
- MergedReftable mr = merge(write(refs));
- for (Ref exp : refs) {
- try (RefCursor rc = mr.seekRef(exp.getName())) {
- assertTrue("has " + exp.getName(), rc.next());
- Ref act = rc.getRef();
- assertEquals(exp.getName(), act.getName());
- assertEquals(exp.getObjectId(), act.getObjectId());
- assertEquals(1, act.getUpdateIndex());
- assertFalse(rc.next());
- }
- }
- }
-
- @Test
- public void missedUpdate() throws IOException {
- ByteArrayOutputStream buf = new ByteArrayOutputStream();
- ReftableWriter writer = new ReftableWriter(buf)
- .setMinUpdateIndex(1)
- .setMaxUpdateIndex(3)
- .begin();
- writer.writeRef(ref("refs/heads/a", 1), 1);
- writer.writeRef(ref("refs/heads/c", 3), 3);
- writer.finish();
- byte[] base = buf.toByteArray();
-
- byte[] delta = write(Arrays.asList(
- ref("refs/heads/b", 2),
- ref("refs/heads/c", 4)),
- 2);
- MergedReftable mr = merge(base, delta);
- try (RefCursor rc = mr.allRefs()) {
- assertTrue(rc.next());
- assertEquals("refs/heads/a", rc.getRef().getName());
- assertEquals(id(1), rc.getRef().getObjectId());
- assertEquals(1, rc.getRef().getUpdateIndex());
-
- assertTrue(rc.next());
- assertEquals("refs/heads/b", rc.getRef().getName());
- assertEquals(id(2), rc.getRef().getObjectId());
- assertEquals(2, rc.getRef().getUpdateIndex());
-
- assertTrue(rc.next());
- assertEquals("refs/heads/c", rc.getRef().getName());
- assertEquals(id(3), rc.getRef().getObjectId());
- assertEquals(3, rc.getRef().getUpdateIndex());
- }
- }
-
- @Test
- public void compaction() throws IOException {
- List<Ref> delta1 = Arrays.asList(
- ref("refs/heads/next", 4),
- ref("refs/heads/master", 1));
- List<Ref> delta2 = Arrays.asList(delete("refs/heads/next"));
- List<Ref> delta3 = Arrays.asList(ref("refs/heads/master", 8));
-
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- ReftableCompactor compactor = new ReftableCompactor(out);
- compactor.addAll(Arrays.asList(
- read(write(delta1)),
- read(write(delta2)),
- read(write(delta3))));
- compactor.compact();
- byte[] table = out.toByteArray();
-
- ReftableReader reader = read(table);
- try (RefCursor rc = reader.allRefs()) {
- assertTrue(rc.next());
- Ref r = rc.getRef();
- assertEquals("refs/heads/master", r.getName());
- assertEquals(id(8), r.getObjectId());
- assertFalse(rc.next());
- }
- }
-
- @Test
- public void versioningSymbolicReftargetMoves() throws IOException {
- Ref master = ref(MASTER, 100);
-
- List<Ref> delta1 = Arrays.asList(master, sym(HEAD, MASTER));
- List<Ref> delta2 = Arrays.asList(ref(MASTER, 200));
-
- MergedReftable mr = merge(write(delta1, 1), write(delta2, 2));
- Ref head = mr.exactRef(HEAD);
- assertEquals(head.getUpdateIndex(), 1);
-
- Ref masterRef = mr.exactRef(MASTER);
- assertEquals(masterRef.getUpdateIndex(), 2);
- }
-
- @Test
- public void versioningSymbolicRefMoves() throws IOException {
- Ref branchX = ref("refs/heads/branchX", 200);
-
- List<Ref> delta1 = Arrays.asList(ref(MASTER, 100), branchX,
- sym(HEAD, MASTER));
- List<Ref> delta2 = Arrays.asList(sym(HEAD, "refs/heads/branchX"));
- List<Ref> delta3 = Arrays.asList(sym(HEAD, MASTER));
-
- MergedReftable mr = merge(write(delta1, 1), write(delta2, 2),
- write(delta3, 3));
- Ref head = mr.exactRef(HEAD);
- assertEquals(head.getUpdateIndex(), 3);
-
- Ref masterRef = mr.exactRef(MASTER);
- assertEquals(masterRef.getUpdateIndex(), 1);
-
- Ref branchRef = mr.exactRef(MASTER);
- assertEquals(branchRef.getUpdateIndex(), 1);
- }
-
- @Test
- public void versioningResolveRef() throws IOException {
- List<Ref> delta1 = Arrays.asList(sym(HEAD, "refs/heads/tmp"),
- sym("refs/heads/tmp", MASTER), ref(MASTER, 100));
- List<Ref> delta2 = Arrays.asList(ref(MASTER, 200));
- List<Ref> delta3 = Arrays.asList(ref(MASTER, 300));
-
- MergedReftable mr = merge(write(delta1, 1), write(delta2, 2),
- write(delta3, 3));
- Ref head = mr.exactRef(HEAD);
- Ref resolvedHead = mr.resolve(head);
- assertEquals(resolvedHead.getObjectId(), id(300));
- assertEquals("HEAD has not moved", resolvedHead.getUpdateIndex(), 1);
-
- Ref master = mr.exactRef(MASTER);
- Ref resolvedMaster = mr.resolve(master);
- assertEquals(resolvedMaster.getObjectId(), id(300));
- assertEquals("master also has update index",
- resolvedMaster.getUpdateIndex(), 3);
- }
-
- private static MergedReftable merge(byte[]... table) {
- List<ReftableReader> stack = new ArrayList<>(table.length);
- for (byte[] b : table) {
- stack.add(read(b));
- }
- return new MergedReftable(stack);
- }
-
- private static ReftableReader read(byte[] table) {
- return new ReftableReader(BlockSource.from(table));
- }
-
- private static Ref ref(String name, int id) {
- return new ObjectIdRef.PeeledNonTag(PACKED, name, id(id));
- }
-
- private static Ref sym(String name, String target) {
- return new SymbolicRef(name, newRef(target));
- }
-
- private static Ref newRef(String name) {
- return new ObjectIdRef.Unpeeled(NEW, name, null);
- }
-
- private static Ref delete(String name) {
- return new ObjectIdRef.Unpeeled(NEW, name, null);
- }
-
- private static ObjectId id(int i) {
- byte[] buf = new byte[OBJECT_ID_LENGTH];
- buf[0] = (byte) (i & 0xff);
- buf[1] = (byte) ((i >>> 8) & 0xff);
- buf[2] = (byte) ((i >>> 16) & 0xff);
- buf[3] = (byte) (i >>> 24);
- return ObjectId.fromRaw(buf);
- }
-
- private byte[] write(Ref... refs) throws IOException {
- return write(Arrays.asList(refs));
- }
-
- private byte[] write(Collection<Ref> refs) throws IOException {
- return write(refs, 1);
- }
-
- private byte[] write(Collection<Ref> refs, long updateIndex)
- throws IOException {
- ByteArrayOutputStream buffer = new ByteArrayOutputStream();
- new ReftableWriter(buffer)
- .setMinUpdateIndex(updateIndex)
- .setMaxUpdateIndex(updateIndex)
- .begin()
- .sortAndWriteRefs(refs)
- .finish();
- return buffer.toByteArray();
- }
-
- @SafeVarargs
- private static List<Ref> merge(List<Ref>... tables) {
- Map<String, Ref> expect = new HashMap<>();
- for (List<Ref> t : tables) {
- for (Ref r : t) {
- if (r.getStorage() == NEW && r.getObjectId() == null) {
- expect.remove(r.getName());
- } else {
- expect.put(r.getName(), r);
- }
- }
- }
-
- List<Ref> expected = new ArrayList<>(expect.values());
- Collections.sort(expected, RefComparator.INSTANCE);
- return expected;
- }
- }
|