diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/Oak.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/Oak.java index 0fa9990be7..700fb9412d 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/Oak.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/Oak.java @@ -85,11 +85,13 @@ import org.apache.jackrabbit.oak.spi.commit.CommitInfo; import org.apache.jackrabbit.oak.spi.commit.CompositeEditorProvider; import org.apache.jackrabbit.oak.spi.commit.CompositeHook; import org.apache.jackrabbit.oak.spi.commit.ConflictHandler; +import org.apache.jackrabbit.oak.spi.commit.ConflictHandlers; import org.apache.jackrabbit.oak.spi.commit.Editor; import org.apache.jackrabbit.oak.spi.commit.EditorHook; import org.apache.jackrabbit.oak.spi.commit.EditorProvider; import org.apache.jackrabbit.oak.spi.commit.Observable; import org.apache.jackrabbit.oak.spi.commit.Observer; +import org.apache.jackrabbit.oak.spi.commit.ThreeWayConflictHandler; import org.apache.jackrabbit.oak.spi.lifecycle.CompositeInitializer; import org.apache.jackrabbit.oak.spi.lifecycle.RepositoryInitializer; import org.apache.jackrabbit.oak.spi.lifecycle.WorkspaceInitializer; @@ -492,8 +494,14 @@ public class Oak { * @param conflictHandler conflict handler * @return this builder */ + @Deprecated @Nonnull public Oak with(@Nonnull ConflictHandler conflictHandler) { + return with(ConflictHandlers.wrap(conflictHandler)); + } + + @Nonnull + public Oak with(@Nonnull ThreeWayConflictHandler conflictHandler) { checkNotNull(conflictHandler); withEditorHook(); commitHooks.add(new ConflictHook(conflictHandler)); diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/commit/AnnotatingConflictHandler.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/commit/AnnotatingConflictHandler.java index b6f8e0b9c1..88fb08e0a3 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/commit/AnnotatingConflictHandler.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/commit/AnnotatingConflictHandler.java @@ -37,14 +37,14 @@ import java.util.List; import org.apache.jackrabbit.oak.api.PropertyState; import org.apache.jackrabbit.oak.api.Type; import org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants; -import org.apache.jackrabbit.oak.spi.commit.ConflictHandler; +import org.apache.jackrabbit.oak.spi.commit.ThreeWayConflictHandler; import org.apache.jackrabbit.oak.spi.state.ConflictType; import org.apache.jackrabbit.oak.spi.state.NodeBuilder; import org.apache.jackrabbit.oak.spi.state.NodeState; /** * This {@link ConflictHandler} implementation resolves conflicts to - * {@link org.apache.jackrabbit.oak.spi.commit.ConflictHandler.Resolution#THEIRS} and in addition marks nodes where a + * {@link org.apache.jackrabbit.oak.spi.commit.ThreeWayConflictHandler.Resolution#THEIRS} and in addition marks nodes where a * conflict occurred with the mixin {@code rep:MergeConflict}: * *
@@ -59,66 +59,72 @@ import org.apache.jackrabbit.oak.spi.state.NodeState;
  *
  * @see ConflictValidator
  */
-public class AnnotatingConflictHandler implements ConflictHandler {
+public class AnnotatingConflictHandler implements ThreeWayConflictHandler {
+
+    // TODO this class might need a look following update to
+    // ThreeWayConflictHandler
 
     @Override
-    public Resolution addExistingProperty(NodeBuilder parent, PropertyState ours, PropertyState theirs) {
+    public Resolution addExistingProperty(NodeBuilder parent, PropertyState ours, PropertyState theirs,
+            PropertyState base) {
         NodeBuilder marker = addConflictMarker(parent);
         createChild(marker, ADD_EXISTING_PROPERTY).setProperty(ours);
         return Resolution.THEIRS;
     }
 
     @Override
-    public Resolution changeDeletedProperty(NodeBuilder parent, PropertyState ours) {
+    public Resolution changeDeletedProperty(NodeBuilder parent, PropertyState ours, PropertyState base) {
         NodeBuilder marker = addConflictMarker(parent);
         createChild(marker, CHANGE_DELETED_PROPERTY).setProperty(ours);
         return Resolution.THEIRS;
     }
 
     @Override
-    public Resolution changeChangedProperty(NodeBuilder parent, PropertyState ours, PropertyState theirs) {
+    public Resolution changeChangedProperty(NodeBuilder parent, PropertyState ours, PropertyState theirs,
+            PropertyState base) {
         NodeBuilder marker = addConflictMarker(parent);
         createChild(marker, CHANGE_CHANGED_PROPERTY).setProperty(ours);
         return Resolution.THEIRS;
     }
 
     @Override
-    public Resolution deleteChangedProperty(NodeBuilder parent, PropertyState theirs) {
+    public Resolution deleteChangedProperty(NodeBuilder parent, PropertyState theirs, PropertyState base) {
         NodeBuilder marker = addConflictMarker(parent);
         createChild(marker, DELETE_CHANGED_PROPERTY).setProperty(theirs);
         return Resolution.THEIRS;
     }
 
     @Override
-    public Resolution deleteDeletedProperty(NodeBuilder parent, PropertyState ours) {
+    public Resolution deleteDeletedProperty(NodeBuilder parent, PropertyState ours, PropertyState base) {
         NodeBuilder marker = addConflictMarker(parent);
         createChild(marker, DELETE_DELETED_PROPERTY).setProperty(ours);
         return Resolution.THEIRS;
     }
 
     @Override
-    public Resolution addExistingNode(NodeBuilder parent, String name, NodeState ours, NodeState theirs) {
+    public Resolution addExistingNode(NodeBuilder parent, String name, NodeState ours, NodeState theirs,
+            NodeState base) {
         NodeBuilder marker = addConflictMarker(parent);
         createChild(marker, ADD_EXISTING_NODE).setChildNode(name, ours);
         return Resolution.THEIRS;
     }
 
     @Override
-    public Resolution changeDeletedNode(NodeBuilder parent, String name, NodeState ours) {
+    public Resolution changeDeletedNode(NodeBuilder parent, String name, NodeState ours, NodeState base) {
         NodeBuilder marker = addConflictMarker(parent);
         createChild(marker, CHANGE_DELETED_NODE).setChildNode(name, ours);
         return Resolution.THEIRS;
     }
 
     @Override
-    public Resolution deleteChangedNode(NodeBuilder parent, String name, NodeState theirs) {
+    public Resolution deleteChangedNode(NodeBuilder parent, String name, NodeState theirs, NodeState base) {
         NodeBuilder marker = addConflictMarker(parent);
         markChild(createChild(marker, DELETE_CHANGED_NODE), name);
         return Resolution.THEIRS;
     }
 
     @Override
-    public Resolution deleteDeletedNode(NodeBuilder parent, String name) {
+    public Resolution deleteDeletedNode(NodeBuilder parent, String name, NodeState base) {
         NodeBuilder marker = addConflictMarker(parent);
         markChild(createChild(marker, DELETE_DELETED_NODE), name);
         return Resolution.THEIRS;
diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/commit/ConflictHook.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/commit/ConflictHook.java
index 0e1979497e..9fc12f0ea5 100644
--- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/commit/ConflictHook.java
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/commit/ConflictHook.java
@@ -22,26 +22,37 @@ import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.spi.commit.CommitHook;
 import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
 import org.apache.jackrabbit.oak.spi.commit.ConflictHandler;
+import org.apache.jackrabbit.oak.spi.commit.ConflictHandlers;
+import org.apache.jackrabbit.oak.spi.commit.ThreeWayConflictHandler;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
 
 /**
  * This commit hook implementation is responsible for resolving
  * conflicts. It does so by detecting the presence of conflict
  * markers added by the Microkernel and delegating to a
- * {@link org.apache.jackrabbit.oak.spi.commit.ConflictHandler}
+ * {@link org.apache.jackrabbit.oak.spi.commit.ThreeWayConflictHandler}
  * for resolving the conflicts.
  *
  * @see org.apache.jackrabbit.oak.spi.state.NodeStore#rebase(org.apache.jackrabbit.oak.spi.state.NodeBuilder)
  */
 public class ConflictHook implements CommitHook {
-    private final ConflictHandler conflictHandler;
+    private final ThreeWayConflictHandler conflictHandler;
+
+    @Deprecated
+    public static final ConflictHook of(ConflictHandler handler) {
+        return of(ConflictHandlers.wrap(handler));
+    }
+
+    public static final ConflictHook of(ThreeWayConflictHandler handler) {
+        return new ConflictHook(handler);
+    }
 
     /**
      * Create a new instance of the conflict hook using the
      * passed conflict handler for resolving conflicts.
      * @param conflictHandler  a conflict handler
      */
-    public ConflictHook(ConflictHandler conflictHandler) {
+    public ConflictHook(ThreeWayConflictHandler conflictHandler) {
         this.conflictHandler = conflictHandler;
     }
 
diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/commit/DefaultConflictHandler.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/commit/DefaultConflictHandler.java
index c8080dd6a9..0393cc5cdb 100644
--- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/commit/DefaultConflictHandler.java
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/commit/DefaultConflictHandler.java
@@ -28,6 +28,7 @@ import org.apache.jackrabbit.oak.spi.state.NodeState;
  * It can be used to implement default behaviour or as a base class for more specialised
  * implementations.
  */
+@Deprecated
 public class DefaultConflictHandler implements ConflictHandler {
 
     /**
diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/commit/DefaultThreeWayConflictHandler.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/commit/DefaultThreeWayConflictHandler.java
new file mode 100644
index 0000000000..7718aa22b3
--- /dev/null
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/commit/DefaultThreeWayConflictHandler.java
@@ -0,0 +1,109 @@
+/*
+ * 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.oak.plugins.commit;
+
+import org.apache.jackrabbit.oak.spi.commit.ThreeWayConflictHandler;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+/**
+ * This implementation of a {@link ThreeWayConflictHandler} always returns the
+ * same resolution. It can be used to implement default behaviour or as a base
+ * class for more specialised implementations.
+ */
+public class DefaultThreeWayConflictHandler implements ThreeWayConflictHandler {
+
+    // TODO spouldn't this move to the ThreeWayConflictHandler package?
+
+    /**
+     * A {@code ConflictHandler} which always return
+     * {@link org.apache.jackrabbit.oak.spi.commit.ConflictHandler.Resolution#OURS}.
+     */
+    public static final ThreeWayConflictHandler OURS = new DefaultThreeWayConflictHandler(Resolution.OURS);
+
+    /**
+     * A {@code ConflictHandler} which always return
+     * {@link org.apache.jackrabbit.oak.spi.commit.ConflictHandler.Resolution#THEIRS}.
+     */
+    public static final ThreeWayConflictHandler THEIRS = new DefaultThreeWayConflictHandler(Resolution.THEIRS);
+
+    private final Resolution resolution;
+
+    /**
+     * Create a new {@code ConflictHandler} which always returns
+     * {@code resolution}.
+     *
+     * @param resolution
+     *            the resolution to return from all methods of this
+     *            {@code ConflictHandler} instance.
+     */
+    public DefaultThreeWayConflictHandler(Resolution resolution) {
+        this.resolution = resolution;
+    }
+
+    @Override
+    public Resolution addExistingProperty(NodeBuilder parent, PropertyState ours, PropertyState theirs,
+            PropertyState base) {
+        return resolution;
+    }
+
+    @Override
+    public Resolution changeDeletedProperty(NodeBuilder parent, PropertyState ours, PropertyState base) {
+        return resolution;
+    }
+
+    @Override
+    public Resolution changeChangedProperty(NodeBuilder parent, PropertyState ours, PropertyState theirs,
+            PropertyState base) {
+        return resolution;
+    }
+
+    @Override
+    public Resolution deleteDeletedProperty(NodeBuilder parent, PropertyState ours, PropertyState base) {
+        return resolution;
+    }
+
+    @Override
+    public Resolution deleteChangedProperty(NodeBuilder parent, PropertyState theirs, PropertyState base) {
+        return resolution;
+    }
+
+    @Override
+    public Resolution addExistingNode(NodeBuilder parent, String name, NodeState ours, NodeState theirs,
+            NodeState base) {
+        return resolution;
+    }
+
+    @Override
+    public Resolution changeDeletedNode(NodeBuilder parent, String name, NodeState ours, NodeState base) {
+        return resolution;
+    }
+
+    @Override
+    public Resolution deleteChangedNode(NodeBuilder parent, String name, NodeState theirs, NodeState base) {
+        return resolution;
+    }
+
+    @Override
+    public Resolution deleteDeletedNode(NodeBuilder parent, String name, NodeState base) {
+        return resolution;
+    }
+
+}
diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/commit/JcrConflictHandler.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/commit/JcrConflictHandler.java
index 5282a88408..e3dfdeb9c7 100644
--- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/commit/JcrConflictHandler.java
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/commit/JcrConflictHandler.java
@@ -18,8 +18,10 @@
  */
 package org.apache.jackrabbit.oak.plugins.commit;
 
-import com.google.common.collect.ImmutableList;
 import org.apache.jackrabbit.oak.spi.commit.CompositeConflictHandler;
+import org.apache.jackrabbit.oak.spi.commit.ConflictHandlers;
+
+import com.google.common.collect.ImmutableList;
 
 /**
  * Utility class providing conflict handlers used for JCR.
@@ -32,10 +34,9 @@ public final class JcrConflictHandler {
      */
     public static CompositeConflictHandler createJcrConflictHandler() {
         return new CompositeConflictHandler(ImmutableList.of(
-                new JcrLastModifiedConflictHandler(),
-                new ChildOrderConflictHandler(),
-                new AnnotatingConflictHandler()
-        ));
+                ConflictHandlers.wrap(new JcrLastModifiedConflictHandler()),
+                ConflictHandlers.wrap(new ChildOrderConflictHandler()),
+                new AnnotatingConflictHandler()));
     }
 
     private JcrConflictHandler() {
diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/commit/MergingNodeStateDiff.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/commit/MergingNodeStateDiff.java
index 57cf46789c..3469352f60 100644
--- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/commit/MergingNodeStateDiff.java
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/commit/MergingNodeStateDiff.java
@@ -17,7 +17,9 @@
 package org.apache.jackrabbit.oak.plugins.commit;
 
 import static org.apache.jackrabbit.oak.api.Type.NAME;
+import static org.apache.jackrabbit.oak.spi.state.ConflictAnnotatingRebaseDiff.BASE;
 import static org.apache.jackrabbit.oak.spi.state.ConflictAnnotatingRebaseDiff.CONFLICT;
+import static org.apache.jackrabbit.oak.spi.state.ConflictAnnotatingRebaseDiff.OURS;
 import static org.apache.jackrabbit.oak.spi.state.ConflictType.ADD_EXISTING_NODE;
 import static org.apache.jackrabbit.oak.spi.state.ConflictType.ADD_EXISTING_PROPERTY;
 import static org.apache.jackrabbit.oak.spi.state.ConflictType.CHANGE_CHANGED_PROPERTY;
@@ -29,15 +31,21 @@ import static org.apache.jackrabbit.oak.spi.state.ConflictType.DELETE_DELETED_NO
 import static org.apache.jackrabbit.oak.spi.state.ConflictType.DELETE_DELETED_PROPERTY;
 
 import java.util.Map;
+import java.util.Set;
 
+import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Sets;
+
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.json.JsopDiff;
+import org.apache.jackrabbit.oak.plugins.memory.EmptyPropertyState;
 import org.apache.jackrabbit.oak.plugins.memory.PropertyBuilder;
 import org.apache.jackrabbit.oak.plugins.tree.impl.TreeConstants;
-import org.apache.jackrabbit.oak.spi.commit.ConflictHandler;
-import org.apache.jackrabbit.oak.spi.commit.PartialConflictHandler.Resolution;
+import org.apache.jackrabbit.oak.spi.commit.ThreeWayConflictHandler.Resolution;
+import org.apache.jackrabbit.oak.spi.commit.ThreeWayConflictHandler;
 import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
+import org.apache.jackrabbit.oak.spi.state.ConflictAnnotatingRebaseDiff;
 import org.apache.jackrabbit.oak.spi.state.ConflictType;
 import org.apache.jackrabbit.oak.spi.state.DefaultNodeStateDiff;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
@@ -53,20 +61,20 @@ public final class MergingNodeStateDiff extends DefaultNodeStateDiff {
 
     private final NodeState parent;
     private final NodeBuilder target;
-    private final ConflictHandler conflictHandler;
+    private final ThreeWayConflictHandler conflictHandler;
 
-    private MergingNodeStateDiff(NodeState parent, NodeBuilder target, ConflictHandler conflictHandler) {
+    private MergingNodeStateDiff(NodeState parent, NodeBuilder target, ThreeWayConflictHandler conflictHandler) {
         this.parent = parent;
         this.target = target;
         this.conflictHandler = conflictHandler;
     }
 
-    static NodeState merge(NodeState fromState, NodeState toState, ConflictHandler conflictHandler) {
+    static NodeState merge(NodeState fromState, NodeState toState, ThreeWayConflictHandler conflictHandler) {
         return merge(fromState, toState, toState.builder(), conflictHandler).getNodeState();
     }
 
     private static NodeBuilder merge(NodeState fromState, NodeState toState, NodeBuilder target,
-                                     ConflictHandler conflictHandler) {
+                    ThreeWayConflictHandler conflictHandler) {
         toState.compareAgainstBaseState(fromState,
                 new MergingNodeStateDiff(toState, target, conflictHandler));
 
@@ -98,23 +106,44 @@ public final class MergingNodeStateDiff extends DefaultNodeStateDiff {
     private void resolveConflict(ConflictType conflictType, NodeState conflictInfo) {
         PropertyConflictHandler propertyConflictHandler = propertyConflictHandlers.get(conflictType);
         if (propertyConflictHandler != null) {
-            for (PropertyState ours : conflictInfo.getProperties()) {
-                PropertyState theirs = parent.getProperty(ours.getName());
-                Resolution resolution = propertyConflictHandler.resolve(ours, theirs);
-                applyResolution(resolution, conflictType, ours);
+            NodeState oursNS = conflictInfo.getChildNode(OURS);
+            NodeState baseNS = conflictInfo.getChildNode(BASE);
+
+            Set processed = Sets.newHashSet();
+            for (PropertyState ours : oursNS.getProperties()) {
+                String name = ours.getName();
+                processed.add(name);
+                PropertyState base = baseNS.getProperty(name);
+                PropertyState theirs = parent.getProperty(name);
+                Resolution resolution = propertyConflictHandler.resolve(ours, theirs, base);
+                applyPropertyResolution(resolution, conflictType, name, ours);
             }
-        }
-        else {
+            for (PropertyState base : baseNS.getProperties()) {
+                String name = base.getName();
+                if (processed.contains(name)) {
+                    continue;
+                }
+                PropertyState theirs = parent.getProperty(name);
+                Resolution resolution = propertyConflictHandler.resolve(null, theirs, base);
+                applyPropertyResolution(resolution, conflictType, name, null);
+            }
+        } else {
             NodeConflictHandler nodeConflictHandler = nodeConflictHandlers.get(conflictType);
             if (nodeConflictHandler != null) {
-                for (ChildNodeEntry oursCNE : conflictInfo.getChildNodeEntries()) {
-                    String name = oursCNE.getName();
-                    NodeState ours = oursCNE.getNodeState();
+                NodeState oursNS = conflictInfo.getChildNode(OURS);
+                NodeState baseNS = conflictInfo.getChildNode(BASE);
+
+                Set candidates = Sets.union(Sets.newHashSet(oursNS.getChildNodeNames()),
+                        Sets.newHashSet(baseNS.getChildNodeNames()));
+                for (String name : candidates) {
+                    NodeState ours = oursNS.getChildNode(name);
+                    NodeState base = baseNS.getChildNode(name);
                     NodeState theirs = parent.getChildNode(name);
-                    Resolution resolution = nodeConflictHandler.resolve(name, ours, theirs);
+                    Resolution resolution = nodeConflictHandler.resolve(name, ours, theirs, base);
                     applyResolution(resolution, conflictType, name, ours);
+
                     if (LOG.isDebugEnabled()) {
-                        String diff = JsopDiff.diffToJsop(ours, theirs);
+                        String diff = JsopDiff.diffToJsop(base, theirs);
                         LOG.debug(
                                 "{} resolved conflict of type {} with resolution {} on node {}, conflict trace {}",
                                 nodeConflictHandler, conflictType, resolution,
@@ -129,12 +158,12 @@ public final class MergingNodeStateDiff extends DefaultNodeStateDiff {
 
         NodeBuilder conflictMarker = getConflictMarker(conflictType);
         if (conflictMarker != null) {
-            assert conflictMarker.getChildNodeCount(1) == 0;
+            assert conflictMarker.getChildNode(ConflictAnnotatingRebaseDiff.BASE).getChildNodeCount(1) == 0;
+            assert conflictMarker.getChildNode(ConflictAnnotatingRebaseDiff.OURS).getChildNodeCount(1) == 0;
         }
     }
 
-    private void applyResolution(Resolution resolution, ConflictType conflictType, PropertyState ours) {
-        String name = ours.getName();
+    private void applyPropertyResolution(Resolution resolution, ConflictType conflictType, String name, PropertyState ours) {
         NodeBuilder conflictMarker = getConflictMarker(conflictType);
         if (resolution == Resolution.OURS) {
             if (DELETE_CHANGED_PROPERTY == conflictType) {
@@ -145,7 +174,14 @@ public final class MergingNodeStateDiff extends DefaultNodeStateDiff {
             }
 
         }
-        conflictMarker.removeProperty(name);
+        NodeBuilder baseClean = conflictMarker.getChildNode(ConflictAnnotatingRebaseDiff.BASE);
+        if (baseClean.exists()) {
+            baseClean.removeProperty(name);
+        }
+        NodeBuilder oursClean = conflictMarker.getChildNode(ConflictAnnotatingRebaseDiff.OURS);
+        if (oursClean.exists()) {
+            oursClean.removeProperty(name);
+        }
     }
 
     private void applyResolution(Resolution resolution, ConflictType conflictType, String name, NodeState ours) {
@@ -158,7 +194,23 @@ public final class MergingNodeStateDiff extends DefaultNodeStateDiff {
                 addChild(target, name, ours);
             }
         }
-        conflictMarker.getChildNode(name).remove();
+
+        NodeBuilder baseClean = conflictMarker.getChildNode(ConflictAnnotatingRebaseDiff.BASE);
+        if (baseClean.exists()) {
+            baseClean.getChildNode(name).remove();
+            // if (baseClean.getChildNodeCount(1) == 0) {
+            // baseClean.remove();
+            // }
+        }
+
+        NodeBuilder oursClean = conflictMarker.getChildNode(ConflictAnnotatingRebaseDiff.OURS);
+        if (oursClean.exists()) {
+            oursClean.getChildNode(name).remove();
+            // if (oursClean.getChildNodeCount(1) == 0) {
+            // oursClean.remove();
+            // }
+        }
+
     }
 
     private NodeBuilder getConflictMarker(ConflictType conflictType) {
@@ -174,18 +226,19 @@ public final class MergingNodeStateDiff extends DefaultNodeStateDiff {
     }
 
     private interface PropertyConflictHandler {
-        Resolution resolve(PropertyState ours, PropertyState theirs);
+        Resolution resolve(PropertyState ours, PropertyState theirs, PropertyState base);
     }
 
     private interface NodeConflictHandler {
-        Resolution resolve(String name, NodeState ours, NodeState theirs);
+        Resolution resolve(String name, NodeState ours, NodeState theirs, NodeState base);
     }
 
     private final Map propertyConflictHandlers = ImmutableMap.of(
         ADD_EXISTING_PROPERTY, new PropertyConflictHandler() {
             @Override
-            public Resolution resolve(PropertyState ours, PropertyState theirs) {
-                return conflictHandler.addExistingProperty(target, ours, theirs);
+            public Resolution resolve(PropertyState ours, PropertyState theirs, PropertyState base) {
+                Preconditions.checkNotNull(ours);
+                return conflictHandler.addExistingProperty(target, ours, theirs, base);
             }
 
             @Override
@@ -195,8 +248,9 @@ public final class MergingNodeStateDiff extends DefaultNodeStateDiff {
         },
         CHANGE_DELETED_PROPERTY, new PropertyConflictHandler() {
             @Override
-            public Resolution resolve(PropertyState ours, PropertyState theirs) {
-                return conflictHandler.changeDeletedProperty(target, ours);
+            public Resolution resolve(PropertyState ours, PropertyState theirs, PropertyState base) {
+                Preconditions.checkNotNull(ours);
+                return conflictHandler.changeDeletedProperty(target, ours, base);
             }
 
             @Override
@@ -206,8 +260,9 @@ public final class MergingNodeStateDiff extends DefaultNodeStateDiff {
         },
         CHANGE_CHANGED_PROPERTY, new PropertyConflictHandler() {
             @Override
-            public Resolution resolve(PropertyState ours, PropertyState theirs) {
-                return conflictHandler.changeChangedProperty(target, ours, theirs);
+            public Resolution resolve(PropertyState ours, PropertyState theirs, PropertyState base) {
+                Preconditions.checkNotNull(ours);
+                return conflictHandler.changeChangedProperty(target, ours, theirs, base);
             }
 
             @Override
@@ -217,8 +272,9 @@ public final class MergingNodeStateDiff extends DefaultNodeStateDiff {
         },
         DELETE_DELETED_PROPERTY, new PropertyConflictHandler() {
             @Override
-            public Resolution resolve(PropertyState ours, PropertyState theirs) {
-                return conflictHandler.deleteDeletedProperty(target, ours);
+            public Resolution resolve(PropertyState ours, PropertyState theirs, PropertyState base) {
+                Preconditions.checkNotNull(ours);
+                return conflictHandler.deleteDeletedProperty(target, ours, base);
             }
 
             @Override
@@ -228,8 +284,8 @@ public final class MergingNodeStateDiff extends DefaultNodeStateDiff {
         },
         DELETE_CHANGED_PROPERTY, new PropertyConflictHandler() {
             @Override
-            public Resolution resolve(PropertyState ours, PropertyState theirs) {
-                return conflictHandler.deleteChangedProperty(target, theirs);
+            public Resolution resolve(PropertyState ours, PropertyState theirs, PropertyState base) {
+                return conflictHandler.deleteChangedProperty(target, theirs, base);
             }
 
             @Override
@@ -242,8 +298,8 @@ public final class MergingNodeStateDiff extends DefaultNodeStateDiff {
     private final Map nodeConflictHandlers = ImmutableMap.of(
         ADD_EXISTING_NODE, new NodeConflictHandler() {
             @Override
-            public Resolution resolve(String name, NodeState ours, NodeState theirs) {
-                return conflictHandler.addExistingNode(target, name, ours, theirs);
+            public Resolution resolve(String name, NodeState ours, NodeState theirs, NodeState base) {
+                return conflictHandler.addExistingNode(target, name, ours, theirs, base);
             }
 
             @Override
@@ -253,8 +309,8 @@ public final class MergingNodeStateDiff extends DefaultNodeStateDiff {
         },
         CHANGE_DELETED_NODE, new NodeConflictHandler() {
             @Override
-            public Resolution resolve(String name, NodeState ours, NodeState theirs) {
-                return conflictHandler.changeDeletedNode(target, name, ours);
+            public Resolution resolve(String name, NodeState ours, NodeState theirs, NodeState base) {
+                return conflictHandler.changeDeletedNode(target, name, ours, base);
             }
 
             @Override
@@ -264,8 +320,8 @@ public final class MergingNodeStateDiff extends DefaultNodeStateDiff {
         },
         DELETE_CHANGED_NODE, new NodeConflictHandler() {
             @Override
-            public Resolution resolve(String name, NodeState ours, NodeState theirs) {
-                return conflictHandler.deleteChangedNode(target, name, theirs);
+            public Resolution resolve(String name, NodeState ours, NodeState theirs, NodeState base) {
+                return conflictHandler.deleteChangedNode(target, name, theirs, base);
             }
 
             @Override
@@ -275,8 +331,8 @@ public final class MergingNodeStateDiff extends DefaultNodeStateDiff {
         },
         DELETE_DELETED_NODE, new NodeConflictHandler() {
             @Override
-            public Resolution resolve(String name, NodeState ours, NodeState theirs) {
-                return conflictHandler.deleteDeletedNode(target, name);
+                public Resolution resolve(String name, NodeState ours, NodeState theirs, NodeState base) {
+                return conflictHandler.deleteDeletedNode(target, name, base);
             }
 
             @Override
diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/AsyncIndexUpdate.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/AsyncIndexUpdate.java
index 087ee97c43..5c558d1614 100644
--- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/AsyncIndexUpdate.java
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/AsyncIndexUpdate.java
@@ -851,7 +851,7 @@ public class AsyncIndexUpdate implements Runnable, Closeable {
         editorProviders.addAll(validatorProviders);
         CompositeHook hooks = new CompositeHook(
                 ResetCommitAttributeHook.INSTANCE,
-                new ConflictHook(new AnnotatingConflictHandler()),
+                ConflictHook.of(new AnnotatingConflictHandler()),
                 new EditorHook(CompositeEditorProvider.compose(editorProviders)),
                 concurrentUpdateCheck);
         try {
diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/ConcurrentPropertyUpdateTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/ConcurrentPropertyUpdateTest.java
index 4ba4875b32..e4124a819b 100644
--- a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/ConcurrentPropertyUpdateTest.java
+++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/ConcurrentPropertyUpdateTest.java
@@ -46,7 +46,7 @@ public class ConcurrentPropertyUpdateTest extends BaseDocumentMKTest {
     private static final int NUM_THREADS = 2;
 
     private static final CommitHook HOOK = new CompositeHook(
-            new ConflictHook(new AnnotatingConflictHandler()),
+            ConflictHook.of(new AnnotatingConflictHandler()),
             new EditorHook(new ConflictValidatorProvider()));
 
     private ExecutorService service = Executors.newFixedThreadPool(NUM_THREADS);
diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreTest.java
index 75f269e1b5..878f255d21 100644
--- a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreTest.java
+++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreTest.java
@@ -1930,7 +1930,7 @@ public class DocumentNodeStoreTest {
                             hookList.add(hook);
                         }
                         hookList.add(blockingHook);
-                        hookList.add(new ConflictHook(new AnnotatingConflictHandler()));
+                        hookList.add(ConflictHook.of(new AnnotatingConflictHandler()));
                         hookList.add(new EditorHook(new ConflictValidatorProvider()));
                         store.merge(builder, CompositeHook.compose(hookList), CommitInfo.EMPTY);
                     } catch (CommitFailedException cfe) {
diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/HierarchyConflictTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/HierarchyConflictTest.java
index 8adf1aa605..15382db640 100644
--- a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/HierarchyConflictTest.java
+++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/HierarchyConflictTest.java
@@ -208,7 +208,7 @@ public class HierarchyConflictTest {
                         return null;
                     }
                 }),
-                new ConflictHook(new AnnotatingConflictHandler()),
+                ConflictHook.of(new AnnotatingConflictHandler()),
                 new EditorHook(new ConflictValidatorProvider()));
         store.merge(root, hooks, CommitInfo.EMPTY);
     }
diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/NodeStoreDiffTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/NodeStoreDiffTest.java
index 939f224ae6..22be630b88 100644
--- a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/NodeStoreDiffTest.java
+++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/NodeStoreDiffTest.java
@@ -98,7 +98,7 @@ public class NodeStoreDiffTest {
         //For now exception would be thrown
         ns.merge(b1,
                 new CompositeHook(
-                        new ConflictHook(new AnnotatingConflictHandler()),
+                        ConflictHook.of(new AnnotatingConflictHandler()),
                         new EditorHook(new ConflictValidatorProvider())
                 ),
                 CommitInfo.EMPTY);
diff --git a/oak-it/src/test/java/org/apache/jackrabbit/oak/api/TreeTest.java b/oak-it/src/test/java/org/apache/jackrabbit/oak/api/TreeTest.java
index 6552f39151..16e37b83f1 100644
--- a/oak-it/src/test/java/org/apache/jackrabbit/oak/api/TreeTest.java
+++ b/oak-it/src/test/java/org/apache/jackrabbit/oak/api/TreeTest.java
@@ -37,6 +37,7 @@ import org.apache.jackrabbit.oak.plugins.commit.AnnotatingConflictHandler;
 import org.apache.jackrabbit.oak.plugins.commit.ChildOrderConflictHandler;
 import org.apache.jackrabbit.oak.plugins.commit.ConflictValidatorProvider;
 import org.apache.jackrabbit.oak.spi.commit.CompositeConflictHandler;
+import org.apache.jackrabbit.oak.spi.commit.ConflictHandlers;
 import org.apache.jackrabbit.oak.spi.security.OpenSecurityProvider;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
@@ -60,7 +61,7 @@ public class TreeTest extends OakBaseTest {
         repository = new Oak(store)
             .with(new OpenSecurityProvider())
             .with(new CompositeConflictHandler(ImmutableList.of(
-                    new ChildOrderConflictHandler() {
+                    ConflictHandlers.wrap(new ChildOrderConflictHandler() {
                         /**
                          * Allow deleting changed node.
                          * See {@link TreeTest#removeWithConcurrentOrderBefore()}
@@ -71,7 +72,7 @@ public class TreeTest extends OakBaseTest {
                                 NodeState theirs) {
                             return Resolution.OURS;
                         }
-                    },
+                    }),
                     new AnnotatingConflictHandler()
             )))
             .with(new ConflictValidatorProvider())
diff --git a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/Jcr.java b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/Jcr.java
index e6903174ac..1283fc6263 100644
--- a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/Jcr.java
+++ b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/Jcr.java
@@ -53,10 +53,12 @@ import org.apache.jackrabbit.oak.security.SecurityProviderImpl;
 import org.apache.jackrabbit.oak.spi.commit.BackgroundObserver;
 import org.apache.jackrabbit.oak.spi.commit.CommitHook;
 import org.apache.jackrabbit.oak.spi.commit.CompositeConflictHandler;
+import org.apache.jackrabbit.oak.spi.commit.ConflictHandlers;
 import org.apache.jackrabbit.oak.spi.commit.Editor;
 import org.apache.jackrabbit.oak.spi.commit.EditorProvider;
 import org.apache.jackrabbit.oak.spi.commit.Observer;
 import org.apache.jackrabbit.oak.spi.commit.PartialConflictHandler;
+import org.apache.jackrabbit.oak.spi.commit.ThreeWayConflictHandler;
 import org.apache.jackrabbit.oak.spi.lifecycle.RepositoryInitializer;
 import org.apache.jackrabbit.oak.spi.query.QueryIndexProvider;
 import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
@@ -216,8 +218,14 @@ public class Jcr {
         return this;
     }
 
+    @Deprecated
     @Nonnull
     public final Jcr with(@Nonnull PartialConflictHandler conflictHandler) {
+        return with(ConflictHandlers.wrap(conflictHandler));
+    }
+
+    @Nonnull
+    public final Jcr with(@Nonnull ThreeWayConflictHandler conflictHandler) {
         ensureRepositoryIsNotCreated();
         this.conflictHandler.addHandler(checkNotNull(conflictHandler));
         return this;
diff --git a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/SegmentCompactionIT.java b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/SegmentCompactionIT.java
index cbd6933d09..0de4d57910 100644
--- a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/SegmentCompactionIT.java
+++ b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/SegmentCompactionIT.java
@@ -77,7 +77,7 @@ import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.jmx.CacheStatsMBean;
 import org.apache.jackrabbit.oak.commons.jmx.AnnotatedStandardMBean;
 import org.apache.jackrabbit.oak.plugins.commit.ConflictHook;
-import org.apache.jackrabbit.oak.plugins.commit.DefaultConflictHandler;
+import org.apache.jackrabbit.oak.plugins.commit.DefaultThreeWayConflictHandler;
 import org.apache.jackrabbit.oak.plugins.metric.MetricStatisticsProvider;
 import org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions;
 import org.apache.jackrabbit.oak.segment.compaction.SegmentRevisionGC;
@@ -471,8 +471,8 @@ public class SegmentCompactionIT {
                     if (!cancelled) {
                         try {
                             CommitHook commitHook = rnd.nextBoolean()
-                                    ? new CompositeHook(new ConflictHook(DefaultConflictHandler.OURS))
-                                    : new CompositeHook(new ConflictHook(DefaultConflictHandler.THEIRS));
+                                    ? new CompositeHook(ConflictHook.of(DefaultThreeWayConflictHandler.OURS))
+                                    : new CompositeHook(ConflictHook.of(DefaultThreeWayConflictHandler.THEIRS));
                             nodeStore.merge(root, commitHook, CommitInfo.EMPTY);
                             segmentCompactionMBean.committed();
                         } catch (CommitFailedException e) {
diff --git a/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/spi/commit/CompositeConflictHandler.java b/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/spi/commit/CompositeConflictHandler.java
index cc76adfd9b..dbe08827a9 100644
--- a/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/spi/commit/CompositeConflictHandler.java
+++ b/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/spi/commit/CompositeConflictHandler.java
@@ -30,6 +30,7 @@ import static org.apache.jackrabbit.oak.spi.state.ConflictType.DELETE_CHANGED_NO
 import static org.apache.jackrabbit.oak.spi.state.ConflictType.DELETE_CHANGED_PROPERTY;
 import static org.apache.jackrabbit.oak.spi.state.ConflictType.DELETE_DELETED_NODE;
 import static org.apache.jackrabbit.oak.spi.state.ConflictType.DELETE_DELETED_PROPERTY;
+import static org.apache.jackrabbit.oak.spi.commit.ThreeWayConflictHandler.Resolution.IGNORED;
 
 import java.util.LinkedList;
 
@@ -47,8 +48,8 @@ import org.apache.jackrabbit.oak.spi.state.NodeState;
  * conflict none of the backing handlers returns a valid resolution
  * this implementation throws an {@code IllegalStateException}.
  */
-public class CompositeConflictHandler implements ConflictHandler {
-    private final LinkedList handlers;
+public class CompositeConflictHandler implements ThreeWayConflictHandler {
+    private final LinkedList handlers;
 
     /**
      * Create a new {@code CompositeConflictHandler} with an initial set of
@@ -56,7 +57,7 @@ public class CompositeConflictHandler implements ConflictHandler {
      * handlers.
      * @param handlers  the backing handlers
      */
-    public CompositeConflictHandler(@Nonnull Iterable handlers) {
+    public CompositeConflictHandler(@Nonnull Iterable handlers) {
         this.handlers = newLinkedList(checkNotNull(handlers));
     }
 
@@ -74,16 +75,17 @@ public class CompositeConflictHandler implements ConflictHandler {
      * @param handler
      * @return this
      */
-    public CompositeConflictHandler addHandler(PartialConflictHandler handler) {
+    public CompositeConflictHandler addHandler(ThreeWayConflictHandler handler) {
         handlers.addFirst(handler);
         return this;
     }
 
     @Override
-    public Resolution addExistingProperty(NodeBuilder parent, PropertyState ours, PropertyState theirs) {
-        for (PartialConflictHandler handler : handlers) {
-            Resolution resolution = handler.addExistingProperty(parent, ours, theirs);
-            if (resolution != null) {
+    public Resolution addExistingProperty(NodeBuilder parent, PropertyState ours, PropertyState theirs,
+            PropertyState base) {
+        for (ThreeWayConflictHandler handler : handlers) {
+            Resolution resolution = handler.addExistingProperty(parent, ours, theirs, base);
+            if (resolution != /* null */ IGNORED) {
                 return resolution;
             }
         }
@@ -92,10 +94,10 @@ public class CompositeConflictHandler implements ConflictHandler {
     }
 
     @Override
-    public Resolution changeDeletedProperty(NodeBuilder parent, PropertyState ours) {
-        for (PartialConflictHandler handler : handlers) {
-            Resolution resolution = handler.changeDeletedProperty(parent, ours);
-            if (resolution != null) {
+    public Resolution changeDeletedProperty(NodeBuilder parent, PropertyState ours, PropertyState base) {
+        for (ThreeWayConflictHandler handler : handlers) {
+            Resolution resolution = handler.changeDeletedProperty(parent, ours, base);
+            if (resolution != /* null */ IGNORED) {
                 return resolution;
             }
         }
@@ -104,10 +106,11 @@ public class CompositeConflictHandler implements ConflictHandler {
     }
 
     @Override
-    public Resolution changeChangedProperty(NodeBuilder parent, PropertyState ours, PropertyState theirs) {
-        for (PartialConflictHandler handler : handlers) {
-            Resolution resolution = handler.changeChangedProperty(parent, ours, theirs);
-            if (resolution != null) {
+    public Resolution changeChangedProperty(NodeBuilder parent, PropertyState ours, PropertyState theirs,
+            PropertyState base) {
+        for (ThreeWayConflictHandler handler : handlers) {
+            Resolution resolution = handler.changeChangedProperty(parent, ours, theirs, base);
+            if (resolution != /* null */ IGNORED) {
                 return resolution;
             }
         }
@@ -116,10 +119,10 @@ public class CompositeConflictHandler implements ConflictHandler {
     }
 
     @Override
-    public Resolution deleteDeletedProperty(NodeBuilder parent, PropertyState ours) {
-        for (PartialConflictHandler handler : handlers) {
-            Resolution resolution = handler.deleteDeletedProperty(parent, ours);
-            if (resolution != null) {
+    public Resolution deleteDeletedProperty(NodeBuilder parent, PropertyState ours, PropertyState base) {
+        for (ThreeWayConflictHandler handler : handlers) {
+            Resolution resolution = handler.deleteDeletedProperty(parent, ours, base);
+            if (resolution != /* null */ IGNORED) {
                 return resolution;
             }
         }
@@ -128,10 +131,10 @@ public class CompositeConflictHandler implements ConflictHandler {
     }
 
     @Override
-    public Resolution deleteChangedProperty(NodeBuilder parent, PropertyState theirs) {
-        for (PartialConflictHandler handler : handlers) {
-            Resolution resolution = handler.deleteChangedProperty(parent, theirs);
-            if (resolution != null) {
+    public Resolution deleteChangedProperty(NodeBuilder parent, PropertyState theirs, PropertyState base) {
+        for (ThreeWayConflictHandler handler : handlers) {
+            Resolution resolution = handler.deleteChangedProperty(parent, theirs, base);
+            if (resolution != /* null */ IGNORED) {
                 return resolution;
             }
         }
@@ -140,10 +143,10 @@ public class CompositeConflictHandler implements ConflictHandler {
     }
 
     @Override
-    public Resolution addExistingNode(NodeBuilder parent, String name, NodeState ours, NodeState theirs) {
-        for (PartialConflictHandler handler : handlers) {
-            Resolution resolution = handler.addExistingNode(parent, name, ours, theirs);
-            if (resolution != null) {
+    public Resolution addExistingNode(NodeBuilder parent, String name, NodeState ours, NodeState theirs, NodeState base) {
+        for (ThreeWayConflictHandler handler : handlers) {
+            Resolution resolution = handler.addExistingNode(parent, name, ours, theirs, base);
+            if (resolution !=IGNORED) {
                 return resolution;
             }
         }
@@ -152,10 +155,10 @@ public class CompositeConflictHandler implements ConflictHandler {
     }
 
     @Override
-    public Resolution changeDeletedNode(NodeBuilder parent, String name, NodeState ours) {
-        for (PartialConflictHandler handler : handlers) {
-            Resolution resolution = handler.changeDeletedNode(parent, name, ours);
-            if (resolution != null) {
+    public Resolution changeDeletedNode(NodeBuilder parent, String name, NodeState ours, NodeState base) {
+        for (ThreeWayConflictHandler handler : handlers) {
+            Resolution resolution = handler.changeDeletedNode(parent, name, ours, base);
+            if (resolution != IGNORED) {
                 return resolution;
             }
         }
@@ -164,10 +167,10 @@ public class CompositeConflictHandler implements ConflictHandler {
     }
 
     @Override
-    public Resolution deleteChangedNode(NodeBuilder parent, String name, NodeState theirs) {
-        for (PartialConflictHandler handler : handlers) {
-            Resolution resolution = handler.deleteChangedNode(parent, name, theirs);
-            if (resolution != null) {
+    public Resolution deleteChangedNode(NodeBuilder parent, String name, NodeState theirs, NodeState base) {
+        for (ThreeWayConflictHandler handler : handlers) {
+            Resolution resolution = handler.deleteChangedNode(parent, name, theirs, base);
+            if (resolution != IGNORED) {
                 return resolution;
             }
         }
@@ -176,14 +179,15 @@ public class CompositeConflictHandler implements ConflictHandler {
     }
 
     @Override
-    public Resolution deleteDeletedNode(NodeBuilder parent, String name) {
-        for (PartialConflictHandler handler : handlers) {
-            Resolution resolution = handler.deleteDeletedNode(parent, name);
-            if (resolution != null) {
+    public Resolution deleteDeletedNode(NodeBuilder parent, String name, NodeState base) {
+        for (ThreeWayConflictHandler handler : handlers) {
+            Resolution resolution = handler.deleteDeletedNode(parent, name, base);
+            if (resolution != IGNORED) {
                 return resolution;
             }
         }
         throw new IllegalStateException("No conflict handler for " +
                 DELETE_DELETED_NODE + " conflict");
     }
+
 }
diff --git a/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/spi/commit/ConflictHandler.java b/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/spi/commit/ConflictHandler.java
index 985ca82483..34710bcca9 100644
--- a/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/spi/commit/ConflictHandler.java
+++ b/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/spi/commit/ConflictHandler.java
@@ -39,6 +39,7 @@ import org.apache.jackrabbit.oak.spi.state.NodeState;
  *
  * @see ConflictHandler
  */
+@Deprecated
 public interface ConflictHandler extends PartialConflictHandler {
 
     /**
diff --git a/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/spi/commit/ConflictHandlers.java b/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/spi/commit/ConflictHandlers.java
new file mode 100644
index 0000000000..180e295c29
--- /dev/null
+++ b/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/spi/commit/ConflictHandlers.java
@@ -0,0 +1,108 @@
+/*
+ * 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.oak.spi.commit;
+
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.spi.commit.ThreeWayConflictHandler.Resolution;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+public class ConflictHandlers {
+
+    // TODO SubtreeConflictHandlers?
+
+    private ConflictHandlers() {
+    }
+
+    @SuppressWarnings("deprecation")
+    public static ThreeWayConflictHandler wrap(PartialConflictHandler handler) {
+        return new ThreeWayConflictHandlerWrapper(handler);
+    }
+
+    @SuppressWarnings("deprecation")
+    private static Resolution wrap(org.apache.jackrabbit.oak.spi.commit.PartialConflictHandler.Resolution r) {
+        if (r == null) {
+            return Resolution.IGNORED;
+        }
+        switch (r) {
+        case OURS:
+            return Resolution.OURS;
+        case THEIRS:
+            return Resolution.THEIRS;
+        case MERGED:
+            return Resolution.MERGED;
+        }
+        return Resolution.IGNORED;
+    }
+
+    @SuppressWarnings("deprecation")
+    private static class ThreeWayConflictHandlerWrapper implements ThreeWayConflictHandler {
+        private final PartialConflictHandler handler;
+
+        public ThreeWayConflictHandlerWrapper(PartialConflictHandler handler) {
+            this.handler = handler;
+        }
+
+        @Override
+        public Resolution addExistingProperty(NodeBuilder parent, PropertyState ours, PropertyState theirs,
+                PropertyState base) {
+            return wrap(handler.addExistingProperty(parent, ours, theirs));
+        }
+
+        @Override
+        public Resolution changeDeletedProperty(NodeBuilder parent, PropertyState ours, PropertyState base) {
+            return wrap(handler.changeDeletedProperty(parent, ours));
+        }
+
+        @Override
+        public Resolution changeChangedProperty(NodeBuilder parent, PropertyState ours, PropertyState theirs,
+                PropertyState base) {
+            return wrap(handler.changeChangedProperty(parent, ours, theirs));
+        }
+
+        @Override
+        public Resolution deleteDeletedProperty(NodeBuilder parent, PropertyState ours, PropertyState base) {
+            return wrap(handler.deleteDeletedProperty(parent, ours));
+        }
+
+        @Override
+        public Resolution deleteChangedProperty(NodeBuilder parent, PropertyState theirs, PropertyState base) {
+            return wrap(handler.deleteChangedProperty(parent, theirs));
+        }
+
+        @Override
+        public Resolution addExistingNode(NodeBuilder parent, String name, NodeState ours, NodeState theirs,
+                NodeState base) {
+            return wrap(handler.addExistingNode(parent, name, ours, theirs));
+        }
+
+        @Override
+        public Resolution changeDeletedNode(NodeBuilder parent, String name, NodeState ours, NodeState base) {
+            return wrap(handler.changeDeletedNode(parent, name, ours));
+        }
+
+        @Override
+        public Resolution deleteChangedNode(NodeBuilder parent, String name, NodeState theirs, NodeState base) {
+            return wrap(handler.deleteChangedNode(parent, name, theirs));
+        }
+
+        @Override
+        public Resolution deleteDeletedNode(NodeBuilder parent, String name, NodeState base) {
+            return wrap(handler.deleteDeletedNode(parent, name));
+        }
+    }
+}
diff --git a/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/spi/commit/PartialConflictHandler.java b/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/spi/commit/PartialConflictHandler.java
index 8769c622f0..a427d80a4c 100644
--- a/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/spi/commit/PartialConflictHandler.java
+++ b/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/spi/commit/PartialConflictHandler.java
@@ -41,6 +41,7 @@ import org.apache.jackrabbit.oak.spi.state.NodeState;
  *
  * @see ConflictHandler
  */
+@Deprecated
 public interface PartialConflictHandler {
 
     /**
diff --git a/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/spi/commit/ThreeWayConflictHandler.java b/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/spi/commit/ThreeWayConflictHandler.java
new file mode 100644
index 0000000000..0021c84cc8
--- /dev/null
+++ b/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/spi/commit/ThreeWayConflictHandler.java
@@ -0,0 +1,175 @@
+/*
+ * 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.oak.spi.commit;
+
+import javax.annotation.Nonnull;
+
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+/**
+ * A {@code ConflictHandler} is responsible for handling conflicts which happen
+ * on {@link org.apache.jackrabbit.oak.api.Root#rebase()} and on the implicit rebase operation which
+ * takes part on {@link org.apache.jackrabbit.oak.api.Root#commit()}.
+ *
+ * This interface contains one method per type of conflict which might occur.
+ * Each of these methods must return a {@link Resolution} for the current conflict.
+ * The resolution indicates to use the changes in the current {@code Root} instance
+ * ({@link Resolution#OURS}) or to use the changes from the underlying persistence
+ * store ({@link Resolution#THEIRS}). Alternatively the resolution can also indicate
+ * that the changes have been successfully merged by this {@code ConflictHandler}
+ * instance ({@link Resolution#MERGED}).
+ *
+ * @see ConflictHandler
+ */
+public interface ThreeWayConflictHandler {
+    /**
+     * Resolutions for conflicts
+     */
+    enum Resolution {
+        /**
+         * Use the changes from the current {@link org.apache.jackrabbit.oak.api.Root} instance
+         */
+        OURS,
+
+        /**
+         * Use the changes from the underlying persistence store
+         */
+        THEIRS,
+
+        /**
+         * Indicated changes have been merged by this {@code ConflictHandler} instance.
+         */
+        MERGED,
+
+        /**
+         * Changes are ignored by this handler (replaces null value).
+         */
+        IGNORED
+    }
+
+    /**
+     * The property {@code ours} has been added to {@code parent} which conflicts
+     * with property {@code theirs} which has been added in the persistence store.
+     *
+     * @param parent  root of the conflict
+     * @param ours  our version of the property
+     * @param theirs  their version of the property
+     * @param base  the base version of the property
+     * @return  {@link Resolution} of the conflict
+     */
+    @Nonnull
+    Resolution addExistingProperty(NodeBuilder parent, PropertyState ours, PropertyState theirs, PropertyState base);
+
+    /**
+     * The property {@code ours} has been changed in {@code parent} while it was
+     * removed in the persistence store.
+     *
+     * @param parent  root of the conflict
+     * @param ours  our version of the property
+     * @param base  the base version of the property
+     * @return  {@link Resolution} of the conflict
+     */
+    @Nonnull
+    Resolution changeDeletedProperty(NodeBuilder parent, PropertyState ours, PropertyState base);
+
+    /**
+     * The property {@code ours} has been changed in {@code parent} while it was
+     * also changed to a different value ({@code theirs}) in the persistence store.
+     *
+     * @param parent  root of the conflict
+     * @param ours  our version of the property
+     * @param theirs  their version of the property
+     * @param base  the base version of the property
+     * @return  {@link Resolution} of the conflict
+     */
+    @Nonnull
+    Resolution changeChangedProperty(NodeBuilder parent, PropertyState ours, PropertyState theirs, PropertyState base);
+
+    /**
+     * The property {@code ours} has been removed in {@code parent} while it was
+     * also removed in the persistence store.
+     *
+     * @param parent  root of the conflict
+     * @param ours  our version of the property
+     * @return  {@link Resolution} of the conflict
+     */
+    @Nonnull
+    Resolution deleteDeletedProperty(NodeBuilder parent, PropertyState ours, PropertyState base);
+
+    /**
+     * The property {@code theirs} changed in the persistence store while it has been
+     * deleted locally.
+     *
+     * @param parent  root of the conflict
+     * @param theirs  their version of the property
+     * @return  {@link Resolution} of the conflict
+     */
+    @Nonnull
+    Resolution deleteChangedProperty(NodeBuilder parent, PropertyState theirs, PropertyState base);
+
+    /**
+     * The node {@code ours} has been added to {@code parent} which conflicts
+     * with node {@code theirs} which has been added in the persistence store.
+     *
+     * @param parent  root of the conflict
+     * @param name  name of the node
+     * @param ours  our version of the node
+     * @param theirs  their version of the node
+     * @return  {@link Resolution} of the conflict
+     */
+    @Nonnull
+    Resolution addExistingNode(NodeBuilder parent, String name, NodeState ours, NodeState theirs, NodeState base);
+
+    /**
+     * The node {@code ours} has been changed in {@code parent} while it was
+     * removed in the persistence store.
+     *
+     * @param parent  root of the conflict
+     * @param name  name of the node
+     * @param ours  our version of the node
+     * @return  {@link Resolution} of the conflict
+     */
+    @Nonnull
+    Resolution changeDeletedNode(NodeBuilder parent, String name, NodeState ours, NodeState base);
+
+    /**
+     * The node {@code theirs} changed in the persistence store while it has been
+     * deleted locally.
+     *
+     * @param parent  root of the conflict
+     * @param name  name of the node
+     * @param theirs  their version of the node
+     * @return  {@link Resolution} of the conflict
+     */
+    @Nonnull
+    Resolution deleteChangedNode(NodeBuilder parent, String name, NodeState theirs, NodeState base);
+
+    /**
+     * The node {@code name} has been removed in {@code parent} while it was
+     * also removed in the persistence store.
+     *
+     * @param parent  root of the conflict
+     * @param name  name of the node
+     * @return  {@link Resolution} of the conflict
+     */
+    @Nonnull
+    Resolution deleteDeletedNode(NodeBuilder parent, String name, NodeState base);
+}
diff --git a/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/spi/state/ConflictAnnotatingRebaseDiff.java b/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/spi/state/ConflictAnnotatingRebaseDiff.java
index d846560d4a..6a22931451 100644
--- a/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/spi/state/ConflictAnnotatingRebaseDiff.java
+++ b/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/spi/state/ConflictAnnotatingRebaseDiff.java
@@ -36,6 +36,8 @@ import static org.apache.jackrabbit.oak.spi.state.ConflictType.DELETE_DELETED_NO
  */
 public class ConflictAnnotatingRebaseDiff extends AbstractRebaseDiff {
     public static final String CONFLICT = ":conflict";
+    public static final String BASE = ":base";
+    public static final String OURS = ":ours";
 
     public ConflictAnnotatingRebaseDiff(NodeBuilder builder) {
         super(builder);
@@ -48,47 +50,59 @@ public class ConflictAnnotatingRebaseDiff extends AbstractRebaseDiff {
 
     @Override
     protected void addExistingProperty(NodeBuilder builder, PropertyState before, PropertyState after) {
-        conflictMarker(builder, ADD_EXISTING_PROPERTY).setProperty(after);
+        NodeBuilder cb = conflictMarker(builder, ADD_EXISTING_PROPERTY);
+        cb.child(BASE).setProperty(before);
+        cb.child(OURS).setProperty(after);
     }
 
     @Override
     protected void changeDeletedProperty(NodeBuilder builder, PropertyState after) {
-        conflictMarker(builder, CHANGE_DELETED_PROPERTY).setProperty(after);
+        NodeBuilder cb = conflictMarker(builder, CHANGE_DELETED_PROPERTY);
+        cb.child(OURS).setProperty(after);
     }
 
     @Override
     protected void changeChangedProperty(NodeBuilder builder, PropertyState before, PropertyState after) {
-        conflictMarker(builder, CHANGE_CHANGED_PROPERTY).setProperty(after);
+        NodeBuilder cb = conflictMarker(builder, CHANGE_CHANGED_PROPERTY);
+        cb.child(BASE).setProperty(before);
+        cb.child(OURS).setProperty(after);
     }
 
     @Override
     protected void deleteDeletedProperty(NodeBuilder builder, PropertyState before) {
-        conflictMarker(builder, DELETE_DELETED_PROPERTY).setProperty(before);
+        NodeBuilder cb = conflictMarker(builder, DELETE_DELETED_PROPERTY);
+        cb.child(BASE).setProperty(before);
     }
 
     @Override
     protected void deleteChangedProperty(NodeBuilder builder, PropertyState before) {
-        conflictMarker(builder, DELETE_CHANGED_PROPERTY).setProperty(before);
+        NodeBuilder cb = conflictMarker(builder, DELETE_CHANGED_PROPERTY);
+        cb.child(BASE).setProperty(before);
     }
 
     @Override
     protected void addExistingNode(NodeBuilder builder, String name, NodeState before, NodeState after) {
-        conflictMarker(builder, ADD_EXISTING_NODE).setChildNode(name, after);
+        NodeBuilder cb = conflictMarker(builder, ADD_EXISTING_NODE);
+        cb.child(BASE).setChildNode(name, before);
+        cb.child(OURS).setChildNode(name, after);
     }
 
     @Override
     protected void changeDeletedNode(NodeBuilder builder, String name, NodeState after) {
-        conflictMarker(builder, CHANGE_DELETED_NODE).setChildNode(name, after);
+        NodeBuilder cb = conflictMarker(builder, CHANGE_DELETED_NODE);
+        cb.child(OURS).setChildNode(name, after);
     }
 
     @Override
     protected void deleteDeletedNode(NodeBuilder builder, String name, NodeState before) {
-        conflictMarker(builder, DELETE_DELETED_NODE).setChildNode(name, before);
+        NodeBuilder cb = conflictMarker(builder, DELETE_DELETED_NODE);
+        cb.child(BASE).setChildNode(name, before);
     }
 
     @Override
     protected void deleteChangedNode(NodeBuilder builder, String name, NodeState before) {
-        conflictMarker(builder, DELETE_CHANGED_NODE).setChildNode(name, before);
+        NodeBuilder cb = conflictMarker(builder, DELETE_CHANGED_NODE);
+        cb.child(BASE).setChildNode(name, before);
     }
 
     private static NodeBuilder conflictMarker(NodeBuilder builder, ConflictType ct) {