diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/StrictWorkMonitor.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/StrictWorkMonitor.java new file mode 100644 index 000000000..22b69a3ee --- /dev/null +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/StrictWorkMonitor.java @@ -0,0 +1,78 @@ +/* + * 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.junit; + +import static org.junit.Assert.assertEquals; + +import org.eclipse.jgit.lib.ProgressMonitor; + +public final class StrictWorkMonitor implements ProgressMonitor { + private int lastWork, totalWork; + + @Override + public void start(int totalTasks) { + // empty + } + + @Override + public void beginTask(String title, int total) { + this.totalWork = total; + lastWork = 0; + } + + @Override + public void update(int completed) { + lastWork += completed; + } + + @Override + public void endTask() { + assertEquals("Units of work recorded", totalWork, lastWork); + } + + @Override + public boolean isCancelled() { + return false; + } +} 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 new file mode 100644 index 000000000..3cfc82d66 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java @@ -0,0 +1,402 @@ +/* + * 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.file; + +import static org.eclipse.jgit.lib.ObjectId.zeroId; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; +import org.eclipse.jgit.junit.StrictWorkMonitor; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.BatchRefUpdate; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefDatabase; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.ReceiveCommand; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase { + @Parameter + public boolean atomic; + + @Parameters(name = "atomic={0}") + public static Collection data() { + return Arrays.asList(new Object[][]{ {Boolean.FALSE}, {Boolean.TRUE} }); + } + + private Repository diskRepo; + private TestRepository repo; + private RefDirectory refdir; + private RevCommit A; + private RevCommit B; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + + diskRepo = createBareRepository(); + refdir = (RefDirectory) diskRepo.getRefDatabase(); + + repo = new TestRepository<>(diskRepo); + A = repo.commit().create(); + B = repo.commit(repo.getRevWalk().parseCommit(A)); + } + + private BatchRefUpdate newBatchUpdate() { + BatchRefUpdate u = refdir.newBatchUpdate(); + if (atomic) { + assertTrue(u.isAtomic()); + } else { + u.setAtomic(false); + } + return u; + } + + @Test + public void simpleNoForce() throws IOException { + writeLooseRef("refs/heads/master", A); + writeLooseRef("refs/heads/masters", B); + List commands = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", + ReceiveCommand.Type.UPDATE), + new ReceiveCommand(B, A, "refs/heads/masters", + ReceiveCommand.Type.UPDATE_NONFASTFORWARD)); + BatchRefUpdate batchUpdate = newBatchUpdate(); + batchUpdate.addCommand(commands); + batchUpdate.execute(new RevWalk(diskRepo), new StrictWorkMonitor()); + Map refs = refdir.getRefs(RefDatabase.ALL); + assertEquals(ReceiveCommand.Result.REJECTED_NONFASTFORWARD, commands + .get(1).getResult()); + if (atomic) { + assertTrue(ReceiveCommand.isTransactionAborted(commands.get(0))); + assertEquals("[HEAD, refs/heads/master, refs/heads/masters]", refs + .keySet().toString()); + assertEquals(A.getId(), refs.get("refs/heads/master").getObjectId()); + assertEquals(B.getId(), refs.get("refs/heads/masters").getObjectId()); + } else { + assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult()); + assertEquals("[HEAD, refs/heads/master, refs/heads/masters]", refs + .keySet().toString()); + assertEquals(B.getId(), refs.get("refs/heads/master").getObjectId()); + assertEquals(B.getId(), refs.get("refs/heads/masters").getObjectId()); + } + } + + @Test + public void simpleForce() throws IOException { + writeLooseRef("refs/heads/master", A); + writeLooseRef("refs/heads/masters", B); + List commands = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", + ReceiveCommand.Type.UPDATE), + new ReceiveCommand(B, A, "refs/heads/masters", + ReceiveCommand.Type.UPDATE_NONFASTFORWARD)); + BatchRefUpdate batchUpdate = newBatchUpdate(); + batchUpdate.setAllowNonFastForwards(true); + batchUpdate.addCommand(commands); + batchUpdate.execute(new RevWalk(diskRepo), new StrictWorkMonitor()); + Map refs = refdir.getRefs(RefDatabase.ALL); + assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult()); + assertEquals(ReceiveCommand.Result.OK, commands.get(1).getResult()); + assertEquals("[HEAD, refs/heads/master, refs/heads/masters]", refs + .keySet().toString()); + assertEquals(B.getId(), refs.get("refs/heads/master").getObjectId()); + assertEquals(A.getId(), refs.get("refs/heads/masters").getObjectId()); + } + + @Test + public void nonFastForwardDoesNotDoExpensiveMergeCheck() throws IOException { + writeLooseRef("refs/heads/master", B); + List commands = Arrays.asList( + new ReceiveCommand(B, A, "refs/heads/master", + ReceiveCommand.Type.UPDATE_NONFASTFORWARD)); + BatchRefUpdate batchUpdate = newBatchUpdate(); + batchUpdate.setAllowNonFastForwards(true); + batchUpdate.addCommand(commands); + batchUpdate.execute(new RevWalk(diskRepo) { + @Override + public boolean isMergedInto(RevCommit base, RevCommit tip) { + throw new AssertionError("isMergedInto() should not be called"); + } + }, new StrictWorkMonitor()); + Map refs = refdir.getRefs(RefDatabase.ALL); + assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult()); + assertEquals(A.getId(), refs.get("refs/heads/master").getObjectId()); + } + + @Test + public void fileDirectoryConflict() throws IOException { + writeLooseRef("refs/heads/master", A); + writeLooseRef("refs/heads/masters", B); + List commands = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", + ReceiveCommand.Type.UPDATE), + new ReceiveCommand(zeroId(), A, "refs/heads/master/x", + ReceiveCommand.Type.CREATE), + new ReceiveCommand(zeroId(), A, "refs/heads", + ReceiveCommand.Type.CREATE)); + BatchRefUpdate batchUpdate = newBatchUpdate(); + batchUpdate.setAllowNonFastForwards(true); + batchUpdate.addCommand(commands); + batchUpdate + .execute(new RevWalk(diskRepo), NullProgressMonitor.INSTANCE); + Map refs = refdir.getRefs(RefDatabase.ALL); + + 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. + assertEquals(ReceiveCommand.Result.LOCK_FAILURE, + commands.get(0).getResult()); + assertTrue(ReceiveCommand.isTransactionAborted(commands.get(1))); + assertTrue(ReceiveCommand.isTransactionAborted(commands.get(2))); + assertEquals("[HEAD, refs/heads/master, refs/heads/masters]", refs + .keySet().toString()); + assertEquals(A.getId(), refs.get("refs/heads/master").getObjectId()); + assertEquals(B.getId(), refs.get("refs/heads/masters").getObjectId()); + } else { + // Non-atomic updates are applied in order: master succeeds, then master/x + // fails due to conflict. + assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult()); + assertEquals(ReceiveCommand.Result.LOCK_FAILURE, commands.get(1) + .getResult()); + assertEquals(ReceiveCommand.Result.LOCK_FAILURE, commands.get(2) + .getResult()); + assertEquals("[HEAD, refs/heads/master, refs/heads/masters]", refs + .keySet().toString()); + assertEquals(B.getId(), refs.get("refs/heads/master").getObjectId()); + assertEquals(B.getId(), refs.get("refs/heads/masters").getObjectId()); + } + } + + @Test + public void conflictThanksToDelete() throws IOException { + writeLooseRef("refs/heads/master", A); + writeLooseRef("refs/heads/masters", B); + List commands = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", + ReceiveCommand.Type.UPDATE), + new ReceiveCommand(zeroId(), A, "refs/heads/masters/x", + ReceiveCommand.Type.CREATE), + new ReceiveCommand(B, zeroId(), "refs/heads/masters", + ReceiveCommand.Type.DELETE)); + BatchRefUpdate batchUpdate = newBatchUpdate(); + batchUpdate.setAllowNonFastForwards(true); + batchUpdate.addCommand(commands); + batchUpdate.execute(new RevWalk(diskRepo), new StrictWorkMonitor()); + Map refs = refdir.getRefs(RefDatabase.ALL); + assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult()); + assertEquals(ReceiveCommand.Result.OK, commands.get(1).getResult()); + assertEquals(ReceiveCommand.Result.OK, commands.get(2).getResult()); + assertEquals("[HEAD, refs/heads/master, refs/heads/masters/x]", refs + .keySet().toString()); + assertEquals(A.getId(), refs.get("refs/heads/masters/x").getObjectId()); + } + + @Test + public void updateToMissingObject() throws IOException { + writeLooseRef("refs/heads/master", A); + ObjectId bad = + ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); + List commands = Arrays.asList( + new ReceiveCommand(A, bad, "refs/heads/master", + ReceiveCommand.Type.UPDATE), + new ReceiveCommand(zeroId(), B, "refs/heads/foo2", + ReceiveCommand.Type.CREATE)); + BatchRefUpdate batchUpdate = newBatchUpdate(); + batchUpdate.setAllowNonFastForwards(true); + batchUpdate.addCommand(commands); + batchUpdate.execute(new RevWalk(diskRepo), NullProgressMonitor.INSTANCE); + Map refs = refdir.getRefs(RefDatabase.ALL); + assertEquals(ReceiveCommand.Result.REJECTED_MISSING_OBJECT, + commands.get(0).getResult()); + + if (atomic) { + assertTrue(ReceiveCommand.isTransactionAborted(commands.get(1))); + assertEquals("[HEAD, refs/heads/master]", refs.keySet() + .toString()); + assertEquals(A.getId(), refs.get("refs/heads/master").getObjectId()); + } else { + assertEquals(ReceiveCommand.Result.OK, commands.get(1).getResult()); + assertEquals("[HEAD, refs/heads/foo2, refs/heads/master]", refs.keySet() + .toString()); + assertEquals(A.getId(), refs.get("refs/heads/master").getObjectId()); + assertEquals(B.getId(), refs.get("refs/heads/foo2").getObjectId()); + } + } + + @Test + public void addMissingObject() throws IOException { + writeLooseRef("refs/heads/master", A); + ObjectId bad = + ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); + List commands = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", + ReceiveCommand.Type.UPDATE), + new ReceiveCommand(zeroId(), bad, "refs/heads/foo2", + ReceiveCommand.Type.CREATE)); + BatchRefUpdate batchUpdate = newBatchUpdate(); + batchUpdate.setAllowNonFastForwards(true); + batchUpdate.addCommand(commands); + batchUpdate.execute(new RevWalk(diskRepo), NullProgressMonitor.INSTANCE); + Map refs = refdir.getRefs(RefDatabase.ALL); + assertEquals(ReceiveCommand.Result.REJECTED_MISSING_OBJECT, + commands.get(1).getResult()); + + if (atomic) { + assertTrue(ReceiveCommand.isTransactionAborted(commands.get(0))); + assertEquals("[HEAD, refs/heads/master]", refs.keySet().toString()); + assertEquals(A.getId(), refs.get("refs/heads/master").getObjectId()); + } else { + assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult()); + assertEquals("[HEAD, refs/heads/master]", refs.keySet() + .toString()); + assertEquals(B.getId(), refs.get("refs/heads/master").getObjectId()); + } + } + + @Test + public void oneNonExistentRef() throws IOException { + List commands = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/foo1", + ReceiveCommand.Type.UPDATE), + new ReceiveCommand(zeroId(), B, "refs/heads/foo2", + ReceiveCommand.Type.CREATE)); + BatchRefUpdate batchUpdate = newBatchUpdate(); + batchUpdate.setAllowNonFastForwards(true); + batchUpdate.addCommand(commands); + batchUpdate.execute(new RevWalk(diskRepo), new StrictWorkMonitor()); + Map refs = refdir.getRefs(RefDatabase.ALL); + assertEquals(ReceiveCommand.Result.LOCK_FAILURE, + commands.get(0).getResult()); + + if (atomic) { + assertTrue(ReceiveCommand.isTransactionAborted(commands.get(1))); + assertEquals("[]", refs.keySet().toString()); + } else { + assertEquals(ReceiveCommand.Result.OK, commands.get(1).getResult()); + assertEquals("[refs/heads/foo2]", refs.keySet().toString()); + assertEquals(B.getId(), refs.get("refs/heads/foo2").getObjectId()); + } + } + + @Test + public void oneRefWrongOldValue() throws IOException { + writeLooseRef("refs/heads/master", A); + List commands = Arrays.asList( + new ReceiveCommand(B, B, "refs/heads/master", + ReceiveCommand.Type.UPDATE), + new ReceiveCommand(zeroId(), B, "refs/heads/foo2", + ReceiveCommand.Type.CREATE)); + BatchRefUpdate batchUpdate = newBatchUpdate(); + batchUpdate.setAllowNonFastForwards(true); + batchUpdate.addCommand(commands); + batchUpdate.execute(new RevWalk(diskRepo), new StrictWorkMonitor()); + Map refs = refdir.getRefs(RefDatabase.ALL); + assertEquals(ReceiveCommand.Result.LOCK_FAILURE, + commands.get(0).getResult()); + + if (atomic) { + assertTrue(ReceiveCommand.isTransactionAborted(commands.get(1))); + assertEquals("[HEAD, refs/heads/master]", refs.keySet().toString()); + assertEquals(A.getId(), refs.get("refs/heads/master").getObjectId()); + } else { + assertEquals(ReceiveCommand.Result.OK, commands.get(1).getResult()); + assertEquals("[HEAD, refs/heads/foo2, refs/heads/master]", refs + .keySet().toString()); + assertEquals(A.getId(), refs.get("refs/heads/master").getObjectId()); + assertEquals(B.getId(), refs.get("refs/heads/foo2").getObjectId()); + } + } + + @Test + public void nonExistentRef() throws IOException { + writeLooseRef("refs/heads/master", A); + List commands = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", + ReceiveCommand.Type.UPDATE), + new ReceiveCommand(A, zeroId(), "refs/heads/foo2", + ReceiveCommand.Type.DELETE)); + BatchRefUpdate batchUpdate = newBatchUpdate(); + batchUpdate.setAllowNonFastForwards(true); + batchUpdate.addCommand(commands); + batchUpdate.execute(new RevWalk(diskRepo), new StrictWorkMonitor()); + Map refs = refdir.getRefs(RefDatabase.ALL); + assertEquals(ReceiveCommand.Result.LOCK_FAILURE, + commands.get(1).getResult()); + + if (atomic) { + assertTrue(ReceiveCommand.isTransactionAborted(commands.get(0))); + assertEquals("[HEAD, refs/heads/master]", refs.keySet().toString()); + assertEquals(A.getId(), refs.get("refs/heads/master").getObjectId()); + } else { + assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult()); + assertEquals("[HEAD, refs/heads/master]", refs.keySet().toString()); + assertEquals(B.getId(), refs.get("refs/heads/master").getObjectId()); + } + } + + private void writeLooseRef(String name, AnyObjectId id) throws IOException { + write(new File(diskRepo.getDirectory(), name), id.name() + "\n"); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java index f5d04cd8c..145fed6b1 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java @@ -46,7 +46,6 @@ import static org.eclipse.jgit.lib.Constants.HEAD; import static org.eclipse.jgit.lib.Constants.R_HEADS; import static org.eclipse.jgit.lib.Constants.R_TAGS; -import static org.eclipse.jgit.lib.ObjectId.zeroId; import static org.eclipse.jgit.lib.Ref.Storage.LOOSE; import static org.eclipse.jgit.lib.Ref.Storage.NEW; import static org.junit.Assert.assertEquals; @@ -61,8 +60,6 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; @@ -73,18 +70,12 @@ import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.AnyObjectId; -import org.eclipse.jgit.lib.BatchRefUpdate; -import org.eclipse.jgit.lib.NullProgressMonitor; -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref.Storage; import org.eclipse.jgit.lib.RefDatabase; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTag; -import org.eclipse.jgit.revwalk.RevWalk; -import org.eclipse.jgit.transport.ReceiveCommand; import org.junit.Before; import org.junit.Test; @@ -1293,460 +1284,6 @@ public void onRefsChanged(RefsChangedEvent event) { assertEquals(1, changeCount.get()); } - @Test - public void testBatchRefUpdateSimpleNoForceNonAtomic() throws IOException { - writeLooseRef("refs/heads/master", A); - writeLooseRef("refs/heads/masters", B); - List commands = Arrays.asList( - new ReceiveCommand(A, B, "refs/heads/master", - ReceiveCommand.Type.UPDATE), - new ReceiveCommand(B, A, "refs/heads/masters", - ReceiveCommand.Type.UPDATE_NONFASTFORWARD)); - BatchRefUpdate batchUpdate = refdir.newBatchUpdate(); - batchUpdate.setAtomic(false); - batchUpdate.addCommand(commands); - batchUpdate.execute(new RevWalk(diskRepo), new StrictWorkMonitor()); - Map refs = refdir.getRefs(RefDatabase.ALL); - assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult()); - assertEquals(ReceiveCommand.Result.REJECTED_NONFASTFORWARD, commands - .get(1).getResult()); - assertEquals("[HEAD, refs/heads/master, refs/heads/masters]", refs - .keySet().toString()); - assertEquals(B.getId(), refs.get("refs/heads/master").getObjectId()); - assertEquals(B.getId(), refs.get("refs/heads/masters").getObjectId()); - } - - @Test - public void testBatchRefUpdateSimpleNoForceAtomic() throws IOException { - writeLooseRef("refs/heads/master", A); - writeLooseRef("refs/heads/masters", B); - List commands = Arrays.asList( - new ReceiveCommand(A, B, "refs/heads/master", - ReceiveCommand.Type.UPDATE), - new ReceiveCommand(B, A, "refs/heads/masters", - ReceiveCommand.Type.UPDATE_NONFASTFORWARD)); - BatchRefUpdate batchUpdate = refdir.newBatchUpdate(); - assertTrue(batchUpdate.isAtomic()); - batchUpdate.addCommand(commands); - batchUpdate.execute(new RevWalk(diskRepo), new StrictWorkMonitor()); - Map refs = refdir.getRefs(RefDatabase.ALL); - assertTrue(ReceiveCommand.isTransactionAborted(commands.get(0))); - assertEquals(ReceiveCommand.Result.REJECTED_NONFASTFORWARD, commands - .get(1).getResult()); - assertEquals("[HEAD, refs/heads/master, refs/heads/masters]", refs - .keySet().toString()); - assertEquals(A.getId(), refs.get("refs/heads/master").getObjectId()); - assertEquals(B.getId(), refs.get("refs/heads/masters").getObjectId()); - } - - @Test - public void testBatchRefUpdateSimpleForceNonAtomic() throws IOException { - testBatchRefUpdateSimpleForce(false); - } - - @Test - public void testBatchRefUpdateSimpleForceAtomic() throws IOException { - testBatchRefUpdateSimpleForce(true); - } - - private void testBatchRefUpdateSimpleForce(boolean atomic) throws IOException { - writeLooseRef("refs/heads/master", A); - writeLooseRef("refs/heads/masters", B); - List commands = Arrays.asList( - new ReceiveCommand(A, B, "refs/heads/master", - ReceiveCommand.Type.UPDATE), - new ReceiveCommand(B, A, "refs/heads/masters", - ReceiveCommand.Type.UPDATE_NONFASTFORWARD)); - BatchRefUpdate batchUpdate = refdir.newBatchUpdate(); - batchUpdate.setAtomic(atomic); - batchUpdate.setAllowNonFastForwards(true); - batchUpdate.addCommand(commands); - batchUpdate.execute(new RevWalk(diskRepo), new StrictWorkMonitor()); - Map refs = refdir.getRefs(RefDatabase.ALL); - assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult()); - assertEquals(ReceiveCommand.Result.OK, commands.get(1).getResult()); - assertEquals("[HEAD, refs/heads/master, refs/heads/masters]", refs - .keySet().toString()); - assertEquals(B.getId(), refs.get("refs/heads/master").getObjectId()); - assertEquals(A.getId(), refs.get("refs/heads/masters").getObjectId()); - } - - @Test - public void testBatchRefUpdateNonFastForwardDoesNotDoExpensiveMergeCheckNonAtomic() - throws IOException { - testBatchRefUpdateNonFastForwardDoesNotDoExpensiveMergeCheck(false); - } - - @Test - public void testBatchRefUpdateNonFastForwardDoesNotDoExpensiveMergeCheckAtomic() - throws IOException { - testBatchRefUpdateNonFastForwardDoesNotDoExpensiveMergeCheck(true); - } - - private void testBatchRefUpdateNonFastForwardDoesNotDoExpensiveMergeCheck( - boolean atomic) throws IOException { - writeLooseRef("refs/heads/master", B); - List commands = Arrays.asList( - new ReceiveCommand(B, A, "refs/heads/master", - ReceiveCommand.Type.UPDATE_NONFASTFORWARD)); - BatchRefUpdate batchUpdate = refdir.newBatchUpdate(); - batchUpdate.setAtomic(atomic); - batchUpdate.setAllowNonFastForwards(true); - batchUpdate.addCommand(commands); - batchUpdate.execute(new RevWalk(diskRepo) { - @Override - public boolean isMergedInto(RevCommit base, RevCommit tip) { - throw new AssertionError("isMergedInto() should not be called"); - } - }, new StrictWorkMonitor()); - Map refs = refdir.getRefs(RefDatabase.ALL); - assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult()); - assertEquals(A.getId(), refs.get("refs/heads/master").getObjectId()); - } - - @Test - public void testBatchRefUpdateFileDirectoryConflictNonAtomic() - throws IOException { - writeLooseRef("refs/heads/master", A); - writeLooseRef("refs/heads/masters", B); - List commands = Arrays.asList( - new ReceiveCommand(A, B, "refs/heads/master", - ReceiveCommand.Type.UPDATE), - new ReceiveCommand(zeroId(), A, "refs/heads/master/x", - ReceiveCommand.Type.CREATE), - new ReceiveCommand(zeroId(), A, "refs/heads", - ReceiveCommand.Type.CREATE)); - BatchRefUpdate batchUpdate = refdir.newBatchUpdate(); - batchUpdate.setAtomic(false); - batchUpdate.setAllowNonFastForwards(true); - batchUpdate.addCommand(commands); - batchUpdate - .execute(new RevWalk(diskRepo), NullProgressMonitor.INSTANCE); - Map refs = refdir.getRefs(RefDatabase.ALL); - // Non-atomic updates are applied in order: master succeeds, then master/x - // fails due to conflict. - assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult()); - assertEquals(ReceiveCommand.Result.LOCK_FAILURE, commands.get(1) - .getResult()); - assertEquals(ReceiveCommand.Result.LOCK_FAILURE, commands.get(2) - .getResult()); - assertEquals("[HEAD, refs/heads/master, refs/heads/masters]", refs - .keySet().toString()); - assertEquals(B.getId(), refs.get("refs/heads/master").getObjectId()); - assertEquals(B.getId(), refs.get("refs/heads/masters").getObjectId()); - } - - @Test - public void testBatchRefUpdateFileDirectoryConflictAtomic() - throws IOException { - writeLooseRef("refs/heads/master", A); - writeLooseRef("refs/heads/masters", B); - List commands = Arrays.asList( - new ReceiveCommand(A, B, "refs/heads/master", - ReceiveCommand.Type.UPDATE), - new ReceiveCommand(zeroId(), A, "refs/heads/master/x", - ReceiveCommand.Type.CREATE), - new ReceiveCommand(zeroId(), A, "refs/heads", - ReceiveCommand.Type.CREATE)); - BatchRefUpdate batchUpdate = refdir.newBatchUpdate(); - assertTrue(batchUpdate.isAtomic()); - batchUpdate.setAllowNonFastForwards(true); - batchUpdate.addCommand(commands); - batchUpdate - .execute(new RevWalk(diskRepo), NullProgressMonitor.INSTANCE); - Map refs = refdir.getRefs(RefDatabase.ALL); - // 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. - assertEquals(ReceiveCommand.Result.LOCK_FAILURE, - commands.get(0).getResult()); - assertTrue(ReceiveCommand.isTransactionAborted(commands.get(1))); - assertTrue(ReceiveCommand.isTransactionAborted(commands.get(2))); - assertEquals("[HEAD, refs/heads/master, refs/heads/masters]", refs - .keySet().toString()); - assertEquals(A.getId(), refs.get("refs/heads/master").getObjectId()); - assertEquals(B.getId(), refs.get("refs/heads/masters").getObjectId()); - } - - @Test - public void testBatchRefUpdateConflictThanksToDeleteNonAtomic() - throws IOException { - writeLooseRef("refs/heads/master", A); - writeLooseRef("refs/heads/masters", B); - List commands = Arrays.asList( - new ReceiveCommand(A, B, "refs/heads/master", - ReceiveCommand.Type.UPDATE), - new ReceiveCommand(zeroId(), A, "refs/heads/masters/x", - ReceiveCommand.Type.CREATE), - new ReceiveCommand(B, zeroId(), "refs/heads/masters", - ReceiveCommand.Type.DELETE)); - BatchRefUpdate batchUpdate = refdir.newBatchUpdate(); - batchUpdate.setAtomic(false); - batchUpdate.setAllowNonFastForwards(true); - batchUpdate.addCommand(commands); - batchUpdate.execute(new RevWalk(diskRepo), new StrictWorkMonitor()); - Map refs = refdir.getRefs(RefDatabase.ALL); - assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult()); - assertEquals(ReceiveCommand.Result.OK, commands.get(1).getResult()); - assertEquals(ReceiveCommand.Result.OK, commands.get(2).getResult()); - assertEquals("[HEAD, refs/heads/master, refs/heads/masters/x]", refs - .keySet().toString()); - assertEquals(A.getId(), refs.get("refs/heads/masters/x").getObjectId()); - } - - @Test - public void testBatchRefUpdateConflictThanksToDeleteAtomic() - throws IOException { - writeLooseRef("refs/heads/master", A); - writeLooseRef("refs/heads/masters", B); - List commands = Arrays.asList( - new ReceiveCommand(A, B, "refs/heads/master", - ReceiveCommand.Type.UPDATE), - new ReceiveCommand(zeroId(), A, "refs/heads/masters/x", - ReceiveCommand.Type.CREATE), - new ReceiveCommand(B, zeroId(), "refs/heads/masters", - ReceiveCommand.Type.DELETE)); - BatchRefUpdate batchUpdate = refdir.newBatchUpdate(); - assertTrue(batchUpdate.isAtomic()); - batchUpdate.setAllowNonFastForwards(true); - batchUpdate.addCommand(commands); - batchUpdate.execute(new RevWalk(diskRepo), new StrictWorkMonitor()); - Map refs = refdir.getRefs(RefDatabase.ALL); - assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult()); - assertEquals(ReceiveCommand.Result.OK, commands.get(1).getResult()); - assertEquals(ReceiveCommand.Result.OK, commands.get(2).getResult()); - assertEquals("[HEAD, refs/heads/master, refs/heads/masters/x]", refs - .keySet().toString()); - assertEquals(A.getId(), refs.get("refs/heads/masters/x").getObjectId()); - } - - @Test - public void testBatchRefUpdateUpdateToMissingObjectNonAtomic() throws IOException { - writeLooseRef("refs/heads/master", A); - ObjectId bad = - ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); - List commands = Arrays.asList( - new ReceiveCommand(A, bad, "refs/heads/master", - ReceiveCommand.Type.UPDATE), - new ReceiveCommand(zeroId(), B, "refs/heads/foo2", - ReceiveCommand.Type.CREATE)); - BatchRefUpdate batchUpdate = refdir.newBatchUpdate(); - batchUpdate.setAtomic(false); - batchUpdate.setAllowNonFastForwards(true); - batchUpdate.addCommand(commands); - batchUpdate.execute(new RevWalk(diskRepo), NullProgressMonitor.INSTANCE); - Map refs = refdir.getRefs(RefDatabase.ALL); - assertEquals(ReceiveCommand.Result.REJECTED_MISSING_OBJECT, - commands.get(0).getResult()); - assertEquals(ReceiveCommand.Result.OK, commands.get(1).getResult()); - assertEquals("[HEAD, refs/heads/foo2, refs/heads/master]", refs.keySet() - .toString()); - assertEquals(A.getId(), refs.get("refs/heads/master").getObjectId()); - assertEquals(B.getId(), refs.get("refs/heads/foo2").getObjectId()); - } - - @Test - public void testBatchRefUpdateUpdateToMissingObjectAtomic() - throws IOException { - writeLooseRef("refs/heads/master", A); - ObjectId bad = - ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); - List commands = Arrays.asList( - new ReceiveCommand(A, bad, "refs/heads/master", - ReceiveCommand.Type.UPDATE), - new ReceiveCommand(zeroId(), B, "refs/heads/foo2", - ReceiveCommand.Type.CREATE)); - BatchRefUpdate batchUpdate = refdir.newBatchUpdate(); - assertTrue(batchUpdate.isAtomic()); - batchUpdate.setAllowNonFastForwards(true); - batchUpdate.addCommand(commands); - batchUpdate.execute(new RevWalk(diskRepo), NullProgressMonitor.INSTANCE); - Map refs = refdir.getRefs(RefDatabase.ALL); - assertEquals(ReceiveCommand.Result.REJECTED_MISSING_OBJECT, - commands.get(0).getResult()); - assertTrue(ReceiveCommand.isTransactionAborted(commands.get(1))); - assertEquals("[HEAD, refs/heads/master]", refs.keySet() - .toString()); - assertEquals(A.getId(), refs.get("refs/heads/master").getObjectId()); - } - - @Test - public void testBatchRefUpdateAddMissingObjectNonAtomic() throws IOException { - writeLooseRef("refs/heads/master", A); - ObjectId bad = - ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); - List commands = Arrays.asList( - new ReceiveCommand(A, B, "refs/heads/master", - ReceiveCommand.Type.UPDATE), - new ReceiveCommand(zeroId(), bad, "refs/heads/foo2", - ReceiveCommand.Type.CREATE)); - BatchRefUpdate batchUpdate = refdir.newBatchUpdate(); - batchUpdate.setAtomic(false); - batchUpdate.setAllowNonFastForwards(true); - batchUpdate.addCommand(commands); - batchUpdate.execute(new RevWalk(diskRepo), NullProgressMonitor.INSTANCE); - Map refs = refdir.getRefs(RefDatabase.ALL); - assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult()); - assertEquals(ReceiveCommand.Result.REJECTED_MISSING_OBJECT, - commands.get(1).getResult()); - assertEquals("[HEAD, refs/heads/master]", refs.keySet() - .toString()); - assertEquals(B.getId(), refs.get("refs/heads/master").getObjectId()); - } - - @Test - public void testBatchRefUpdateAddMissingObjectAtomic() throws IOException { - writeLooseRef("refs/heads/master", A); - ObjectId bad = - ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); - List commands = Arrays.asList( - new ReceiveCommand(A, B, "refs/heads/master", - ReceiveCommand.Type.UPDATE), - new ReceiveCommand(zeroId(), bad, "refs/heads/foo2", - ReceiveCommand.Type.CREATE)); - BatchRefUpdate batchUpdate = refdir.newBatchUpdate(); - assertTrue(batchUpdate.isAtomic()); - batchUpdate.setAllowNonFastForwards(true); - batchUpdate.addCommand(commands); - batchUpdate.execute(new RevWalk(diskRepo), NullProgressMonitor.INSTANCE); - Map refs = refdir.getRefs(RefDatabase.ALL); - assertTrue(ReceiveCommand.isTransactionAborted(commands.get(0))); - assertEquals(ReceiveCommand.Result.REJECTED_MISSING_OBJECT, - commands.get(1).getResult()); - assertEquals("[HEAD, refs/heads/master]", refs.keySet().toString()); - assertEquals(A.getId(), refs.get("refs/heads/master").getObjectId()); - } - - @Test - public void testBatchRefUpdateOneNonExistentRefNonAtomic() - throws IOException { - List commands = Arrays.asList( - new ReceiveCommand(A, B, "refs/heads/foo1", - ReceiveCommand.Type.UPDATE), - new ReceiveCommand(zeroId(), B, "refs/heads/foo2", - ReceiveCommand.Type.CREATE)); - BatchRefUpdate batchUpdate = refdir.newBatchUpdate(); - batchUpdate.setAtomic(false); - batchUpdate.setAllowNonFastForwards(true); - batchUpdate.addCommand(commands); - batchUpdate.execute(new RevWalk(diskRepo), new StrictWorkMonitor()); - Map refs = refdir.getRefs(RefDatabase.ALL); - assertEquals(ReceiveCommand.Result.LOCK_FAILURE, - commands.get(0).getResult()); - assertEquals(ReceiveCommand.Result.OK, commands.get(1).getResult()); - assertEquals("[refs/heads/foo2]", refs.keySet().toString()); - assertEquals(B.getId(), refs.get("refs/heads/foo2").getObjectId()); - } - - @Test - public void testBatchRefUpdateOneNonExistentRefAtomic() - throws IOException { - List commands = Arrays.asList( - new ReceiveCommand(A, B, "refs/heads/foo1", - ReceiveCommand.Type.UPDATE), - new ReceiveCommand(zeroId(), B, "refs/heads/foo2", - ReceiveCommand.Type.CREATE)); - BatchRefUpdate batchUpdate = refdir.newBatchUpdate(); - assertTrue(batchUpdate.isAtomic()); - batchUpdate.setAllowNonFastForwards(true); - batchUpdate.addCommand(commands); - batchUpdate.execute(new RevWalk(diskRepo), new StrictWorkMonitor()); - Map refs = refdir.getRefs(RefDatabase.ALL); - assertEquals(ReceiveCommand.Result.LOCK_FAILURE, - commands.get(0).getResult()); - assertTrue(ReceiveCommand.isTransactionAborted(commands.get(1))); - assertEquals("[]", refs.keySet().toString()); - } - - @Test - public void testBatchRefUpdateOneRefWrongOldValueNonAtomic() - throws IOException { - writeLooseRef("refs/heads/master", A); - List commands = Arrays.asList( - new ReceiveCommand(B, B, "refs/heads/master", - ReceiveCommand.Type.UPDATE), - new ReceiveCommand(zeroId(), B, "refs/heads/foo2", - ReceiveCommand.Type.CREATE)); - BatchRefUpdate batchUpdate = refdir.newBatchUpdate(); - batchUpdate.setAtomic(false); - batchUpdate.setAllowNonFastForwards(true); - batchUpdate.addCommand(commands); - batchUpdate.execute(new RevWalk(diskRepo), new StrictWorkMonitor()); - Map refs = refdir.getRefs(RefDatabase.ALL); - assertEquals(ReceiveCommand.Result.LOCK_FAILURE, - commands.get(0).getResult()); - assertEquals(ReceiveCommand.Result.OK, commands.get(1).getResult()); - assertEquals("[HEAD, refs/heads/foo2, refs/heads/master]", refs - .keySet().toString()); - assertEquals(A.getId(), refs.get("refs/heads/master").getObjectId()); - assertEquals(B.getId(), refs.get("refs/heads/foo2").getObjectId()); - } - - @Test - public void testBatchRefUpdateOneRefWrongOldValueAtomic() - throws IOException { - writeLooseRef("refs/heads/master", A); - List commands = Arrays.asList( - new ReceiveCommand(B, B, "refs/heads/master", - ReceiveCommand.Type.UPDATE), - new ReceiveCommand(zeroId(), B, "refs/heads/foo2", - ReceiveCommand.Type.CREATE)); - BatchRefUpdate batchUpdate = refdir.newBatchUpdate(); - assertTrue(batchUpdate.isAtomic()); - batchUpdate.setAllowNonFastForwards(true); - batchUpdate.addCommand(commands); - batchUpdate.execute(new RevWalk(diskRepo), new StrictWorkMonitor()); - Map refs = refdir.getRefs(RefDatabase.ALL); - assertEquals(ReceiveCommand.Result.LOCK_FAILURE, - commands.get(0).getResult()); - assertTrue(ReceiveCommand.isTransactionAborted(commands.get(1))); - assertEquals("[HEAD, refs/heads/master]", refs.keySet().toString()); - assertEquals(A.getId(), refs.get("refs/heads/master").getObjectId()); - } - - @Test - public void testBatchRefDeleteNonExistentRefNonAtomic() - throws IOException { - writeLooseRef("refs/heads/master", A); - List commands = Arrays.asList( - new ReceiveCommand(A, B, "refs/heads/master", - ReceiveCommand.Type.UPDATE), - new ReceiveCommand(A, zeroId(), "refs/heads/foo2", - ReceiveCommand.Type.DELETE)); - BatchRefUpdate batchUpdate = refdir.newBatchUpdate(); - batchUpdate.setAtomic(false); - batchUpdate.setAllowNonFastForwards(true); - batchUpdate.addCommand(commands); - batchUpdate.execute(new RevWalk(diskRepo), new StrictWorkMonitor()); - Map refs = refdir.getRefs(RefDatabase.ALL); - assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult()); - assertEquals(ReceiveCommand.Result.LOCK_FAILURE, - commands.get(1).getResult()); - assertEquals("[HEAD, refs/heads/master]", refs.keySet().toString()); - assertEquals(B.getId(), refs.get("refs/heads/master").getObjectId()); - } - - @Test - public void testBatchRefDeleteNonExistentRefAtomic() - throws IOException { - writeLooseRef("refs/heads/master", A); - List commands = Arrays.asList( - new ReceiveCommand(A, B, "refs/heads/master", - ReceiveCommand.Type.UPDATE), - new ReceiveCommand(A, zeroId(), "refs/heads/foo2", - ReceiveCommand.Type.DELETE)); - BatchRefUpdate batchUpdate = refdir.newBatchUpdate(); - assertTrue(batchUpdate.isAtomic()); - batchUpdate.setAllowNonFastForwards(true); - batchUpdate.addCommand(commands); - batchUpdate.execute(new RevWalk(diskRepo), new StrictWorkMonitor()); - Map refs = refdir.getRefs(RefDatabase.ALL); - assertTrue(ReceiveCommand.isTransactionAborted(commands.get(0))); - assertEquals(ReceiveCommand.Result.LOCK_FAILURE, - commands.get(1).getResult()); - assertEquals("[HEAD, refs/heads/master]", refs.keySet().toString()); - assertEquals(A.getId(), refs.get("refs/heads/master").getObjectId()); - } - private void writeLooseRef(String name, AnyObjectId id) throws IOException { writeLooseRef(name, id.name() + "\n"); } @@ -1772,34 +1309,4 @@ private void deleteLooseRef(String name) { File path = new File(diskRepo.getDirectory(), name); assertTrue("deleted " + name, path.delete()); } - - private static final class StrictWorkMonitor implements ProgressMonitor { - private int lastWork, totalWork; - - @Override - public void start(int totalTasks) { - // empty - } - - @Override - public void beginTask(String title, int total) { - this.totalWork = total; - lastWork = 0; - } - - @Override - public void update(int completed) { - lastWork += completed; - } - - @Override - public void endTask() { - assertEquals("Units of work recorded", totalWork, lastWork); - } - - @Override - public boolean isCancelled() { - return false; - } - } }