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:52 UTC

svn commit: r1441466 - in /jackrabbit/oak/trunk/oak-mongomk/src: main/java/org/apache/jackrabbit/mongomk/api/ main/java/org/apache/jackrabbit/mongomk/impl/ main/java/org/apache/jackrabbit/mongomk/impl/command/ main/java/org/apache/jackrabbit/mongomk/im...

Author: mduerig
Date: Fri Feb  1 14:50:51 2013
New Revision: 1441466

URL: http://svn.apache.org/viewvc?rev=1441466&view=rev
Log:
OAK-536: Implement rebase for branches in Microkernel
implement rebase() for the Mongo Microkernel

Added:
    jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/impl/command/RebaseCommand.java   (with props)
    jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/impl/MongoMKRebaseTest.java   (with props)
Modified:
    jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/api/NodeStore.java
    jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/impl/MongoMicroKernel.java
    jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/impl/MongoNodeStore.java
    jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/impl/model/tree/MongoNodeDelta.java
    jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/util/MongoUtil.java

Modified: jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/api/NodeStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/api/NodeStore.java?rev=1441466&r1=1441465&r2=1441466&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/api/NodeStore.java (original)
+++ jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/api/NodeStore.java Fri Feb  1 14:50:51 2013
@@ -125,6 +125,16 @@ public interface NodeStore {
     String merge(String branchRevisionId, String message) throws Exception;
 
     /**
+     * @see MicroKernel#rebase(String, String)
+     *
+     * @param branchRevisionId Branch revision id to rebase.
+     * @param newBaseRevisionId  New base revision to rebase onto.
+     * @return  The revision id of the rebased branch.
+     * @throws Exception If an error occurred while rebasing.
+     */
+    String rebase(String branchRevisionId, String newBaseRevisionId) throws Exception;
+
+    /**
      * @see MicroKernel#nodeExists(String, String)
      *
      * @param path The path of the node to test.

Modified: jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/impl/MongoMicroKernel.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/impl/MongoMicroKernel.java?rev=1441466&r1=1441465&r2=1441466&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/impl/MongoMicroKernel.java (original)
+++ jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/impl/MongoMicroKernel.java Fri Feb  1 14:50:51 2013
@@ -219,8 +219,13 @@ public class MongoMicroKernel implements
 
     @Nonnull
     @Override
-    public String rebase(@Nonnull String branchRevisionId, String newBaseRevisionId) {
-        throw new UnsupportedOperationException();
+    public String rebase(@Nonnull String branchRevisionId, String newBaseRevisionId)
+            throws MicroKernelException{
+        try {
+            return nodeStore.rebase(branchRevisionId, newBaseRevisionId);
+        } catch (Exception e) {
+            throw new MicroKernelException(e);
+        }
     }
 
     @Override

Modified: jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/impl/MongoNodeStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/impl/MongoNodeStore.java?rev=1441466&r1=1441465&r2=1441466&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/impl/MongoNodeStore.java (original)
+++ jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/impl/MongoNodeStore.java Fri Feb  1 14:50:51 2013
@@ -19,6 +19,10 @@ package org.apache.jackrabbit.mongomk.im
 import java.util.Collections;
 import java.util.Map;
 
+import com.mongodb.BasicDBObject;
+import com.mongodb.DB;
+import com.mongodb.DBCollection;
+import com.mongodb.DBObject;
 import org.apache.jackrabbit.mk.util.SimpleLRUCache;
 import org.apache.jackrabbit.mongomk.api.NodeStore;
 import org.apache.jackrabbit.mongomk.api.command.Command;
@@ -36,6 +40,7 @@ import org.apache.jackrabbit.mongomk.imp
 import org.apache.jackrabbit.mongomk.impl.command.MergeCommand;
 import org.apache.jackrabbit.mongomk.impl.command.NodeExistsCommand;
 import org.apache.jackrabbit.mongomk.impl.command.OneLevelDiffCommand;
+import org.apache.jackrabbit.mongomk.impl.command.RebaseCommand;
 import org.apache.jackrabbit.mongomk.impl.command.WaitForCommitCommand;
 import org.apache.jackrabbit.mongomk.impl.model.MongoCommit;
 import org.apache.jackrabbit.mongomk.impl.model.MongoNode;
@@ -44,11 +49,6 @@ import org.apache.jackrabbit.mongomk.uti
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.mongodb.BasicDBObject;
-import com.mongodb.DB;
-import com.mongodb.DBCollection;
-import com.mongodb.DBObject;
-
 /**
  * Implementation of {@link NodeStore} for the {@code MongoDB}.
  */
@@ -146,6 +146,12 @@ public class MongoNodeStore implements N
     }
 
     @Override
