You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by md...@apache.org on 2013/02/01 15:50:25 UTC
svn commit: r1441465 - in /jackrabbit/oak/trunk/oak-mk/src:
main/java/org/apache/jackrabbit/mk/core/
main/java/org/apache/jackrabbit/mk/model/ test/java/org/apache/jackrabbit/mk/
Author: mduerig
Date: Fri Feb 1 14:50:24 2013
New Revision: 1441465
URL: http://svn.apache.org/viewvc?rev=1441465&view=rev
Log:
OAK-536: Implement rebase for branches in Microkernel
implement rebase() for the H2 Microkernel
Modified:
jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/core/MicroKernelImpl.java
jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/core/Repository.java
jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/CommitBuilder.java
jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/StagedNodeTree.java
jackrabbit/oak/trunk/oak-mk/src/test/java/org/apache/jackrabbit/mk/MicroKernelImplTest.java
Modified: jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/core/MicroKernelImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/core/MicroKernelImpl.java?rev=1441465&r1=1441464&r2=1441465&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/core/MicroKernelImpl.java (original)
+++ jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/core/MicroKernelImpl.java Fri Feb 1 14:50:24 2013
@@ -20,8 +20,6 @@ import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
-import javax.annotation.Nonnull;
-
import org.apache.jackrabbit.mk.api.MicroKernel;
import org.apache.jackrabbit.mk.api.MicroKernelException;
import org.apache.jackrabbit.mk.json.JsonObject;
@@ -126,6 +124,15 @@ public class MicroKernelImpl implements
}
}
+ private Id getBaseRevisionId(Id branchId) throws MicroKernelException {
+ try {
+ return rep.getBaseRevision(branchId);
+ }
+ catch (Exception e) {
+ throw new MicroKernelException(e);
+ }
+ }
+
public String getRevisionHistory(long since, int maxEntries, String path) throws MicroKernelException {
if (rep == null) {
throw new IllegalStateException("this instance has already been disposed");
@@ -542,10 +549,26 @@ public class MicroKernelImpl implements
}
}
- @Nonnull
@Override
- public String rebase(@Nonnull String branchRevisionId, String newBaseRevisionId) {
- throw new UnsupportedOperationException();
+ public String rebase(String branchRevisionId, String newBaseRevisionId) {
+ Id branchId = Id.fromString(branchRevisionId);
+ Id baseId = getBaseRevisionId(branchId);
+ Id newBaseId = newBaseRevisionId == null ? getHeadRevisionId() : Id.fromString(newBaseRevisionId);
+
+ if (baseId.equals(newBaseId)) {
+ return branchRevisionId;
+ }
+ else {
+ Id newBranchId = Id.fromString(branch(newBaseRevisionId));
+ try {
+ CommitBuilder cb = rep.getCommitBuilder(newBranchId,
+ "rebasing " + branchRevisionId + " onto " + newBaseRevisionId);
+ return cb.rebase(baseId, branchId).toString();
+ }
+ catch (Exception e) {
+ throw new MicroKernelException(e);
+ }
+ }
}
public long getLength(String blobId) throws MicroKernelException {
Modified: jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/core/Repository.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/core/Repository.java?rev=1441465&r1=1441464&r2=1441465&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/core/Repository.java (original)
+++ jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/core/Repository.java Fri Feb 1 14:50:24 2013
@@ -16,6 +16,9 @@
*/
package org.apache.jackrabbit.mk.core;
+import java.io.Closeable;
+import java.io.File;
+
import org.apache.jackrabbit.mk.blobs.BlobStore;
import org.apache.jackrabbit.mk.blobs.FileBlobStore;
import org.apache.jackrabbit.mk.blobs.MemoryBlobStore;
@@ -31,9 +34,6 @@ import org.apache.jackrabbit.mk.store.Re
import org.apache.jackrabbit.mk.util.IOUtils;
import org.apache.jackrabbit.oak.commons.PathUtils;
-import java.io.Closeable;
-import java.io.File;
-
/**
*
*/
@@ -144,6 +144,14 @@ public class Repository {
return rs.getHeadCommitId();
}
+ public Id getBaseRevision(Id branchRevision) throws Exception {
+ if (!initialized) {
+ throw new IllegalStateException("not initialized");
+ }
+ StoredCommit commit = rs.getCommit(branchRevision);
+ return commit == null ? null : commit.getBranchRootId();
+ }
+
public StoredCommit getHeadCommit() throws Exception {
if (!initialized) {
throw new IllegalStateException("not initialized");
Modified: jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/CommitBuilder.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/CommitBuilder.java?rev=1441465&r1=1441464&r2=1441465&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/CommitBuilder.java (original)
+++ jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/CommitBuilder.java Fri Feb 1 14:50:24 2013
@@ -16,6 +16,9 @@
*/
package org.apache.jackrabbit.mk.model;
+import java.util.ArrayList;
+import java.util.List;
+
import org.apache.jackrabbit.mk.json.JsonObject;
import org.apache.jackrabbit.mk.json.JsopBuilder;
import org.apache.jackrabbit.mk.model.tree.DiffBuilder;
@@ -25,9 +28,6 @@ import org.apache.jackrabbit.oak.commons
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.util.ArrayList;
-import java.util.List;
-
/**
*
*/
@@ -194,6 +194,39 @@ public class CommitBuilder {
return newRevId;
}
+ public Id rebase(Id fromId, Id toId) throws Exception {
+ RevisionStore.PutToken token = store.createPutToken();
+
+ Id rebasedId = stagedTree.rebase(baseRevId, fromId, toId, token);
+
+ if (store.getCommit(toId).getRootNodeId().equals(rebasedId)) {
+ // the rebase didn't cause any changes,
+ // no need to create new commit object/update head revision
+ return toId;
+ }
+
+ StoredCommit baseCommit = store.getCommit(baseRevId);
+ MutableCommit newCommit = new MutableCommit();
+ newCommit.setParentId(baseRevId);
+ newCommit.setCommitTS(System.currentTimeMillis());
+ newCommit.setMsg(msg);
+ // dynamically build diff for rebased commit
+ String diff = new DiffBuilder(
+ store.getNodeState(store.getRootNode(toId)),
+ store.getNodeState(store.getNode(rebasedId)),
+ "/", -1, store, "").build();
+ newCommit.setChanges(diff);
+ newCommit.setRootNodeId(rebasedId);
+ newCommit.setBranchRootId(baseCommit.getBranchRootId());
+ Id newRevId = store.putCommit(token, newCommit);
+
+ // reset instance
+ stagedTree.reset(newRevId);
+ changeLog.clear();
+
+ return newRevId;
+ }
+
public Id /* new revId */ doMerge() throws Exception {
StoredCommit branchCommit = store.getCommit(baseRevId);
Id branchRootId = branchCommit.getBranchRootId();
Modified: jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/StagedNodeTree.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/StagedNodeTree.java?rev=1441465&r1=1441464&r2=1441465&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/StagedNodeTree.java (original)
+++ jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/StagedNodeTree.java Fri Feb 1 14:50:24 2013
@@ -16,16 +16,18 @@
*/
package org.apache.jackrabbit.mk.model;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
import org.apache.jackrabbit.mk.json.JsonObject;
import org.apache.jackrabbit.mk.model.tree.NodeDelta;
import org.apache.jackrabbit.mk.store.NotFoundException;
import org.apache.jackrabbit.mk.store.RevisionStore;
+import org.apache.jackrabbit.mk.store.RevisionStore.PutToken;
import org.apache.jackrabbit.oak.commons.PathUtils;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
/**
* A {@code StagedNodeTree} provides methods to manipulate a specific revision
* of the tree. The changes are recorded and can be persisted by calling
@@ -80,6 +82,21 @@ public class StagedNodeTree {
return root != null ? root.persist(token) : null;
}
+ public Id rebase(Id baseId, Id fromId, Id toId, PutToken token) throws Exception {
+ // reset staging area to new base revision
+ reset(baseId);
+
+ StoredNode baseRoot = store.getRootNode(baseId);
+ StoredNode fromRoot = store.getRootNode(fromId);
+ StoredNode toRoot = store.getRootNode(toId);
+
+ // recursively apply changes from 'fromRoot' to 'toRoot' onto 'baseRoot'
+ rebaseNode(baseRoot, fromRoot, toRoot, "/");
+
+ // persist staged nodes
+ return persist(token);
+ }
+
/**
* Performs a three-way merge merging <i>our</i> tree (rooted at {@code ourRoot})
* and <i>their</i> tree (identified by {@code newBaseRevisionId}),
@@ -362,6 +379,142 @@ public class StagedNodeTree {
return node;
}
+ private void rebaseNode(StoredNode base, StoredNode from, StoredNode to, String path)
+ throws Exception {
+ assert from != null;
+ assert to != null;
+ assert base != null;
+
+ NodeDelta theirDelta = new NodeDelta(store, store.getNodeState(from), store.getNodeState(base));
+ NodeDelta ourDelta = new NodeDelta(store, store.getNodeState(from), store.getNodeState(to));
+
+ // apply the changes
+ StagedNode stagedNode = getStagedNode(path, true);
+
+ for (Entry<String, String> added : ourDelta.getAddedProperties().entrySet()) {
+ String name = added.getKey();
+ String ourValue = added.getValue();
+ String theirValue = theirDelta.getAddedProperties().get(name);
+
+ if (theirValue != null && !theirValue.equals(ourValue)) {
+ markConflict(stagedNode, "addExistingProperty", name, ourValue);
+ }
+ else {
+ stagedNode.getProperties().put(name, ourValue);
+ }
+ }
+
+ for (Entry<String, String> removed : ourDelta.getRemovedProperties().entrySet()) {
+ String name = removed.getKey();
+ String ourValue = removed.getValue();
+
+ if (theirDelta.getRemovedProperties().containsKey(name)) {
+ markConflict(stagedNode, "deleteDeletedProperty", name, ourValue);
+ }
+ else if (theirDelta.getChangedProperties().containsKey(name)) {
+ markConflict(stagedNode, "deleteChangedProperty", name, ourValue);
+ }
+ else {
+ stagedNode.getProperties().remove(name);
+ }
+ }
+
+ for (Entry<String, String> changed : ourDelta.getChangedProperties().entrySet()) {
+ String name = changed.getKey();
+ String ourValue = changed.getValue();
+ String theirValue = theirDelta.getChangedProperties().get(name);
+
+ if (theirDelta.getRemovedProperties().containsKey(name)) {
+ markConflict(stagedNode, "changeDeletedProperty", name, ourValue);
+ }
+ else if (theirValue != null && !theirValue.equals(ourValue)) {
+ markConflict(stagedNode, "changeChangedProperty", name, ourValue);
+ }
+ else {
+ stagedNode.getProperties().put(name, ourValue);
+ }
+ }
+
+ for (Entry<String, Id> added : ourDelta.getAddedChildNodes().entrySet()) {
+ String name = added.getKey();
+ Id ourId = added.getValue();
+ Id theirId = theirDelta.getAddedChildNodes().get(name);
+
+ if (theirId != null && !theirId.equals(ourId)) {
+ markConflict(stagedNode, "addExistingNode", name, ourId);
+ }
+ else {
+ stagedNode.add(new ChildNodeEntry(name, ourId));
+ }
+ }
+
+ for (Entry<String, Id> removed : ourDelta.getRemovedChildNodes().entrySet()) {
+ String name = removed.getKey();
+ Id ourId = removed.getValue();
+
+ if (theirDelta.getRemovedChildNodes().containsKey(name)) {
+ markConflict(stagedNode, "deleteDeletedNode", name, ourId);
+ }
+ else if (theirDelta.getChangedChildNodes().containsKey(name)) {
+ markConflict(stagedNode, "deleteChangedNode", name, ourId);
+ }
+ else {
+ stagedNode.remove(name);
+ }
+ }
+
+ for (Entry<String, Id> changed : ourDelta.getChangedChildNodes().entrySet()) {
+ String name = changed.getKey();
+ Id ourId = changed.getValue();
+
+ StoredNode changedBase = getChildNode(base, name);
+ if (changedBase == null) {
+ markConflict(stagedNode, "changeDeletedNode", name, ourId);
+ continue;
+ }
+
+ StoredNode changedFrom = getChildNode(from, name);
+ StoredNode changedTo = getChildNode(to, name);
+ String changedPath = PathUtils.concat(path, name);
+ rebaseNode(changedBase, changedFrom, changedTo, changedPath);
+ }
+ }
+
+ private void markConflict(StagedNode parent, String conflictType, String name, String ourValue) {
+ StagedNode marker = getOrAddConflictMarker(parent, conflictType);
+ marker.getProperties().put(name, ourValue);
+ }
+
+ private void markConflict(StagedNode parent, String conflictType, String name, Id ourId) {
+ StagedNode marker = getOrAddConflictMarker(parent, conflictType);
+ marker.add(new ChildNodeEntry(name, ourId));
+ }
+
+ private StagedNode getOrAddConflictMarker(StagedNode parent, String name) {
+ StagedNode conflict = getOrAddNode(parent, ":conflict");
+ return getOrAddNode(conflict, name);
+ }
+
+ private StagedNode getOrAddNode(StagedNode parent, String name) {
+ ChildNodeEntry cne = parent.getChildNodeEntry(name);
+ if (cne == null) {
+ return parent.add(name, new StagedNode(store));
+ } else {
+ try {
+ return parent.getStagedChildNode(name, true);
+ }
+ catch (Exception e) {
+ // should never happen
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+
+ private StoredNode getChildNode(StoredNode parent, String name) throws Exception {
+ ChildNodeEntry cne = parent.getChildNodeEntry(name);
+ return cne == null ? null : store.getNode(cne.getId());
+ }
+
/**
* Performs a three-way merge of the trees rooted at {@code ourRoot},
* {@code theirRoot}, using the tree at {@code baseRoot} as reference.
Modified: jackrabbit/oak/trunk/oak-mk/src/test/java/org/apache/jackrabbit/mk/MicroKernelImplTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/test/java/org/apache/jackrabbit/mk/MicroKernelImplTest.java?rev=1441465&r1=1441464&r2=1441465&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/test/java/org/apache/jackrabbit/mk/MicroKernelImplTest.java (original)
+++ jackrabbit/oak/trunk/oak-mk/src/test/java/org/apache/jackrabbit/mk/MicroKernelImplTest.java Fri Feb 1 14:50:24 2013
@@ -26,6 +26,8 @@ import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
public class MicroKernelImplTest {
@@ -74,4 +76,319 @@ public class MicroKernelImplTest {
assertEquals("+\"/b\":{\"l\":1,\"x\":{\"l\":2,\"y\":{}}}", mk.diff(r1, r2, "/b", -1).trim());
}
+
+ @Test
+ public void rebaseWithoutChanges() {
+ String branch = mk.branch(null);
+ String rebased = mk.rebase(branch, null);
+ assertEquals(branch, rebased);
+ }
+
+ @Test
+ public void fastForwardRebase() {
+ String branch = mk.branch(null);
+ branch = mk.commit("", "+\"/a\":{}", branch, null);
+ String rebased = mk.rebase(branch, null);
+ assertEquals(branch, rebased);
+ }
+
+ @Test
+ public void rebaseEmptyBranch() {
+ String branch = mk.branch(null);
+ String trunk = mk.commit("", "+\"/a\":{}", null, null);
+ String rebased = mk.rebase(branch, null);
+
+ assertEquals("{\":childNodeCount\":1,\"a\":{}}", mk.getNodes("/", rebased, 0, 0, -1, null));
+ assertEquals("{\":childNodeCount\":1,\"a\":{}}", mk.getNodes("/", null, 0, 0, -1, null));
+ assertEquals(trunk, mk.getHeadRevision());
+ assertFalse((trunk.equals(rebased)));
+ }
+
+ @Test
+ public void rebaseAddNode() {
+ mk.commit("", "+\"/x\":{}", null, null);
+ String branch = mk.branch(null);
+ branch = mk.commit("", "+\"/x/b\":{}", branch, null);
+ String trunk = mk.commit("", "+\"/x/a\":{}", null, null);
+ String rebased = mk.rebase(branch, null);
+
+ assertEquals(1, mk.getChildNodeCount("/x", null));
+ assertNotNull(mk.getNodes("/x/a", null, 0, 0, -1, null));
+
+ assertEquals(1, mk.getChildNodeCount("/x", branch));
+ assertNotNull(mk.getNodes("/x/b", branch, 0, 0, -1, null));
+
+ assertEquals(2, mk.getChildNodeCount("/x", rebased));
+ assertNotNull(mk.getNodes("/x/a", rebased, 0, 0, -1, null));
+ assertNotNull(mk.getNodes("/x/b", rebased, 0, 0, -1, null));
+ }
+
+ @Test
+ public void rebaseRemoveNode() {
+ mk.commit("", "+\"/x\":{\"y\":{}}", null, null);
+ String branch = mk.branch(null);
+ branch = mk.commit("", "-\"/x/y\"", branch, null);
+ String trunk = mk.commit("", "+\"/x/a\":{}", null, null);
+ String rebased = mk.rebase(branch, null);
+
+ assertEquals(2, mk.getChildNodeCount("/x", null));
+ assertNotNull(mk.getNodes("/x/a", null, 0, 0, -1, null));
+ assertNotNull(mk.getNodes("/x/y", null, 0, 0, -1, null));
+
+ assertEquals(0, mk.getChildNodeCount("/x", branch));
+
+ assertEquals(1, mk.getChildNodeCount("/x", rebased));
+ assertNotNull(mk.getNodes("/x/a", rebased, 0, 0, -1, null));
+ }
+
+ @Test
+ public void rebaseAddProperty() {
+ mk.commit("", "+\"/x\":{\"y\":{}}", null, null);
+ String branch = mk.branch(null);
+ branch = mk.commit("", "^\"/x/y/p\":42", branch, null);
+ String trunk = mk.commit("", "^\"/x/y/q\":99", null, null);
+ String rebased = mk.rebase(branch, null);
+
+ String branchNode = mk.getNodes("/x/y", branch, 0, 0, -1, null);
+ assertTrue(branchNode.contains("\"p\":42"));
+ assertFalse(branchNode.contains("\"q\":99"));
+
+ String rebasedNode = mk.getNodes("/x/y", rebased, 0, 0, -1, null);
+ assertTrue(rebasedNode.contains("\"p\":42"));
+ assertTrue(rebasedNode.contains("\"q\":99"));
+
+ String trunkNode = mk.getNodes("/x/y", null, 0, 0, -1, null);
+ assertFalse(trunkNode.contains("\"p\":42"));
+ assertTrue(trunkNode.contains("\"q\":99"));
+ }
+
+ @Test
+ public void rebaseRemoveProperty() {
+ mk.commit("", "+\"/x\":{\"y\":{\"p\":42}}", null, null);
+ String branch = mk.branch(null);
+ branch = mk.commit("", "^\"/x/y/p\":null", branch, null);
+ String trunk = mk.commit("", "^\"/x/y/q\":99", null, null);
+ String rebased = mk.rebase(branch, null);
+
+ String branchNode = mk.getNodes("/x/y", branch, 0, 0, -1, null);
+ assertFalse(branchNode.contains("\"p\":42"));
+ assertFalse(branchNode.contains("\"q\":99"));
+
+ String rebasedNode = mk.getNodes("/x/y", rebased, 0, 0, -1, null);
+ assertFalse(rebasedNode.contains("\"p\":42"));
+ assertTrue(rebasedNode.contains("\"q\":99"));
+
+ String trunkNode = mk.getNodes("/x/y", null, 0, 0, -1, null);
+ assertTrue(trunkNode.contains("\"p\":42"));
+ assertTrue(trunkNode.contains("\"q\":99"));
+ }
+
+ @Test
+ public void rebaseChangeProperty() {
+ mk.commit("", "+\"/x\":{\"y\":{\"p\":42}}", null, null);
+ String branch = mk.branch(null);
+ branch = mk.commit("", "^\"/x/y/p\":41", branch, null);
+ String trunk = mk.commit("", "^\"/x/y/q\":99", null, null);
+ String rebased = mk.rebase(branch, null);
+
+ String branchNode = mk.getNodes("/x/y", branch, 0, 0, -1, null);
+ assertTrue(branchNode.contains("\"p\":41"));
+ assertFalse(branchNode.contains("\"q\":99"));
+
+ String rebasedNode = mk.getNodes("/x/y", rebased, 0, 0, -1, null);
+ assertTrue(rebasedNode.contains("\"p\":41"));
+ assertTrue(rebasedNode.contains("\"q\":99"));
+
+ String trunkNode = mk.getNodes("/x/y", null, 0, 0, -1, null);
+ assertTrue(trunkNode.contains("\"p\":42"));
+ assertTrue(trunkNode.contains("\"q\":99"));
+ }
+
+ @Test
+ public void rebaseChangePropertyWithSameValue() {
+ mk.commit("", "+\"/x\":{\"y\":{\"p\":42}}", null, null);
+ String branch = mk.branch(null);
+ branch = mk.commit("", "^\"/x/y/p\":99", branch, null);
+ String trunk = mk.commit("", "^\"/x/y/p\":99", null, null);
+ String rebased = mk.rebase(branch, null);
+
+ String branchNode = mk.getNodes("/x/y", branch, 0, 0, -1, null);
+ assertTrue(branchNode.contains("\"p\":99"));
+
+ String rebasedNode = mk.getNodes("/x/y", branch, 0, 0, -1, null);
+ assertTrue(rebasedNode.contains("\"p\":99"));
+
+ String trunkNode = mk.getNodes("/x/y", null, 0, 0, -1, null);
+ assertTrue(trunkNode.contains("\"p\":99"));
+ }
+
+ @Test
+ public void rebaseAddExistingNode() {
+ mk.commit("", "+\"/x\":{}", null, null);
+ String branch = mk.branch(null);
+ branch = mk.commit("", "+\"/x/a\":{}", branch, null);
+ mk.commit("", "+\"/x/a\":{\"b\":{}}", null, null);
+
+ branch = mk.rebase(branch, null);
+
+ assertTrue(mk.nodeExists("/x/a/b", branch));
+ String conflict = mk.getNodes("/x/:conflict", branch, 100, 0, -1, null);
+ assertEquals(
+ "{\":childNodeCount\":1,\"addExistingNode\":{\":childNodeCount\":1,\"a\":{\":childNodeCount\":0}}}",
+ conflict);
+ }
+
+ @Test
+ public void rebaseAddExistingProperties() {
+ mk.commit("", "+\"/x\":{\"y\":{}}", null, null);
+ String branch = mk.branch(null);
+ branch = mk.commit("", "^\"/x/y/p\":42 ^\"/x/y/q\":42", branch, null);
+ mk.commit("", "^\"/x/y/p\":99 ^\"/x/y/q\":99", null, null);
+
+ branch = mk.rebase(branch, null);
+
+ String branchNode = mk.getNodes("/x/y", branch, 0, 0, -1, null);
+ assertTrue(branchNode.contains("\"p\":99"));
+ String conflict = mk.getNodes("/x/y/:conflict", branch, 100, 0, -1, null);
+ assertEquals(
+ "{\":childNodeCount\":1,\"addExistingProperty\":{\"q\":42,\"p\":42,\":childNodeCount\":0}}",
+ conflict);
+ }
+
+ @Test
+ public void rebaseChangeRemovedProperty() {
+ mk.commit("", "+\"/x\":{\"y\":{\"p\":42}}", null, null);
+ String branch = mk.branch(null);
+ branch = mk.commit("", "^\"/x/y/p\":99", branch, null);
+ mk.commit("", "^\"/x/y/p\":null", null, null);
+
+ branch = mk.rebase(branch, null);
+
+ String branchNode = mk.getNodes("/x/y", branch, 0, 0, -1, null);
+ assertFalse(branchNode.contains("\"p\":99"));
+ String conflict = mk.getNodes("/x/y/:conflict", branch, 100, 0, -1, null);
+ assertEquals(
+ "{\":childNodeCount\":1,\"changeDeletedProperty\":{\"p\":99,\":childNodeCount\":0}}",
+ conflict);
+ }
+
+ @Test
+ public void rebaseRemoveChangedProperty() {
+ mk.commit("", "+\"/x\":{\"y\":{\"p\":42}}", null, null);
+ String branch = mk.branch(null);
+ branch = mk.commit("", "^\"/x/y/p\":null", branch, null);
+ mk.commit("", "^\"/x/y/p\":99", null, null);
+
+ branch = mk.rebase(branch, null);
+
+ String branchNode = mk.getNodes("/x/y", branch, 0, 0, -1, null);
+ assertTrue(branchNode.contains("\"p\":99"));
+ String conflict = mk.getNodes("/x/y/:conflict", branch, 100, 0, -1, null);
+ assertEquals(
+ "{\":childNodeCount\":1,\"deleteChangedProperty\":{\"p\":42,\":childNodeCount\":0}}",
+ conflict);
+ }
+
+ @Test
+ public void rebaseChangedChangedProperty() {
+ mk.commit("", "+\"/x\":{\"y\":{\"p\":42}}", null, null);
+ String branch = mk.branch(null);
+ branch = mk.commit("", "^\"/x/y/p\":41", branch, null);
+ mk.commit("", "^\"/x/y/p\":99", null, null);
+
+ branch = mk.rebase(branch, null);
+
+ String branchNode = mk.getNodes("/x/y", branch, 0, 0, -1, null);
+ assertTrue(branchNode.contains("\"p\":99"));
+ String conflict = mk.getNodes("/x/y/:conflict", branch, 100, 0, -1, null);
+ assertEquals(
+ "{\":childNodeCount\":1,\"changeChangedProperty\":{\"p\":41,\":childNodeCount\":0}}",
+ conflict);
+ }
+
+ @Test
+ public void rebaseRemoveChangedNode() {
+ mk.commit("", "+\"/x\":{\"y\":{}}", null, null);
+ String branch = mk.branch(null);
+ branch = mk.commit("", "-\"/x/y\"", branch, null);
+ mk.commit("", "^\"/x/y/p\":42", null, null);
+
+ branch = mk.rebase(branch, null);
+
+ String branchNode = mk.getNodes("/x/y", branch, 0, 0, -1, null);
+ assertTrue(branchNode.contains("\"p\":42"));
+ String conflict = mk.getNodes("/x/:conflict", branch, 100, 0, -1, null);
+ assertEquals(
+ "{\":childNodeCount\":1,\"deleteChangedNode\":{\":childNodeCount\":1,\"y\":{\":childNodeCount\":0}}}",
+ conflict);
+ }
+
+ @Test
+ public void rebaseChangeRemovedNode() {
+ mk.commit("", "+\"/x\":{\"y\":{}}", null, null);
+ String branch = mk.branch(null);
+ branch = mk.commit("", "^\"/x/p\":42", branch, null);
+ mk.commit("", "-\"/x\"", null, null);
+
+ branch = mk.rebase(branch, null);
+
+ assertFalse(mk.nodeExists("/x", branch));
+ String conflict = mk.getNodes("/:conflict", branch, 100, 0, -1, null);
+ assertEquals(
+ "{\":childNodeCount\":1,\"changeDeletedNode\":{\":childNodeCount\":1,\"x\":{\"p\":42,\"" +
+ ":childNodeCount\":1,\"y\":{\":childNodeCount\":0}}}}",
+ conflict);
+ }
+
+ @Test
+ public void rebaseRemoveRemovedProperty() {
+ mk.commit("", "+\"/x\":{\"y\":{\"p\":42}}", null, null);
+ String branch = mk.branch(null);
+ branch = mk.commit("", "^\"/x/y/p\":null", branch, null);
+ mk.commit("", "^\"/x/y/p\":null", null, null);
+
+ branch = mk.rebase(branch, null);
+
+ String branchNode = mk.getNodes("/x/y", branch, 0, 0, -1, null);
+ assertFalse(branchNode.contains("\"p\":42"));
+ String conflict = mk.getNodes("/x/y/:conflict", branch, 100, 0, -1, null);
+ assertEquals(
+ "{\":childNodeCount\":1,\"deleteDeletedProperty\":{\"p\":42,\":childNodeCount\":0}}",
+ conflict);
+ }
+
+ @Test
+ public void rebaseRemoveRemovedNode() {
+ mk.commit("", "+\"/x\":{\"y\":{}}", null, null);
+ String branch = mk.branch(null);
+ branch = mk.commit("", "-\"/x/y\"", branch, null);
+ mk.commit("", "-\"/x/y\"", null, null);
+
+ branch = mk.rebase(branch, null);
+
+ assertFalse(mk.nodeExists("/x/y", branch));
+ String conflict = mk.getNodes("/x/:conflict", branch, 100, 0, -1, null);
+ assertEquals(
+ "{\":childNodeCount\":1,\"deleteDeletedNode\":{\":childNodeCount\":1,\"y\":{\":childNodeCount\":0}}}",
+ conflict);
+ }
+
+ @Test
+ public void mergeRebased() {
+ mk.commit("", "+\"/x\":{\"y\":{}}", null, null);
+ String branch = mk.branch(null);
+ String trunk = mk.commit("", "^\"/x/p\":42", null, null);
+ branch = mk.commit("", "^\"/x/q\":43", branch, null);
+ branch = mk.rebase(branch, null);
+
+ String branchNode = mk.getNodes("/x", branch, 0, 0, -1, null);
+ assertTrue(branchNode.contains("\"p\":42"));
+ assertTrue(branchNode.contains("\"q\":43"));
+
+ mk.merge(branch, null);
+ String trunkNode = mk.getNodes("/x", branch, 0, 0, -1, null);
+ assertTrue(trunkNode.contains("\"p\":42"));
+ assertTrue(trunkNode.contains("\"q\":43"));
+ }
+
}