123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564 |
- /*
- * Copyright (C) 2017, Google Inc. 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 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 nonOverlappedUpdateIndices() throws IOException {
- ByteArrayOutputStream buf = new ByteArrayOutputStream();
- ReftableWriter writer = new ReftableWriter(buf)
- .setMinUpdateIndex(1)
- .setMaxUpdateIndex(2)
- .begin();
- writer.writeRef(ref("refs/heads/a", 1), 1);
- writer.writeRef(ref("refs/heads/b", 2), 2);
- writer.finish();
- byte[] base = buf.toByteArray();
-
- buf = new ByteArrayOutputStream();
- writer = new ReftableWriter(buf)
- .setMinUpdateIndex(3)
- .setMaxUpdateIndex(4)
- .begin();
- writer.writeRef(ref("refs/heads/a", 10), 3);
- writer.writeRef(ref("refs/heads/b", 20), 4);
- writer.finish();
- byte[] delta = buf.toByteArray();
-
- MergedReftable mr = merge(base, delta);
- assertEquals(1, mr.minUpdateIndex());
- assertEquals(4, mr.maxUpdateIndex());
-
- try (RefCursor rc = mr.allRefs()) {
- assertTrue(rc.next());
- assertEquals("refs/heads/a", rc.getRef().getName());
- assertEquals(id(10), rc.getRef().getObjectId());
- assertEquals(3, rc.getRef().getUpdateIndex());
-
- assertTrue(rc.next());
- assertEquals("refs/heads/b", rc.getRef().getName());
- assertEquals(id(20), rc.getRef().getObjectId());
- assertEquals(4, rc.getRef().getUpdateIndex());
- }
- }
-
- @Test
- public void overlappedUpdateIndices() 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/b", 2), 3);
- writer.finish();
- byte[] base = buf.toByteArray();
-
- buf = new ByteArrayOutputStream();
- writer = new ReftableWriter(buf)
- .setMinUpdateIndex(2)
- .setMaxUpdateIndex(4)
- .begin();
- writer.writeRef(ref("refs/heads/a", 10), 2);
- writer.writeRef(ref("refs/heads/b", 20), 4);
- writer.finish();
- byte[] delta = buf.toByteArray();
-
- MergedReftable mr = merge(base, delta);
- assertEquals(1, mr.minUpdateIndex());
- assertEquals(4, mr.maxUpdateIndex());
-
- try (RefCursor rc = mr.allRefs()) {
- assertTrue(rc.next());
- assertEquals("refs/heads/a", rc.getRef().getName());
- assertEquals(id(10), rc.getRef().getObjectId());
- assertEquals(2, rc.getRef().getUpdateIndex());
-
- assertTrue(rc.next());
- assertEquals("refs/heads/b", rc.getRef().getName());
- assertEquals(id(20), rc.getRef().getObjectId());
- assertEquals(4, rc.getRef().getUpdateIndex());
- }
- }
-
- @Test
- public void enclosedUpdateIndices() throws IOException {
- ByteArrayOutputStream buf = new ByteArrayOutputStream();
- ReftableWriter writer = new ReftableWriter(buf)
- .setMinUpdateIndex(1)
- .setMaxUpdateIndex(4)
- .begin();
- writer.writeRef(ref("refs/heads/a", 1), 1);
- writer.writeRef(ref("refs/heads/b", 20), 4);
- writer.finish();
- byte[] base = buf.toByteArray();
-
- buf = new ByteArrayOutputStream();
- writer = new ReftableWriter(buf)
- .setMinUpdateIndex(2)
- .setMaxUpdateIndex(3)
- .begin();
- writer.writeRef(ref("refs/heads/a", 10), 2);
- writer.writeRef(ref("refs/heads/b", 2), 3);
- writer.finish();
- byte[] delta = buf.toByteArray();
-
- MergedReftable mr = merge(base, delta);
- assertEquals(1, mr.minUpdateIndex());
- assertEquals(4, mr.maxUpdateIndex());
-
- try (RefCursor rc = mr.allRefs()) {
- assertTrue(rc.next());
- assertEquals("refs/heads/a", rc.getRef().getName());
- assertEquals(id(10), rc.getRef().getObjectId());
- assertEquals(2, rc.getRef().getUpdateIndex());
-
- assertTrue(rc.next());
- assertEquals("refs/heads/b", rc.getRef().getName());
- assertEquals(id(20), rc.getRef().getObjectId());
- assertEquals(4, 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;
- }
- }
|