summaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit.test
diff options
context:
space:
mode:
authorHan-Wen Nienhuys <hanwen@google.com>2019-08-12 17:33:51 +0200
committerMatthias Sohn <matthias.sohn@sap.com>2019-11-16 23:16:01 -0800
commit38586d54d0a2da26d574fec831fce8964205db50 (patch)
treef725df7edde7b3c86d4f3dd3946018dc44672a62 /org.eclipse.jgit.test
parent7b73d5eec746f58972585ba5f6364190704a7519 (diff)
downloadjgit-38586d54d0a2da26d574fec831fce8964205db50.tar.gz
jgit-38586d54d0a2da26d574fec831fce8964205db50.zip
file: implement FileReftableDatabase
Reftable is a binary, block-based storage format for the ref-database. It provides several advantages over the traditional packed + loose storage format: * O(1) write performance, even for deletions and transactions. * atomic updates to the ref database. * O(log N) lookup and prefix scans * free from restrictions imposed by the file system: it is case-sensitive even on case-insensitive file systems, and has no inherent limitations for directory/file conflicts * prefix compression reduces space usage for repetitive ref names, such as gerrit's refs/changes/xx/xxxxx format. FileReftableDatabase is based on FileReftableStack, which does compactions inline. This is simple, and has good median performance, but every so often it will rewrite the entire ref database. For testing, a FileReftableTest (mirroring RefUpdateTest) is added to check for Reftable specific behavior. This must be done separately, as reflogs have different semantics. Add a reftable flavor of BatchRefUpdateTest. Add a FileReftableStackTest to exercise compaction. Add FileRepository#convertToReftable so existing testdata can be reused. CQ: 21007 Change-Id: I1837f268e91c6b446cb0155061727dbaccb714b8 Signed-off-by: Han-Wen Nienhuys <hanwen@google.com> Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
Diffstat (limited to 'org.eclipse.jgit.test')
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java137
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableStackTest.java203
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableTest.java553
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java58
4 files changed, 912 insertions, 39 deletions
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java
index 2d47d848f5..cb5b07a4db 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java
@@ -61,6 +61,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import java.io.File;
@@ -109,13 +110,18 @@ import org.junit.runners.Parameterized.Parameters;
@SuppressWarnings("boxing")
@RunWith(Parameterized.class)
public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
- @Parameter
+ @Parameter(0)
public boolean atomic;
- @Parameters(name = "atomic={0}")
+ @Parameter(1)
+ public boolean useReftable;
+
+ @Parameters(name = "atomic={0} reftable={1}")
public static Collection<Object[]> data() {
- return Arrays
- .asList(new Object[][] { { Boolean.FALSE }, { Boolean.TRUE } });
+ return Arrays.asList(new Object[][] { { Boolean.FALSE, Boolean.FALSE },
+ { Boolean.TRUE, Boolean.FALSE },
+ { Boolean.FALSE, Boolean.TRUE },
+ { Boolean.TRUE, Boolean.TRUE }, });
}
private Repository diskRepo;
@@ -126,7 +132,7 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
private RevCommit A;
- private RevCommit B;
+ private RevCommit B; // B descends from A.
/**
* When asserting the number of RefsChangedEvents you must account for one
@@ -148,11 +154,18 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
public void setUp() throws Exception {
super.setUp();
- diskRepo = createBareRepository();
+ FileRepository fileRepo = createBareRepository();
+ if (useReftable) {
+ fileRepo.convertToReftable(false, false);
+ }
+
+ diskRepo = fileRepo;
setLogAllRefUpdates(true);
- refdir = (RefDirectory) diskRepo.getRefDatabase();
- refdir.setRetrySleepMs(Arrays.asList(0, 0));
+ if (!useReftable) {
+ refdir = (RefDirectory) diskRepo.getRefDatabase();
+ refdir.setRetrySleepMs(Arrays.asList(0, 0));
+ }
repo = new TestRepository<>(diskRepo);
A = repo.commit().create();
@@ -171,6 +184,7 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
@Test
public void packedRefsFileIsSorted() throws IOException {
assumeTrue(atomic);
+ assumeFalse(useReftable);
for (int i = 0; i < 2; i++) {
BatchRefUpdate bu = diskRepo.getRefDatabase().newBatchUpdate();
@@ -198,8 +212,7 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
@Test
public void simpleNoForce() throws IOException {
- writeLooseRef("refs/heads/master", A);
- writeLooseRef("refs/heads/masters", B);
+ writeLooseRefs("refs/heads/master", A, "refs/heads/masters", B);
List<ReceiveCommand> cmds = Arrays.asList(
new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
@@ -220,8 +233,7 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
@Test
public void simpleForce() throws IOException {
- writeLooseRef("refs/heads/master", A);
- writeLooseRef("refs/heads/masters", B);
+ writeLooseRefs("refs/heads/master", A, "refs/heads/masters", B);
List<ReceiveCommand> cmds = Arrays.asList(
new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
@@ -231,7 +243,7 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
assertResults(cmds, OK, OK);
assertRefs("refs/heads/master", B, "refs/heads/masters", A);
- assertEquals(atomic ? 2 : 3, refsChangedEvents);
+ assertEquals(batchesRefUpdates() ? 2 : 3, refsChangedEvents);
}
@Test
@@ -258,8 +270,7 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
@Test
public void fileDirectoryConflict() throws IOException {
- writeLooseRef("refs/heads/master", A);
- writeLooseRef("refs/heads/masters", B);
+ writeLooseRefs("refs/heads/master", A, "refs/heads/masters", B);
List<ReceiveCommand> cmds = Arrays.asList(
new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
@@ -269,16 +280,14 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
if (atomic) {
// Atomic update sees that master and master/x are conflicting, then
- // marks
- // the first one in the list as LOCK_FAILURE and aborts the rest.
+ // marks the first one in the list as LOCK_FAILURE and aborts the rest.
assertResults(cmds, LOCK_FAILURE, TRANSACTION_ABORTED,
TRANSACTION_ABORTED);
assertRefs("refs/heads/master", A, "refs/heads/masters", B);
assertEquals(1, refsChangedEvents);
} else {
// Non-atomic updates are applied in order: master succeeds, then
- // master/x
- // fails due to conflict.
+ // master/x fails due to conflict.
assertResults(cmds, OK, LOCK_FAILURE, LOCK_FAILURE);
assertRefs("refs/heads/master", B, "refs/heads/masters", B);
assertEquals(2, refsChangedEvents);
@@ -287,8 +296,7 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
@Test
public void conflictThanksToDelete() throws IOException {
- writeLooseRef("refs/heads/master", A);
- writeLooseRef("refs/heads/masters", B);
+ writeLooseRefs("refs/heads/master", A, "refs/heads/masters", B);
List<ReceiveCommand> cmds = Arrays.asList(
new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
@@ -300,7 +308,7 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
assertRefs("refs/heads/master", B, "refs/heads/masters/x", A);
if (atomic) {
assertEquals(2, refsChangedEvents);
- } else {
+ } else if (!useReftable) {
// The non-atomic case actually produces 5 events, but that's an
// implementation detail. We expect at least 4 events, one for the
// initial read due to writeLooseRef(), and then one for each
@@ -427,7 +435,7 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
assertResults(cmds, OK, OK);
assertRefs("refs/heads/master", B, "refs/heads/branch", B);
- assertEquals(atomic ? 2 : 3, refsChangedEvents);
+ assertEquals(batchesRefUpdates() ? 2 : 3, refsChangedEvents);
assertReflogUnchanged(oldLogs, "refs/heads/master");
assertReflogUnchanged(oldLogs, "refs/heads/branch");
}
@@ -448,7 +456,7 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
assertResults(cmds, OK, OK);
assertRefs("refs/heads/master", B, "refs/heads/branch1", B,
"refs/heads/branch2", A);
- assertEquals(atomic ? 3 : 4, refsChangedEvents);
+ assertEquals(batchesRefUpdates() ? 3 : 4, refsChangedEvents);
assertReflogEquals(reflog(A, B, new PersonIdent(diskRepo), "a reflog"),
getLastReflog("refs/heads/master"));
assertReflogEquals(
@@ -473,7 +481,7 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
assertResults(cmds, OK, OK, OK);
assertRefs("refs/heads/master", B, "refs/heads/branch1", A,
"refs/heads/branch2", A);
- assertEquals(atomic ? 3 : 5, refsChangedEvents);
+ assertEquals(batchesRefUpdates() ? 3 : 5, refsChangedEvents);
assertReflogEquals(
// Always forced; setAllowNonFastForwards(true) bypasses the
// check.
@@ -514,7 +522,7 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
assertResults(cmds, OK, OK);
assertRefs("refs/heads/master", B, "refs/heads/branch", A);
- assertEquals(atomic ? 2 : 3, refsChangedEvents);
+ assertEquals(batchesRefUpdates() ? 2 : 3, refsChangedEvents);
assertReflogEquals(
reflog(A, B, new PersonIdent(diskRepo),
"a reflog: fast-forward"),
@@ -538,7 +546,7 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
.setRefLogIdent(ident));
assertResults(cmds, OK, OK);
- assertEquals(atomic ? 2 : 3, refsChangedEvents);
+ assertEquals(batchesRefUpdates() ? 2 : 3, refsChangedEvents);
assertRefs("refs/heads/master", B, "refs/heads/branch", B);
assertReflogEquals(reflog(A, B, ident, "a reflog"),
getLastReflog("refs/heads/master"), true);
@@ -560,8 +568,15 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
assertResults(cmds, OK, OK);
assertRefs("refs/heads/branch", B);
- assertEquals(atomic ? 3 : 4, refsChangedEvents);
- assertNull(getLastReflog("refs/heads/master"));
+ assertEquals(batchesRefUpdates() ? 3 : 4, refsChangedEvents);
+ if (useReftable) {
+ // reftable retains reflog entries for deleted branches.
+ assertReflogEquals(
+ reflog(A, zeroId(), new PersonIdent(diskRepo), "a reflog"),
+ getLastReflog("refs/heads/master"));
+ } else {
+ assertNull(getLastReflog("refs/heads/master"));
+ }
assertReflogEquals(reflog(A, B, new PersonIdent(diskRepo), "a reflog"),
getLastReflog("refs/heads/branch"));
}
@@ -577,8 +592,11 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
assertResults(cmds, OK, OK);
assertRefs("refs/heads/master/x", A);
- assertEquals(atomic ? 2 : 3, refsChangedEvents);
- assertNull(getLastReflog("refs/heads/master"));
+ assertEquals(batchesRefUpdates() ? 2 : 3, refsChangedEvents);
+ if (!useReftable) {
+ // reftable retains reflog entries for deleted branches.
+ assertNull(getLastReflog("refs/heads/master"));
+ }
assertReflogEquals(
reflog(zeroId(), A, new PersonIdent(diskRepo), "a reflog"),
getLastReflog("refs/heads/master/x"));
@@ -624,7 +642,7 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
.setRefLogMessage("a reflog", true));
assertResults(cmds, OK, OK);
- assertEquals(atomic ? 2 : 3, refsChangedEvents);
+ assertEquals(batchesRefUpdates() ? 2 : 3, refsChangedEvents);
assertReflogEquals(reflog(A, B, ident, "custom log"),
getLastReflog("refs/heads/master"), true);
assertReflogEquals(reflog(zeroId(), B, ident, "a reflog: created"),
@@ -645,7 +663,7 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", true));
assertResults(cmds, OK, OK);
- assertEquals(atomic ? 2 : 3, refsChangedEvents);
+ assertEquals(batchesRefUpdates() ? 2 : 3, refsChangedEvents);
assertReflogUnchanged(oldLogs, "refs/heads/master");
assertReflogEquals(
reflog(zeroId(), B, new PersonIdent(diskRepo),
@@ -655,6 +673,7 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
@Test
public void refLogNotWrittenWithoutConfigOption() throws Exception {
+ assumeFalse(useReftable);
setLogAllRefUpdates(false);
writeRef("refs/heads/master", A);
@@ -674,6 +693,7 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
@Test
public void forceRefLogInUpdate() throws Exception {
+ assumeFalse(useReftable);
setLogAllRefUpdates(false);
writeRef("refs/heads/master", A);
assertTrue(getLastReflogs("refs/heads/master", "refs/heads/branch")
@@ -695,6 +715,7 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
@Test
public void forceRefLogInCommand() throws Exception {
+ assumeFalse(useReftable);
setLogAllRefUpdates(false);
writeRef("refs/heads/master", A);
@@ -717,6 +738,8 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
@Test
public void packedRefsLockFailure() throws Exception {
+ assumeFalse(useReftable);
+
writeLooseRef("refs/heads/master", A);
List<ReceiveCommand> cmds = Arrays.asList(
@@ -748,6 +771,8 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
@Test
public void oneRefLockFailure() throws Exception {
+ assumeFalse(useReftable);
+
writeLooseRef("refs/heads/master", A);
List<ReceiveCommand> cmds = Arrays.asList(
@@ -778,6 +803,7 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
@Test
public void singleRefUpdateDoesNotRequirePackedRefsLock() throws Exception {
+ assumeFalse(useReftable);
writeLooseRef("refs/heads/master", A);
List<ReceiveCommand> cmds = Arrays
@@ -799,6 +825,7 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
@Test
public void atomicUpdateRespectsInProcessLock() throws Exception {
assumeTrue(atomic);
+ assumeFalse(useReftable);
writeLooseRef("refs/heads/master", A);
@@ -857,7 +884,38 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
}
private void writeLooseRef(String name, AnyObjectId id) throws IOException {
- write(new File(diskRepo.getDirectory(), name), id.name() + "\n");
+ if (useReftable) {
+ writeRef(name, id);
+ } else {
+ write(new File(diskRepo.getDirectory(), name), id.name() + "\n");
+ }
+ }
+
+ private void writeLooseRefs(String name1, AnyObjectId id1, String name2,
+ AnyObjectId id2) throws IOException {
+ if (useReftable) {
+ BatchRefUpdate bru = diskRepo.getRefDatabase().newBatchUpdate();
+
+ Ref r1 = diskRepo.exactRef(name1);
+ ReceiveCommand c1 = new ReceiveCommand(
+ r1 != null ? r1.getObjectId() : ObjectId.zeroId(),
+ id1.toObjectId(), name1, r1 == null ? CREATE : UPDATE);
+
+ Ref r2 = diskRepo.exactRef(name2);
+ ReceiveCommand c2 = new ReceiveCommand(
+ r2 != null ? r2.getObjectId() : ObjectId.zeroId(),
+ id2.toObjectId(), name2, r2 == null ? CREATE : UPDATE);
+
+ bru.addCommand(c1, c2);
+ try (RevWalk rw = new RevWalk(diskRepo)) {
+ bru.execute(rw, NullProgressMonitor.INSTANCE);
+ }
+ assertEquals(c2.getResult(), ReceiveCommand.Result.OK);
+ assertEquals(c1.getResult(), ReceiveCommand.Result.OK);
+ } else {
+ writeLooseRef(name1, id1);
+ writeLooseRef(name2, id2);
+ }
}
private void writeRef(String name, AnyObjectId id) throws IOException {
@@ -876,7 +934,7 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
}
private BatchRefUpdate newBatchUpdate(List<ReceiveCommand> cmds) {
- BatchRefUpdate u = refdir.newBatchUpdate();
+ BatchRefUpdate u = diskRepo.getRefDatabase().newBatchUpdate();
if (atomic) {
assertTrue(u.isAtomic());
} else {
@@ -909,7 +967,8 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
expected.put((String) args[i], (AnyObjectId) args[i + 1]);
}
- Map<String, Ref> refs = refdir.getRefs(RefDatabase.ALL);
+ Map<String, Ref> refs = diskRepo.getRefDatabase()
+ .getRefs(RefDatabase.ALL);
Ref actualHead = refs.remove(Constants.HEAD);
if (actualHead != null) {
String actualLeafName = actualHead.getLeaf().getName();
@@ -958,7 +1017,7 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
ReceiveCommand c = cmds.get(i);
Result r = expected[i];
assertTrue(String.format(
- "result of command (%d) should be %s: %s %s%s",
+ "result of command (%d) should be %s, got %s %s%s",
Integer.valueOf(i), r, c, c.getResult(),
c.getMessage() != null ? " (" + c.getMessage() + ")" : ""),
r.p.test(c));
@@ -1048,4 +1107,8 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
}
};
}
+
+ private boolean batchesRefUpdates() {
+ return atomic || useReftable;
+ }
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableStackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableStackTest.java
new file mode 100644
index 0000000000..a2710e1534
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableStackTest.java
@@ -0,0 +1,203 @@
+/*
+ * 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.file;
+
+import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.eclipse.jgit.internal.storage.file.FileReftableStack.Segment;
+import org.eclipse.jgit.internal.storage.reftable.MergedReftable;
+import org.eclipse.jgit.internal.storage.reftable.RefCursor;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.util.FileUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class FileReftableStackTest {
+
+ private static Ref newRef(String name, ObjectId id) {
+ return new ObjectIdRef.PeeledNonTag(PACKED, name, id);
+ }
+
+ private File reftableDir;
+
+ @Before
+ public void setup() throws Exception {
+ reftableDir = FileUtils.createTempDir("rtstack", "", null);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (reftableDir != null) {
+ FileUtils.delete(reftableDir, FileUtils.RECURSIVE);
+ }
+ }
+
+ void writeBranches(FileReftableStack stack, String template, int start,
+ int N) throws IOException {
+ for (int i = 0; i < N; i++) {
+ while (true) {
+ final long next = stack.getMergedReftable().maxUpdateIndex()
+ + 1;
+
+ String name = String.format(template,
+ Integer.valueOf(start + i));
+ Ref r = newRef(name, ObjectId.zeroId());
+ boolean ok = stack.addReftable(rw -> {
+ rw.setMinUpdateIndex(next).setMaxUpdateIndex(next).begin()
+ .writeRef(r);
+ });
+ if (ok) {
+ break;
+ }
+ }
+ }
+ }
+
+ public void testCompaction(int N) throws Exception {
+ try (FileReftableStack stack = new FileReftableStack(
+ new File(reftableDir, "refs"), reftableDir, null,
+ () -> new Config())) {
+ writeBranches(stack, "refs/heads/branch%d", 0, N);
+ MergedReftable table = stack.getMergedReftable();
+ for (int i = 1; i < N; i++) {
+ String name = String.format("refs/heads/branch%d",
+ Integer.valueOf(i));
+ RefCursor c = table.seekRef(name);
+ assertTrue(c.next());
+ assertEquals(ObjectId.zeroId(), c.getRef().getObjectId());
+ }
+
+ List<String> files = Arrays.asList(reftableDir.listFiles()).stream()
+ .map(File::getName).collect(Collectors.toList());
+ Collections.sort(files);
+
+ assertTrue(files.size() < 20);
+
+ FileReftableStack.CompactionStats stats = stack.getStats();
+ assertEquals(0, stats.failed);
+ assertTrue(stats.attempted < N);
+ assertTrue(stats.refCount < FileReftableStack.log(N) * N);
+ }
+ }
+
+ @Test
+ public void testCompaction9() throws Exception {
+ testCompaction(9);
+ }
+
+ @Test
+ public void testCompaction1024() throws Exception {
+ testCompaction(1024);
+ }
+
+ @Rule
+ public final ExpectedException thrown = ExpectedException.none();
+
+ @SuppressWarnings({ "resource", "unused" })
+ @Test
+ public void missingReftable() throws Exception {
+ try (FileReftableStack stack = new FileReftableStack(
+ new File(reftableDir, "refs"), reftableDir, null,
+ () -> new Config())) {
+ outer: for (int i = 0; i < 10; i++) {
+ final long next = stack.getMergedReftable().maxUpdateIndex()
+ + 1;
+ String name = String.format("branch%d", Integer.valueOf(i));
+ Ref r = newRef(name, ObjectId.zeroId());
+ boolean ok = stack.addReftable(rw -> {
+ rw.setMinUpdateIndex(next).setMaxUpdateIndex(next).begin()
+ .writeRef(r);
+ });
+ assertTrue(ok);
+
+ List<File> files = Arrays.asList(reftableDir.listFiles());
+ for (int j = 0; j < files.size(); j++) {
+ File f = files.get(j);
+ if (f.getName().endsWith(".ref")) {
+ assertTrue(f.delete());
+ break outer;
+ }
+ }
+ }
+ }
+ thrown.expect(FileNotFoundException.class);
+ new FileReftableStack(new File(reftableDir, "refs"), reftableDir, null,
+ () -> new Config());
+ }
+
+ @Test
+ public void testSegments() {
+ long in[] = { 1024, 1024, 1536, 100, 64, 50, 25, 24 };
+ List<Segment> got = FileReftableStack.segmentSizes(in);
+ Segment want[] = { new Segment(0, 3, 10, 3584),
+ new Segment(3, 5, 6, 164), new Segment(5, 6, 5, 50),
+ new Segment(6, 8, 4, 49), };
+ assertEquals(got.size(), want.length);
+ for (int i = 0; i < want.length; i++) {
+ assertTrue(want[i].equals(got.get(i)));
+ }
+ }
+
+ @Test
+ public void testLog2() throws Exception {
+ assertEquals(10, FileReftableStack.log(1024));
+ assertEquals(10, FileReftableStack.log(1025));
+ assertEquals(10, FileReftableStack.log(2047));
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableTest.java
new file mode 100644
index 0000000000..bb68ef185a
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableTest.java
@@ -0,0 +1,553 @@
+/*
+ * Copyright (C) 2019, 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.file;
+
+import static org.eclipse.jgit.lib.RefUpdate.Result.FAST_FORWARD;
+import static org.eclipse.jgit.lib.RefUpdate.Result.FORCED;
+import static org.eclipse.jgit.lib.RefUpdate.Result.IO_FAILURE;
+import static org.eclipse.jgit.lib.RefUpdate.Result.LOCK_FAILURE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefRename;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.RefUpdate.Result;
+import org.eclipse.jgit.lib.ReflogEntry;
+import org.eclipse.jgit.lib.ReflogReader;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.junit.Test;
+
+public class FileReftableTest extends SampleDataRepositoryTestCase {
+ String bCommit;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ Ref b = db.exactRef("refs/heads/b");
+ bCommit = b.getObjectId().getName();
+ db.convertToReftable(false, false);
+ }
+
+ @SuppressWarnings("boxing")
+ @Test
+ public void testRacyReload() throws Exception {
+ ObjectId id = db.resolve("master");
+ int retry = 0;
+ try (FileRepository repo1 = new FileRepository(db.getDirectory());
+ FileRepository repo2 = new FileRepository(db.getDirectory())) {
+ FileRepository repos[] = { repo1, repo2 };
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 2; j++) {
+ FileRepository repo = repos[j];
+ RefUpdate u = repo.getRefDatabase().newUpdate(
+ String.format("branch%d", i * 10 + j), false);
+
+ u.setNewObjectId(id);
+ RefUpdate.Result r = u.update();
+ if (!r.equals(Result.NEW)) {
+ retry++;
+ u = repo.getRefDatabase().newUpdate(
+ String.format("branch%d", i * 10 + j), false);
+
+ u.setNewObjectId(id);
+ r = u.update();
+ assertEquals(r, Result.NEW);
+ }
+ }
+ }
+
+ // only the first one succeeds
+ assertEquals(retry, 19);
+ }
+ }
+
+ @Test
+ public void testCompactFully() throws Exception {
+ ObjectId c1 = db.resolve("master^^");
+ ObjectId c2 = db.resolve("master^");
+ for (int i = 0; i < 5; i++) {
+ RefUpdate u = db.updateRef("refs/heads/master");
+ u.setForceUpdate(true);
+ u.setNewObjectId((i%2) == 0 ? c1 : c2);
+ assertEquals(u.update(), FORCED);
+ }
+
+ File tableDir = new File(db.getDirectory(), Constants.REFTABLE);
+ assertTrue(tableDir.listFiles().length > 1);
+ ((FileReftableDatabase)db.getRefDatabase()).compactFully();
+ assertEquals(tableDir.listFiles().length,1);
+ }
+
+ @Test
+ public void testConvert() throws Exception {
+ Ref h = db.exactRef("HEAD");
+ assertTrue(h.isSymbolic());
+ assertEquals("refs/heads/master", h.getTarget().getName());
+
+ Ref b = db.exactRef("refs/heads/b");
+ assertFalse(b.isSymbolic());
+ assertTrue(b.isPeeled());
+ assertEquals(bCommit, b.getObjectId().name());
+ }
+
+ @Test
+ public void testConvertToRefdir() throws Exception {
+ db.convertToPackedRefs(false);
+ assertTrue(db.getRefDatabase() instanceof RefDirectory);
+ Ref h = db.exactRef("HEAD");
+ assertTrue(h.isSymbolic());
+ assertEquals("refs/heads/master", h.getTarget().getName());
+
+ Ref b = db.exactRef("refs/heads/b");
+ assertFalse(b.isSymbolic());
+ assertTrue(b.isPeeled());
+ assertEquals(bCommit, b.getObjectId().name());
+ }
+
+ @Test
+ public void testBatchrefUpdate() throws Exception {
+ ObjectId cur = db.resolve("master");
+ ObjectId prev = db.resolve("master^");
+
+ PersonIdent person = new PersonIdent("name", "mail@example.com");
+ ReceiveCommand rc1 = new ReceiveCommand(ObjectId.zeroId(), cur, "refs/heads/batch1");
+ ReceiveCommand rc2 = new ReceiveCommand(ObjectId.zeroId(), prev, "refs/heads/batch2");
+ String msg = "message";
+ try (RevWalk rw = new RevWalk(db)) {
+ db.getRefDatabase().newBatchUpdate()
+ .addCommand(rc1, rc2)
+ .setAtomic(true)
+ .setRefLogIdent(person)
+ .setRefLogMessage(msg, false)
+ .execute(rw, NullProgressMonitor.INSTANCE);
+ }
+
+ assertEquals(rc1.getResult(), ReceiveCommand.Result.OK);
+ assertEquals(rc2.getResult(), ReceiveCommand.Result.OK);
+
+ ReflogEntry e = db.getReflogReader("refs/heads/batch1").getLastEntry();
+ assertEquals(msg, e.getComment());
+ assertEquals(person, e.getWho());
+ assertEquals(cur, e.getNewId());
+
+ e = db.getReflogReader("refs/heads/batch2").getLastEntry();
+ assertEquals(msg, e.getComment());
+ assertEquals(person, e.getWho());
+ assertEquals(prev, e.getNewId());
+
+ assertEquals(cur, db.exactRef("refs/heads/batch1").getObjectId());
+ assertEquals(prev, db.exactRef("refs/heads/batch2").getObjectId());
+ }
+
+ @Test
+ public void testFastforwardStatus() throws Exception {
+ ObjectId cur = db.resolve("master");
+ ObjectId prev = db.resolve("master^");
+ RefUpdate u = db.updateRef("refs/heads/master");
+
+ u.setNewObjectId(prev);
+ u.setForceUpdate(true);
+ assertEquals(FORCED, u.update());
+
+ RefUpdate u2 = db.updateRef("refs/heads/master");
+
+ u2.setNewObjectId(cur);
+ assertEquals(FAST_FORWARD, u2.update());
+ }
+
+ @Test
+ public void testUpdateChecksOldValue() throws Exception {
+ ObjectId cur = db.resolve("master");
+ ObjectId prev = db.resolve("master^");
+ RefUpdate u1 = db.updateRef("refs/heads/master");
+ RefUpdate u2 = db.updateRef("refs/heads/master");
+
+ u1.setExpectedOldObjectId(cur);
+ u1.setNewObjectId(prev);
+ u1.setForceUpdate(true);
+
+ u2.setExpectedOldObjectId(cur);
+ u2.setNewObjectId(prev);
+ u2.setForceUpdate(true);
+
+ assertEquals(FORCED, u1.update());
+ assertEquals(LOCK_FAILURE, u2.update());
+ }
+
+ @Test
+ public void testWritesymref() throws Exception {
+ writeSymref(Constants.HEAD, "refs/heads/a");
+ assertNotNull(db.exactRef("refs/heads/b"));
+ }
+
+ @Test
+ public void testFastforwardStatus2() throws Exception {
+ writeSymref(Constants.HEAD, "refs/heads/a");
+ ObjectId bId = db.exactRef("refs/heads/b").getObjectId();
+ RefUpdate u = db.updateRef("refs/heads/a");
+ u.setNewObjectId(bId);
+ u.setRefLogMessage("Setup", false);
+ assertEquals(FAST_FORWARD, u.update());
+ }
+
+ @Test
+ public void testDelete() throws Exception {
+ RefUpdate up = db.getRefDatabase().newUpdate("refs/heads/a", false);
+ up.setForceUpdate(true);
+ RefUpdate.Result res = up.delete();
+ assertEquals(res, FORCED);
+ assertNull(db.exactRef("refs/heads/a"));
+ }
+
+ @Test
+ public void testDeleteWithoutHead() throws IOException {
+ // Prepare repository without HEAD
+ RefUpdate refUpdate = db.updateRef(Constants.HEAD, true);
+ refUpdate.setForceUpdate(true);
+ refUpdate.setNewObjectId(ObjectId.zeroId());
+
+ RefUpdate.Result updateResult = refUpdate.update();
+ assertEquals(FORCED, updateResult);
+
+ Ref r = db.exactRef("HEAD");
+ assertEquals(ObjectId.zeroId(), r.getObjectId());
+ RefUpdate.Result deleteHeadResult = db.updateRef(Constants.HEAD)
+ .delete();
+
+ // why does doDelete say NEW ?
+ assertEquals(RefUpdate.Result.NO_CHANGE, deleteHeadResult);
+
+ // Any result is ok as long as it's not an NPE
+ db.updateRef(Constants.R_HEADS + "master").delete();
+ }
+
+ @Test
+ public void testUpdateRefDetached() throws Exception {
+ ObjectId pid = db.resolve("refs/heads/master");
+ ObjectId ppid = db.resolve("refs/heads/master^");
+ RefUpdate updateRef = db.updateRef("HEAD", true);
+ updateRef.setForceUpdate(true);
+ updateRef.setNewObjectId(ppid);
+ RefUpdate.Result update = updateRef.update();
+ assertEquals(FORCED, update);
+ assertEquals(ppid, db.resolve("HEAD"));
+ Ref ref = db.exactRef("HEAD");
+ assertEquals("HEAD", ref.getName());
+ assertTrue("is detached", !ref.isSymbolic());
+
+ // the branch HEAD referred to is left untouched
+ assertEquals(pid, db.resolve("refs/heads/master"));
+ ReflogReader reflogReader = db.getReflogReader("HEAD");
+ ReflogEntry e = reflogReader.getReverseEntries().get(0);
+ assertEquals(ppid, e.getNewId());
+ assertEquals("GIT_COMMITTER_EMAIL", e.getWho().getEmailAddress());
+ assertEquals("GIT_COMMITTER_NAME", e.getWho().getName());
+ assertEquals(1250379778000L, e.getWho().getWhen().getTime());
+ assertEquals(pid, e.getOldId());
+ }
+
+ @Test
+ public void testWriteReflog() throws Exception {
+ ObjectId pid = db.resolve("refs/heads/master^");
+ RefUpdate updateRef = db.updateRef("refs/heads/master");
+ updateRef.setNewObjectId(pid);
+ String msg = "REFLOG!";
+ updateRef.setRefLogMessage(msg, true);
+ PersonIdent person = new PersonIdent("name", "mail@example.com");
+ updateRef.setRefLogIdent(person);
+ updateRef.setForceUpdate(true);
+ RefUpdate.Result update = updateRef.update();
+ assertEquals(FORCED, update); // internal
+ ReflogReader r = db.getReflogReader("refs/heads/master");
+
+ ReflogEntry e = r.getLastEntry();
+ assertEquals(e.getNewId(), pid);
+ assertEquals(e.getComment(), "REFLOG!: FORCED");
+ assertEquals(e.getWho(), person);
+ }
+
+ @Test
+ public void testLooseDelete() throws IOException {
+ final String newRef = "refs/heads/abc";
+ assertNull(db.exactRef(newRef));
+
+ RefUpdate ref = db.updateRef(newRef);
+ ObjectId nonZero = db.resolve(Constants.HEAD);
+ assertNotEquals(nonZero, ObjectId.zeroId());
+ ref.setNewObjectId(nonZero);
+ assertEquals(RefUpdate.Result.NEW, ref.update());
+
+ ref = db.updateRef(newRef);
+ ref.setNewObjectId(db.resolve(Constants.HEAD));
+
+ assertEquals(ref.delete(), RefUpdate.Result.NO_CHANGE);
+
+ // Differs from RefupdateTest. Deleting a loose ref leaves reflog trail.
+ ReflogReader reader = db.getReflogReader("refs/heads/abc");
+ assertEquals(ObjectId.zeroId(), reader.getReverseEntry(1).getOldId());
+ assertEquals(nonZero, reader.getReverseEntry(1).getNewId());
+ assertEquals(nonZero, reader.getReverseEntry(0).getOldId());
+ assertEquals(ObjectId.zeroId(), reader.getReverseEntry(0).getNewId());
+ }
+
+ private static class SubclassedId extends ObjectId {
+ SubclassedId(AnyObjectId src) {
+ super(src);
+ }
+ }
+
+ @Test
+ public void testNoCacheObjectIdSubclass() throws IOException {
+ final String newRef = "refs/heads/abc";
+ final RefUpdate ru = updateRef(newRef);
+ final SubclassedId newid = new SubclassedId(ru.getNewObjectId());
+ ru.setNewObjectId(newid);
+ RefUpdate.Result update = ru.update();
+ assertEquals(RefUpdate.Result.NEW, update);
+ Ref r = db.exactRef(newRef);
+ assertEquals(newRef, r.getName());
+ assertNotNull(r.getObjectId());
+ assertNotSame(newid, r.getObjectId());
+ assertSame(ObjectId.class, r.getObjectId().getClass());
+ assertEquals(newid, r.getObjectId());
+ List<ReflogEntry> reverseEntries1 = db.getReflogReader("refs/heads/abc")
+ .getReverseEntries();
+ ReflogEntry entry1 = reverseEntries1.get(0);
+ assertEquals(1, reverseEntries1.size());
+ assertEquals(ObjectId.zeroId(), entry1.getOldId());
+ assertEquals(r.getObjectId(), entry1.getNewId());
+
+ assertEquals(new PersonIdent(db).toString(),
+ entry1.getWho().toString());
+ assertEquals("", entry1.getComment());
+ List<ReflogEntry> reverseEntries2 = db.getReflogReader("HEAD")
+ .getReverseEntries();
+ assertEquals(0, reverseEntries2.size());
+ }
+
+ @Test
+ public void testDeleteSymref() throws IOException {
+ RefUpdate dst = updateRef("refs/heads/abc");
+ assertEquals(RefUpdate.Result.NEW, dst.update());
+ ObjectId id = dst.getNewObjectId();
+
+ RefUpdate u = db.updateRef("refs/symref");
+ assertEquals(RefUpdate.Result.NEW, u.link(dst.getName()));
+
+ Ref ref = db.exactRef(u.getName());
+ assertNotNull(ref);
+ assertTrue(ref.isSymbolic());
+ assertEquals(dst.getName(), ref.getLeaf().getName());
+ assertEquals(id, ref.getLeaf().getObjectId());
+
+ u = db.updateRef(u.getName());
+ u.setDetachingSymbolicRef();
+ u.setForceUpdate(true);
+ assertEquals(FORCED, u.delete());
+
+ assertNull(db.exactRef(u.getName()));
+ ref = db.exactRef(dst.getName());
+ assertNotNull(ref);
+ assertFalse(ref.isSymbolic());
+ assertEquals(id, ref.getObjectId());
+ }
+
+ @Test
+ public void writeUnbornHead() throws Exception {
+ RefUpdate.Result r = db.updateRef("HEAD").link("refs/heads/unborn");
+ assertEquals(FORCED, r);
+
+ Ref head = db.exactRef("HEAD");
+ assertTrue(head.isSymbolic());
+ assertEquals(head.getTarget().getName(), "refs/heads/unborn");
+ }
+
+ /**
+ * Update the HEAD ref when the referenced branch is unborn
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testUpdateRefDetachedUnbornHead() throws Exception {
+ ObjectId ppid = db.resolve("refs/heads/master^");
+ writeSymref("HEAD", "refs/heads/unborn");
+ RefUpdate updateRef = db.updateRef("HEAD", true);
+ updateRef.setForceUpdate(true);
+ updateRef.setNewObjectId(ppid);
+ RefUpdate.Result update = updateRef.update();
+ assertEquals(RefUpdate.Result.NEW, update);
+ assertEquals(ppid, db.resolve("HEAD"));
+ Ref ref = db.exactRef("HEAD");
+ assertEquals("HEAD", ref.getName());
+ assertTrue("is detached", !ref.isSymbolic());
+
+ // the branch HEAD referred to is left untouched
+ assertNull(db.resolve("refs/heads/unborn"));
+ ReflogReader reflogReader = db.getReflogReader("HEAD");
+ ReflogEntry e = reflogReader.getReverseEntries().get(0);
+ assertEquals(ObjectId.zeroId(), e.getOldId());
+ assertEquals(ppid, e.getNewId());
+ assertEquals("GIT_COMMITTER_EMAIL", e.getWho().getEmailAddress());
+ assertEquals("GIT_COMMITTER_NAME", e.getWho().getName());
+ assertEquals(1250379778000L, e.getWho().getWhen().getTime());
+ }
+
+ @Test
+ public void testDeleteNotFound() throws IOException {
+ RefUpdate ref = updateRef("refs/heads/doesnotexist");
+ assertNull(db.exactRef(ref.getName()));
+ assertEquals(RefUpdate.Result.NEW, ref.delete());
+ assertNull(db.exactRef(ref.getName()));
+ }
+
+ @Test
+ public void testRenameSymref() throws IOException {
+ db.resolve("HEAD");
+ RefRename r = db.renameRef("HEAD", "KOPF");
+ assertEquals(IO_FAILURE, r.rename());
+ }
+
+ @Test
+ public void testRenameCurrentBranch() throws IOException {
+ ObjectId rb = db.resolve("refs/heads/b");
+ writeSymref(Constants.HEAD, "refs/heads/b");
+ ObjectId oldHead = db.resolve(Constants.HEAD);
+ assertEquals("internal test condition, b == HEAD", oldHead, rb);
+ RefRename renameRef = db.renameRef("refs/heads/b",
+ "refs/heads/new/name");
+ RefUpdate.Result result = renameRef.rename();
+ assertEquals(RefUpdate.Result.RENAMED, result);
+ assertEquals(rb, db.resolve("refs/heads/new/name"));
+ assertNull(db.resolve("refs/heads/b"));
+ assertEquals(rb, db.resolve(Constants.HEAD));
+
+ List<String> names = new ArrayList<>();
+ names.add("HEAD");
+ names.add("refs/heads/b");
+ names.add("refs/heads/new/name");
+
+ for (String nm : names) {
+ ReflogReader rd = db.getReflogReader(nm);
+ assertNotNull(rd);
+ ReflogEntry last = rd.getLastEntry();
+ ObjectId id = last.getNewId();
+ assertTrue(ObjectId.zeroId().equals(id) || rb.equals(id));
+
+ id = last.getNewId();
+ assertTrue(ObjectId.zeroId().equals(id) || rb.equals(id));
+
+ String want = "Branch: renamed b to new/name";
+ assertEquals(want, last.getComment());
+ }
+ }
+
+ @Test
+ public void testRenameDestExists() throws IOException {
+ ObjectId rb = db.resolve("refs/heads/b");
+ writeSymref(Constants.HEAD, "refs/heads/b");
+ ObjectId oldHead = db.resolve(Constants.HEAD);
+ assertEquals("internal test condition, b == HEAD", oldHead, rb);
+ RefRename renameRef = db.renameRef("refs/heads/b", "refs/heads/a");
+ RefUpdate.Result result = renameRef.rename();
+ assertEquals(RefUpdate.Result.LOCK_FAILURE, result);
+ }
+
+ @Test
+ public void testRenameAtomic() throws IOException {
+ ObjectId prevId = db.resolve("refs/heads/master^");
+
+ RefRename rename = db.renameRef("refs/heads/master",
+ "refs/heads/newmaster");
+
+ RefUpdate updateRef = db.updateRef("refs/heads/master");
+ updateRef.setNewObjectId(prevId);
+ updateRef.setForceUpdate(true);
+ assertEquals(FORCED, updateRef.update());
+ assertEquals(RefUpdate.Result.LOCK_FAILURE, rename.rename());
+ }
+
+ @Test
+ public void reftableRefsStorageClass() throws IOException {
+ Ref b = db.exactRef("refs/heads/b");
+ assertEquals(Ref.Storage.PACKED, b.getStorage());
+ }
+
+ private RefUpdate updateRef(String name) throws IOException {
+ final RefUpdate ref = db.updateRef(name);
+ ref.setNewObjectId(db.resolve(Constants.HEAD));
+ return ref;
+ }
+
+ private void writeSymref(String src, String dst) throws IOException {
+ RefUpdate u = db.updateRef(src);
+ switch (u.link(dst)) {
+ case NEW:
+ case FORCED:
+ case NO_CHANGE:
+ break;
+ default:
+ fail("link " + src + " to " + dst);
+ }
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
index 3a43564180..bd2c203f5c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
@@ -45,9 +45,12 @@
package org.eclipse.jgit.internal.storage.file;
-import static org.eclipse.jgit.junit.Assert.assertEquals;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.junit.Assert.assertEquals;
import static org.eclipse.jgit.lib.Constants.LOCK_SUFFIX;
+import static org.eclipse.jgit.lib.RefUpdate.Result.FORCED;
+import static org.eclipse.jgit.lib.RefUpdate.Result.IO_FAILURE;
+import static org.eclipse.jgit.lib.RefUpdate.Result.LOCK_FAILURE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -82,7 +85,6 @@ import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.junit.Test;
public class RefUpdateTest extends SampleDataRepositoryTestCase {
-
private void writeSymref(String src, String dst) throws IOException {
RefUpdate u = db.updateRef(src);
switch (u.link(dst)) {
@@ -233,6 +235,17 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
}
@Test
+ public void testWriteReflog() throws IOException {
+ ObjectId pid = db.resolve("refs/heads/master^");
+ RefUpdate updateRef = db.updateRef("refs/heads/master");
+ updateRef.setNewObjectId(pid);
+ updateRef.setForceUpdate(true);
+ Result update = updateRef.update();
+ assertEquals(Result.FORCED, update);
+ assertEquals(1,db.getReflogReader("refs/heads/master").getReverseEntries().size());
+ }
+
+ @Test
public void testLooseDelete() throws IOException {
final String newRef = "refs/heads/abc";
RefUpdate ref = updateRef(newRef);
@@ -379,6 +392,8 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
refUpdate.setNewObjectId(ObjectId.zeroId());
Result updateResult = refUpdate.update();
assertEquals(Result.FORCED, updateResult);
+
+ assertEquals(ObjectId.zeroId(), db.exactRef("HEAD").getObjectId());
Result deleteHeadResult = db.updateRef(Constants.HEAD).delete();
assertEquals(Result.NO_CHANGE, deleteHeadResult);
@@ -903,6 +918,45 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
}
@Test
+ public void testUpdateChecksOldValue() throws Exception {
+ ObjectId cur = db.resolve("master");
+ ObjectId prev = db.resolve("master^");
+ RefUpdate u1 = db.updateRef("refs/heads/master");
+ RefUpdate u2 = db.updateRef("refs/heads/master");
+
+ u1.setExpectedOldObjectId(cur);
+ u1.setNewObjectId(prev);
+ u1.setForceUpdate(true);
+
+ u2.setExpectedOldObjectId(cur);
+ u2.setNewObjectId(prev);
+ u2.setForceUpdate(true);
+
+ assertEquals(FORCED, u1.update());
+ assertEquals(LOCK_FAILURE, u2.update());
+ }
+
+ @Test
+ public void testRenameAtomic() throws IOException {
+ ObjectId prevId = db.resolve("refs/heads/master^");
+
+ RefRename rename = db.renameRef("refs/heads/master", "refs/heads/newmaster");
+
+ RefUpdate updateRef = db.updateRef("refs/heads/master");
+ updateRef.setNewObjectId(prevId);
+ updateRef.setForceUpdate(true);
+ assertEquals(FORCED, updateRef.update());
+ assertEquals(RefUpdate.Result.LOCK_FAILURE, rename.rename());
+ }
+
+ @Test
+ public void testRenameSymref() throws IOException {
+ db.resolve("HEAD");
+ RefRename r = db.renameRef("HEAD", "KOPF");
+ assertEquals(IO_FAILURE, r.rename());
+ }
+
+ @Test
public void testRenameRefNameColission1avoided() throws IOException {
// setup
ObjectId rb = db.resolve("refs/heads/b");