/* Copyright (c) 2013-2014 Boundless and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/org/documents/edl-v10.html
*
* Contributors:
* Johnathan Garrett (LMN Solutions) - initial implementation
*/
package org.locationtech.geogig.test.integration;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.locationtech.geogig.api.GeogigTransaction;
import org.locationtech.geogig.api.Ref;
import org.locationtech.geogig.api.RevCommit;
import org.locationtech.geogig.api.plumbing.RefParse;
import org.locationtech.geogig.api.plumbing.TransactionBegin;
import org.locationtech.geogig.api.plumbing.TransactionEnd;
import org.locationtech.geogig.api.plumbing.UpdateRef;
import org.locationtech.geogig.api.plumbing.merge.Conflict;
import org.locationtech.geogig.api.plumbing.merge.ConflictsReadOp;
import org.locationtech.geogig.api.porcelain.BranchCreateOp;
import org.locationtech.geogig.api.porcelain.CheckoutOp;
import org.locationtech.geogig.api.porcelain.CommitOp;
import org.locationtech.geogig.api.porcelain.LogOp;
import org.locationtech.geogig.api.porcelain.MergeOp;
import org.locationtech.geogig.api.porcelain.RemoteAddOp;
import com.google.common.base.Suppliers;
public class GeogigTransactionTest extends RepositoryTestCase {
@Rule
public ExpectedException exception = ExpectedException.none();
@Override
protected void setUpInternal() throws Exception {
injector.configDatabase().put("user.name", "groldan");
injector.configDatabase().put("user.email", "groldan@boundlessgeo.com");
}
@Test
public void testTransaction() throws Exception {
LinkedList<RevCommit> expectedMain = new LinkedList<RevCommit>();
LinkedList<RevCommit> expectedTransaction = new LinkedList<RevCommit>();
// make a commit
insertAndAdd(points1);
RevCommit commit = geogig.command(CommitOp.class).call();
expectedMain.addFirst(commit);
expectedTransaction.addFirst(commit);
// start a transaction
GeogigTransaction t = geogig.command(TransactionBegin.class).call();
// perform a commit in the transaction
insertAndAdd(t, points2);
commit = t.command(CommitOp.class).call();
expectedTransaction.addFirst(commit);
// Verify that the base repository is unchanged
Iterator<RevCommit> logs = geogig.command(LogOp.class).call();
List<RevCommit> logged = new ArrayList<RevCommit>();
for (; logs.hasNext();) {
logged.add(logs.next());
}
assertEquals(expectedMain, logged);
// Verify that the transaction has the commit
logs = t.command(LogOp.class).call();
logged = new ArrayList<RevCommit>();
for (; logs.hasNext();) {
logged.add(logs.next());
}
assertEquals(expectedTransaction, logged);
// Commit the transaction
geogig.command(TransactionEnd.class).setTransaction(t).setRebase(true).call();
// Verify that the base repository has the changes from the transaction
logs = geogig.command(LogOp.class).call();
logged = new ArrayList<RevCommit>();
for (; logs.hasNext();) {
logged.add(logs.next());
}
assertEquals(expectedTransaction, logged);
}
@Test
public void testSyncTransaction() throws Exception {
LinkedList<RevCommit> expectedMain = new LinkedList<RevCommit>();
LinkedList<RevCommit> expectedTransaction = new LinkedList<RevCommit>();
// make a commit
insertAndAdd(points1);
RevCommit firstCommit = geogig.command(CommitOp.class).call();
expectedMain.addFirst(firstCommit);
expectedTransaction.addFirst(firstCommit);
// start a transaction
GeogigTransaction t = geogig.command(TransactionBegin.class).call();
// perform a commit in the transaction
insertAndAdd(t, points2);
RevCommit transactionCommit = t.command(CommitOp.class).call();
expectedTransaction.addFirst(transactionCommit);
// perform a commit on the repo
insertAndAdd(points3);
RevCommit repoCommit = geogig.command(CommitOp.class).call();
expectedMain.addFirst(repoCommit);
// Verify that the base repository is unchanged
Iterator<RevCommit> logs = geogig.command(LogOp.class).call();
List<RevCommit> logged = new ArrayList<RevCommit>();
for (; logs.hasNext();) {
logged.add(logs.next());
}
assertEquals(expectedMain, logged);
// Verify that the transaction has the commit
logs = t.command(LogOp.class).call();
logged = new ArrayList<RevCommit>();
for (; logs.hasNext();) {
logged.add(logs.next());
}
assertEquals(expectedTransaction, logged);
// Commit the transaction
geogig.command(TransactionEnd.class).setTransaction(t).call();
// Verify that a merge commit was created
logs = geogig.command(LogOp.class).call();
RevCommit lastCommit = logs.next();
assertFalse(lastCommit.equals(repoCommit));
assertTrue(lastCommit.getMessage().contains("Merge commit"));
assertEquals(lastCommit.getParentIds().get(0), transactionCommit.getId());
assertEquals(lastCommit.getParentIds().get(1), repoCommit.getId());
assertEquals(logs.next(), repoCommit);
assertEquals(logs.next(), transactionCommit);
assertEquals(logs.next(), firstCommit);
assertFalse(logs.hasNext());
}
@Test
public void testTransactionAuthor() throws Exception {
LinkedList<RevCommit> expectedMain = new LinkedList<RevCommit>();
LinkedList<RevCommit> expectedTransaction = new LinkedList<RevCommit>();
// make a commit
insertAndAdd(points1);
RevCommit firstCommit = geogig.command(CommitOp.class).call();
expectedMain.addFirst(firstCommit);
expectedTransaction.addFirst(firstCommit);
// start a transaction
GeogigTransaction t = geogig.command(TransactionBegin.class).call();
t.setAuthor("Transaction Author", "transaction@author.com");
// perform a commit in the transaction
insertAndAdd(t, points2);
RevCommit transactionCommit = t.command(CommitOp.class).call();
expectedTransaction.addFirst(transactionCommit);
// perform a commit on the repo
insertAndAdd(points3);
RevCommit repoCommit = geogig.command(CommitOp.class).call();
expectedMain.addFirst(repoCommit);
// Verify that the base repository is unchanged
Iterator<RevCommit> logs = geogig.command(LogOp.class).call();
List<RevCommit> logged = new ArrayList<RevCommit>();
for (; logs.hasNext();) {
logged.add(logs.next());
}
assertEquals(expectedMain, logged);
// Verify that the transaction has the commit
logs = t.command(LogOp.class).call();
logged = new ArrayList<RevCommit>();
for (; logs.hasNext();) {
logged.add(logs.next());
}
assertEquals(expectedTransaction, logged);
// Commit the transaction
t.commitSyncTransaction();
// Verify that a merge commit was created
logs = geogig.command(LogOp.class).call();
RevCommit lastCommit = logs.next();
assertFalse(lastCommit.equals(repoCommit));
assertTrue(lastCommit.getMessage().contains("Merge commit"));
assertEquals(lastCommit.getParentIds().get(0), transactionCommit.getId());
assertEquals(lastCommit.getParentIds().get(1), repoCommit.getId());
assertEquals("Transaction Author", lastCommit.getAuthor().getName().get());
assertEquals("transaction@author.com", lastCommit.getAuthor().getEmail().get());
assertEquals(logs.next(), repoCommit);
assertEquals(logs.next(), transactionCommit);
assertEquals(logs.next(), firstCommit);
assertFalse(logs.hasNext());
}
@Test
public void testMultipleTransaction() throws Exception {
// make a commit
insertAndAdd(points1);
RevCommit mainCommit = geogig.command(CommitOp.class).setMessage("Commit1").call();
// start the first transaction
GeogigTransaction transaction1 = geogig.command(TransactionBegin.class).call();
// perform a commit in the transaction
insertAndAdd(transaction1, points2);
RevCommit transaction1Commit = transaction1.command(CommitOp.class).setMessage("Commit2")
.call();
// Verify that the base repository is unchanged
Iterator<RevCommit> logs = geogig.command(LogOp.class).call();
assertEquals(mainCommit, logs.next());
assertFalse(logs.hasNext());
// Verify that the transaction has the commit
logs = transaction1.command(LogOp.class).call();
assertEquals(transaction1Commit, logs.next());
assertEquals(mainCommit, logs.next());
assertFalse(logs.hasNext());
// start the second transaction
GeogigTransaction transaction2 = geogig.command(TransactionBegin.class).call();
// perform a commit in the transaction
insertAndAdd(transaction2, points3);
RevCommit transaction2Commit = transaction2.command(CommitOp.class).setMessage("Commit3")
.call();
// Verify that the base repository is unchanged
logs = geogig.command(LogOp.class).call();
assertEquals(mainCommit, logs.next());
assertFalse(logs.hasNext());
// Verify that the transaction has the commit
logs = transaction2.command(LogOp.class).call();
assertEquals(transaction2Commit, logs.next());
assertEquals(mainCommit, logs.next());
assertFalse(logs.hasNext());
// Commit the first transaction
geogig.command(TransactionEnd.class).setTransaction(transaction1).setRebase(true).call();
// Verify that the base repository has the changes from the transaction
logs = geogig.command(LogOp.class).call();
assertEquals(transaction1Commit, logs.next());
assertEquals(mainCommit, logs.next());
assertFalse(logs.hasNext());
// Now try to commit the second transaction
geogig.command(TransactionEnd.class).setTransaction(transaction2).setRebase(true).call();
// Verify that the base repository has the changes from the transaction
logs = geogig.command(LogOp.class).call();
RevCommit lastCommit = logs.next();
assertFalse(lastCommit.equals(transaction2Commit));
assertEquals(lastCommit.getMessage(), transaction2Commit.getMessage());
assertEquals(lastCommit.getAuthor(), transaction2Commit.getAuthor());
assertEquals(lastCommit.getCommitter().getName(), transaction2Commit.getCommitter()
.getName());
assertFalse(lastCommit.getCommitter().getTimestamp() == transaction2Commit.getCommitter()
.getTimestamp());
assertEquals(logs.next(), transaction1Commit);
assertEquals(logs.next(), mainCommit);
assertFalse(logs.hasNext());
}
@Test
public void testConflictIsolation() throws Exception {
insertAndAdd(points2);
geogig.command(CommitOp.class).setMessage("foo").call();
geogig.command(BranchCreateOp.class).setAutoCheckout(true).setName("branch1").call();
insertAndAdd(points1);
RevCommit mainCommit = geogig.command(CommitOp.class).setMessage("Commit1").call();
geogig.command(CheckoutOp.class).setSource("master").call();
insertAndAdd(points1_modified);
RevCommit modifiedCommit = geogig.command(CommitOp.class).setMessage("Commit2").call();
GeogigTransaction tx = geogig.command(TransactionBegin.class).call();
try {
tx.command(MergeOp.class).addCommit(Suppliers.ofInstance(mainCommit.getId())).call();
fail("Expected a merge conflict!");
} catch (org.locationtech.geogig.api.porcelain.MergeConflictsException e) {
// expected.
}
List<Conflict> txConflicts = tx.command(ConflictsReadOp.class).call();
List<Conflict> baseConflicts = geogig.command(ConflictsReadOp.class).call();
assertTrue("There should be no conflicts outside the transaction",
baseConflicts.size() == 0);
assertTrue("There should be conflicts in the transaction", txConflicts.size() != 0);
}
@Test
public void testBranchCreateCollision() throws Exception {
// make a commit
insertAndAdd(points1);
RevCommit mainCommit = geogig.command(CommitOp.class).setMessage("Commit1").call();
// start the first transaction
GeogigTransaction transaction1 = geogig.command(TransactionBegin.class).call();
// make a new branch
transaction1.command(BranchCreateOp.class).setAutoCheckout(true).setName("branch1").call();
// perform a commit in the transaction
insertAndAdd(transaction1, points2);
RevCommit transaction1Commit = transaction1.command(CommitOp.class).setMessage("Commit2")
.call();
// Verify that the base repository is unchanged
Iterator<RevCommit> logs = geogig.command(LogOp.class).call();
assertEquals(logs.next(), mainCommit);
assertFalse(logs.hasNext());
// Verify that the transaction has the commit
logs = transaction1.command(LogOp.class).call();
assertEquals(logs.next(), transaction1Commit);
assertEquals(logs.next(), mainCommit);
assertFalse(logs.hasNext());
// start the second transaction
GeogigTransaction transaction2 = geogig.command(TransactionBegin.class).call();
// make a new branch
transaction2.command(BranchCreateOp.class).setAutoCheckout(true).setName("branch1").call();
// perform a commit in the transaction
insertAndAdd(transaction2, points3);
RevCommit transaction2Commit = transaction2.command(CommitOp.class).setMessage("Commit3")
.call();
// Verify that the base repository is unchanged
logs = geogig.command(LogOp.class).call();
assertEquals(logs.next(), mainCommit);
assertFalse(logs.hasNext());
// Verify that the transaction has the commit
logs = transaction2.command(LogOp.class).call();
assertEquals(logs.next(), transaction2Commit);
assertEquals(logs.next(), mainCommit);
assertFalse(logs.hasNext());
// Commit the first transaction
geogig.command(TransactionEnd.class).setTransaction(transaction1).setRebase(true).call();
// Verify that the base repository has the changes from the transaction
logs = geogig.command(LogOp.class).call();
assertEquals(logs.next(), mainCommit);
assertFalse(logs.hasNext());
geogig.command(CheckoutOp.class).setSource("branch1").call();
logs = geogig.command(LogOp.class).call();
assertEquals(logs.next(), transaction1Commit);
assertEquals(logs.next(), mainCommit);
assertFalse(logs.hasNext());
// Now try to commit the second transaction
geogig.command(TransactionEnd.class).setTransaction(transaction2).setRebase(true).call();
// Verify that the base repository has the changes from the transaction
logs = geogig.command(LogOp.class).call();
RevCommit lastCommit = logs.next();
assertFalse(lastCommit.equals(transaction2Commit));
assertEquals(lastCommit.getMessage(), transaction2Commit.getMessage());
assertEquals(lastCommit.getAuthor(), transaction2Commit.getAuthor());
assertEquals(lastCommit.getCommitter().getName(), transaction2Commit.getCommitter()
.getName());
assertFalse(lastCommit.getCommitter().getTimestamp() == transaction2Commit.getCommitter()
.getTimestamp());
assertEquals(logs.next(), transaction1Commit);
assertEquals(logs.next(), mainCommit);
assertFalse(logs.hasNext());
}
@Test
public void testCancelTransaction() throws Exception {
LinkedList<RevCommit> expectedMain = new LinkedList<RevCommit>();
// make a commit
insertAndAdd(points1);
RevCommit commit = geogig.command(CommitOp.class).call();
expectedMain.addFirst(commit);
// start a transaction
GeogigTransaction t = geogig.command(TransactionBegin.class).call();
// perform a commit in the transaction
insertAndAdd(t, points2);
commit = t.command(CommitOp.class).call();
// Verify that the base repository is unchanged
Iterator<RevCommit> logs = geogig.command(LogOp.class).call();
List<RevCommit> logged = new ArrayList<RevCommit>();
for (; logs.hasNext();) {
logged.add(logs.next());
}
assertEquals(expectedMain, logged);
// Cancel the transaction
geogig.command(TransactionEnd.class).setCancel(true).setTransaction(t).call();
// Verify that the base repository is unchanged
logs = geogig.command(LogOp.class).call();
logged = new ArrayList<RevCommit>();
for (; logs.hasNext();) {
logged.add(logs.next());
}
assertEquals(expectedMain, logged);
}
@Test
public void testEndNoTransaction() throws Exception {
exception.expect(IllegalArgumentException.class);
geogig.command(TransactionEnd.class).call();
}
@Test
public void testEndWithinTransaction() throws Exception {
// make a commit
insertAndAdd(points1);
geogig.command(CommitOp.class).call();
// start a transaction
GeogigTransaction t = geogig.command(TransactionBegin.class).call();
// perform a commit in the transaction
insertAndAdd(t, points2);
t.command(CommitOp.class).call();
// End the transaction
exception.expect(IllegalStateException.class);
t.command(TransactionEnd.class).setTransaction(t).call();
}
@Test
public void testBeginWithinTransaction() throws Exception {
// make a commit
insertAndAdd(points1);
geogig.command(CommitOp.class).call();
// start a transaction
GeogigTransaction t = geogig.command(TransactionBegin.class).call();
// start a transaction within the transaction
exception.expect(IllegalStateException.class);
t.command(TransactionBegin.class).call();
}
@Test
public void testCommitUpdatesRemoteRefs() throws Exception {
// make a commit
insertAndAdd(points1);
RevCommit headCommit = geogig.command(CommitOp.class).call();
geogig.command(RemoteAddOp.class).setName("upstream")
.setURL("http://test.com/geogig/upstream").call();
final String remoteRef = "refs/remotes/upstream/master";
final String unchangedRemoteRef = "refs/remotes/upstream/testbranch";
Ref remoteHead = geogig.command(UpdateRef.class).setName(remoteRef)
.setNewValue(headCommit.getId()).call().get();
assertEquals(headCommit.getId(), remoteHead.getObjectId());
geogig.command(UpdateRef.class).setName(unchangedRemoteRef).setNewValue(headCommit.getId())
.call().get();
// start a transaction
GeogigTransaction tx = geogig.command(TransactionBegin.class).call();
// make a commit
insertAndAdd(tx, points2);
RevCommit newcommit = tx.command(CommitOp.class).call();
// upadte remote
Ref txRemoteHead = tx.command(UpdateRef.class).setName(remoteRef)
.setNewValue(newcommit.getId()).call().get();
assertEquals(newcommit.getId(), txRemoteHead.getObjectId());
// commit transaction
tx.commit();
txRemoteHead = geogig.command(RefParse.class).setName(remoteRef).call().get();
assertEquals(newcommit.getId(), txRemoteHead.getObjectId());
txRemoteHead = geogig.command(RefParse.class).setName(unchangedRemoteRef).call().get();
assertEquals(headCommit.getId(), txRemoteHead.getObjectId());
}
}