+    public String rebase(String branchRevisionId, String newBaseRevisionId) throws Exception {
+        RebaseCommand command = new RebaseCommand(this, branchRevisionId, newBaseRevisionId);
+        return commandExecutor.execute(command);
+    }
+
+    @Override
     public boolean nodeExists(String path, String revisionId) throws Exception {
         NodeExistsCommand command = new NodeExistsCommand(this, path,
                 MongoUtil.toMongoRepresentation(revisionId));

Added: jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/impl/command/RebaseCommand.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/impl/command/RebaseCommand.java?rev=1441466&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/impl/command/RebaseCommand.java (added)
+++ jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/impl/command/RebaseCommand.java Fri Feb  1 14:50:51 2013
@@ -0,0 +1,268 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.jackrabbit.mongomk.impl.command;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.UUID;
+
+import org.apache.jackrabbit.mk.model.tree.DiffBuilder;
+import org.apache.jackrabbit.mk.model.tree.NodeState;
+import org.apache.jackrabbit.mongomk.api.model.Node;
+import org.apache.jackrabbit.mongomk.impl.MongoNodeStore;
+import org.apache.jackrabbit.mongomk.impl.action.FetchCommitAction;
+import org.apache.jackrabbit.mongomk.impl.action.FetchHeadRevisionIdAction;
+import org.apache.jackrabbit.mongomk.impl.model.CommitBuilder;
+import org.apache.jackrabbit.mongomk.impl.model.MongoCommit;
+import org.apache.jackrabbit.mongomk.impl.model.NodeImpl;
+import org.apache.jackrabbit.mongomk.impl.model.tree.MongoNodeDelta;
+import org.apache.jackrabbit.mongomk.impl.model.tree.MongoNodeState;
+import org.apache.jackrabbit.mongomk.impl.model.tree.SimpleMongoNodeStore;
+import org.apache.jackrabbit.mongomk.util.MongoUtil;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.apache.jackrabbit.mongomk.util.MongoUtil.fromMongoRepresentation;
+import static org.apache.jackrabbit.mongomk.util.MongoUtil.getBaseRevision;
+import static org.apache.jackrabbit.mongomk.util.MongoUtil.toMongoRepresentation;
+
+/**
+ * A {@code Command} for {@link org.apache.jackrabbit.mongomk.impl.MongoMicroKernel#rebase(String, String)}
+ */
+public class RebaseCommand extends BaseCommand<String> {
+    private static final Logger LOG = LoggerFactory.getLogger(RebaseCommand.class);
+
+    private final String branchRevisionId;
+    private final String newBaseRevisionId;
+
+    public RebaseCommand(MongoNodeStore nodeStore, String branchRevisionId, String newBaseRevisionId) {
+        super(nodeStore);
+        this.branchRevisionId = branchRevisionId;
+        this.newBaseRevisionId = newBaseRevisionId;
+    }
+
+    private MongoCommit getCommit(String revisionId) throws Exception {
+        return new FetchCommitAction(nodeStore, toMongoRepresentation(revisionId)).execute();
+    }
+
+    @Override
+    public String execute() throws Exception {
+        MongoCommit branchCommit = getCommit(branchRevisionId);
+        String branchId = branchCommit.getBranchId();
+        checkArgument(branchId != null, "Can only rebase a private branch commit");
+
+        MongoCommit newBaseCommit;
+        if (newBaseRevisionId == null) {
+            Long headRevision = new FetchHeadRevisionIdAction(nodeStore).execute();
+            newBaseCommit = new FetchCommitAction(nodeStore, headRevision).execute();
+        }
+        else {
+            newBaseCommit = getCommit(newBaseRevisionId);
+        }
+        checkArgument(newBaseCommit.getBranchId() == null, "Cannot rebase onto private branch commit");
+
+        long branchHeadRev = branchCommit.getRevisionId();
+        long branchBaseRev = getBaseRevision(branchId);
+        long newBaseRev = newBaseCommit.getRevisionId();
+
+        if (newBaseRev == branchBaseRev) {
+            return branchRevisionId;
+        }
+
+        Node branchHeadRoot = getNode("/", branchHeadRev, branchId);
+        Node branchBaseRoot = getNode("/", branchBaseRev);
+        Node newBaseRoot = getNode("/", newBaseRev);
+        branchHeadRoot = rebaseNode(copy(newBaseRoot), branchBaseRoot, branchHeadRoot, "/");
+
+        String diff = new DiffBuilder(
+                MongoUtil.wrap(newBaseRoot),
+                MongoUtil.wrap(branchHeadRoot),
+                "/", -1, new SimpleMongoNodeStore(), "")
+            .build();
+
+        MongoCommit newBranchInitialCommit = (MongoCommit)CommitBuilder.build("", "",
+                fromMongoRepresentation(newBaseRev), MongoNodeStore.INITIAL_COMMIT_MESSAGE);
+        newBranchInitialCommit.setBranchId(fromMongoRepresentation(newBaseRev) + '-' + UUID.randomUUID().toString());
+        String newBranchRevId = nodeStore.commit(newBranchInitialCommit);
+
+        if (diff.isEmpty()) {
+            return newBranchRevId;
+        }
+
+        MongoCommit newCommit = (MongoCommit) CommitBuilder.build("", diff, newBranchRevId,
+                "rebased " + branchHeadRev + " onto " + newBaseRev);
+        newCommit.setBranchId(newBranchInitialCommit.getBranchId());
+        return nodeStore.commit(newCommit);
+    }
+
+    private static NodeImpl rebaseNode(Node baseNode, Node fromNode, Node toNode, String path) {
+        MongoNodeDelta theirDelta = new MongoNodeDelta(new SimpleMongoNodeStore(),
+                MongoUtil.wrap(fromNode), MongoUtil.wrap(baseNode));
+        MongoNodeDelta ourDelta = new MongoNodeDelta(new SimpleMongoNodeStore(),
+                MongoUtil.wrap(fromNode), MongoUtil.wrap(toNode));
+
+        NodeImpl stagedNode = (NodeImpl)baseNode;
+
+        // Apply our changes.
+        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, NodeState> added : ourDelta.getAddedChildNodes().entrySet()) {
+            String name = added.getKey();
+            NodeState ourState = added.getValue();
+            NodeState theirState = theirDelta.getAddedChildNodes().get(name);
+
+            if (theirState != null && !theirState.equals(ourState)) {
+                markConflict(stagedNode, "addExistingNode", name, ourState);
+            }
+            else {
+                stagedNode.addChildNodeEntry(unwrap(ourState));
+            }
+        }
+
+        for (Entry<String, NodeState> removed : ourDelta.getRemovedChildNodes().entrySet()) {
+            String name = removed.getKey();
+            NodeState ourState = removed.getValue();
+
+            if (theirDelta.getRemovedChildNodes().containsKey(name)) {
+                markConflict(stagedNode, "deleteDeletedNode", name, ourState);
+            }
+            else if (theirDelta.getChangedChildNodes().containsKey(name)) {
+                markConflict(stagedNode, "deleteChangedNode", name, ourState);
+            }
+            else {
+                stagedNode.removeChildNodeEntry(name);
+            }
+        }
+
+        for (Entry<String, NodeState> changed : ourDelta.getChangedChildNodes().entrySet()) {
+            String name = changed.getKey();
+            NodeState ourState = changed.getValue();
+
+            Node changedBase = baseNode.getChildNodeEntry(name);
+            if (changedBase == null) {
+                markConflict(stagedNode, "changeDeletedNode", name, ourState);
+                continue;
+            }
+
+            Node changedFrom = fromNode.getChildNodeEntry(name);
+            Node changedTo = toNode.getChildNodeEntry(name);
+            String changedPath = PathUtils.concat(path, name);
+            rebaseNode(changedBase, changedFrom, changedTo, changedPath);
+        }
+
+        return stagedNode;
+    }
+
+    private static Node unwrap(NodeState state) {
+        return ((MongoNodeState) state).unwrap();
+    }
+
+    private static void markConflict(NodeImpl parent, String conflictType, String name, String ourValue) {
+        NodeImpl marker = getOrAddConflictMarker(parent, conflictType);
+        marker.getProperties().put(name, ourValue);
+    }
+
+    private static void markConflict(NodeImpl parent, String conflictType, String name, NodeState ourState) {
+        NodeImpl marker = getOrAddConflictMarker(parent, conflictType);
+        marker.addChildNodeEntry(unwrap(ourState));
+    }
+
+    private static NodeImpl getOrAddConflictMarker(NodeImpl parent, String name) {
+        NodeImpl conflict = getOrAddNode(parent, ":conflict");
+        return getOrAddNode(conflict, name);
+    }
+
+    private static NodeImpl getOrAddNode(NodeImpl parent, String name) {
+        Node child = parent.getChildNodeEntry(name);
+        if (child == null) {
+            child = new NodeImpl(PathUtils.concat(parent.getPath(), name));
+            parent.addChildNodeEntry(child);
+        }
+        return (NodeImpl) child;
+    }
+
+    private Node getNode(String path, long revisionId) throws Exception {
+        return getNode(path, revisionId, null);
+    }
+
+    private Node getNode(String path, long revisionId, String branchId) throws Exception {
+        GetNodesCommand command = new GetNodesCommand(nodeStore, path, revisionId);
+        command.setBranchId(branchId);
+        return command.execute();
+    }
+
+    private static NodeImpl copy(Node node) {
+        NodeImpl copy = new NodeImpl(node.getPath());
+        copy.setRevisionId(node.getRevisionId());
+        for (Map.Entry<String, String> entry : node.getProperties().entrySet()) {
+            copy.addProperty(entry.getKey(), entry.getValue());
+        }
+        for (Iterator<Node> it = node.getChildNodeEntries(0, -1); it.hasNext(); ) {
+            Node child = it.next();
+            copy.addChildNodeEntry(copy(child));
+        }
+        return copy;
+    }
+
+}

Propchange: jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/impl/command/RebaseCommand.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/impl/command/RebaseCommand.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev URL

Modified: jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/impl/model/tree/MongoNodeDelta.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/impl/model/tree/MongoNodeDelta.java?rev=1441466&r1=1441465&r2=1441466&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/impl/model/tree/MongoNodeDelta.java (original)
+++ jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/impl/model/tree/MongoNodeDelta.java Fri Feb  1 14:50:51 2013
@@ -21,7 +21,6 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import org.apache.jackrabbit.mk.model.Id;
 import org.apache.jackrabbit.mk.model.tree.NodeState;
 import org.apache.jackrabbit.mk.model.tree.NodeStateDiff;
 import org.apache.jackrabbit.mk.model.tree.NodeStore;
@@ -63,7 +62,7 @@ public class MongoNodeDelta {
     Map<String, String> changedProperties = new HashMap<String, String>();
 
     Map<String, NodeState> addedChildNodes = new HashMap<String, NodeState>();
-    Map<String, Id> removedChildNodes = new HashMap<String, Id>();
+    Map<String, NodeState> removedChildNodes = new HashMap<String, NodeState>();
     Map<String, NodeState> changedChildNodes = new HashMap<String, NodeState>();
 
     public MongoNodeDelta(NodeStore provider, NodeState node1, NodeState node2) {
@@ -88,7 +87,7 @@ public class MongoNodeDelta {
         return addedChildNodes;
     }
 
-    public Map<String, Id> getRemovedChildNodes() {
+    public Map<String, NodeState> getRemovedChildNodes() {
         return removedChildNodes;
     }
 
@@ -154,7 +153,7 @@ public class MongoNodeDelta {
 
         //Map<String, Id> otherChangedChildNodes = other.getChangedChildNodes();
         Map<String, NodeState> otherChangedChildNodes = other.getChangedChildNodes();
-        Map<String, Id> otherRemovedChildNodes = other.getRemovedChildNodes();
+        Map<String, NodeState> otherRemovedChildNodes = other.getRemovedChildNodes();
         for (Map.Entry<String, NodeState> changed : changedChildNodes.entrySet()) {
             NodeState otherValue = otherChangedChildNodes.get(changed.getKey());
             if (otherValue != null && !changed.getValue().equals(otherValue)) {
@@ -167,7 +166,7 @@ public class MongoNodeDelta {
             }
         }
 
-        for (Map.Entry<String, Id> removed : removedChildNodes.entrySet()) {
+        for (Map.Entry<String, NodeState> removed : removedChildNodes.entrySet()) {
             if (otherChangedChildNodes.containsKey(removed.getKey())) {
                 // removed child node entry has been changed
                 conflicts.add(new Conflict(ConflictType.REMOVED_DIRTY_NODE_CONFLICT, removed.getKey()));
@@ -209,7 +208,7 @@ public class MongoNodeDelta {
 
         @Override
         public void childNodeDeleted(String name, NodeState before) {
-            removedChildNodes.put(name, null /*provider.getId(before)*/);
+            removedChildNodes.put(name, before /*provider.getId(before)*/);
         }
     }
 

Modified: jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/util/MongoUtil.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/util/MongoUtil.java?rev=1441466&r1=1441465&r2=1441466&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/util/MongoUtil.java (original)
+++ jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/util/MongoUtil.java Fri Feb  1 14:50:51 2013
@@ -20,6 +20,8 @@ import org.apache.jackrabbit.mk.model.tr
 import org.apache.jackrabbit.mongomk.api.model.Node;
 import org.apache.jackrabbit.mongomk.impl.model.tree.MongoNodeState;
 
+import static com.google.common.base.Preconditions.checkArgument;
+
 /**
  * MongoMK specific utility class.
  */
@@ -40,6 +42,12 @@ public class MongoUtil {
         }
     }
 
+    public static long getBaseRevision(String branchId) {
+        int i = branchId.indexOf('-');
+        checkArgument(i >= 0, "Not a branch id: " + branchId);
+        return Long.parseLong(branchId.substring(0, i));
+    }
+
     public static NodeState wrap(Node node) {
         return node != null? new MongoNodeState(node) : null;
     }

Added: jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/impl/MongoMKRebaseTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/impl/MongoMKRebaseTest.java?rev=1441466&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/impl/MongoMKRebaseTest.java (added)
+++ jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/impl/MongoMKRebaseTest.java Fri Feb  1 14:50:51 2013
@@ -0,0 +1,347 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.mongomk.impl;
+
+import org.apache.jackrabbit.mongomk.BaseMongoMicroKernelTest;
+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;
+
+/**
+ * Tests for {@code MicroKernel#rebase}
+ * FIXME: this is copied from MicroKernelImplTest. Factor out.
+ */
+public class MongoMKRebaseTest extends BaseMongoMicroKernelTest {
+
+    @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 rebaseAddExistingProperty() {
+        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"));
+    }
+
+}
\ No newline at end of file

Propchange: jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/impl/MongoMKRebaseTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/impl/MongoMKRebaseTest.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev URL