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 st...@apache.org on 2012/04/18 17:42:14 UTC
svn commit: r1327547 - in
/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk: core/
model/ store/
Author: stefan
Date: Wed Apr 18 15:42:14 2012
New Revision: 1327547
URL: http://svn.apache.org/viewvc?rev=1327547&view=rev
Log:
OAK-45: Add support for branching and merging of private copies to MicroKernel
Added:
jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/DiffBuilder.java
Modified:
jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/core/MicroKernelImpl.java
jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/AbstractCommit.java
jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/Commit.java
jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/CommitBuilder.java
jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/MutableCommit.java
jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/StoredCommit.java
jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/store/CopyingGC.java
jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/store/DefaultRevisionStore.java
jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/store/RevisionStore.java
Modified: jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/core/MicroKernelImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/core/MicroKernelImpl.java?rev=1327547&r1=1327546&r2=1327547&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/core/MicroKernelImpl.java (original)
+++ jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/core/MicroKernelImpl.java Wed Apr 18 15:42:14 2012
@@ -18,9 +18,7 @@ package org.apache.jackrabbit.mk.core;
import java.io.InputStream;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
import org.apache.jackrabbit.mk.api.MicroKernel;
import org.apache.jackrabbit.mk.api.MicroKernelException;
@@ -30,12 +28,11 @@ import org.apache.jackrabbit.mk.model.Ch
import org.apache.jackrabbit.mk.model.Commit;
import org.apache.jackrabbit.mk.model.CommitBuilder;
import org.apache.jackrabbit.mk.model.CommitBuilder.NodeTree;
+import org.apache.jackrabbit.mk.model.DiffBuilder;
import org.apache.jackrabbit.mk.model.Id;
import org.apache.jackrabbit.mk.model.NodeState;
import org.apache.jackrabbit.mk.model.PropertyState;
import org.apache.jackrabbit.mk.model.StoredCommit;
-import org.apache.jackrabbit.mk.model.TraversingNodeDiffHandler;
-import org.apache.jackrabbit.mk.store.RevisionProvider;
import org.apache.jackrabbit.mk.util.CommitGate;
import org.apache.jackrabbit.mk.util.PathUtils;
@@ -209,176 +206,21 @@ public class MicroKernelImpl implements
}
public String diff(String fromRevision, String toRevision, String filter) throws MicroKernelException {
- Id toRevisionId = toRevision == null ? getHeadRevisionId() : Id.fromString(toRevision);
-
- return diff(Id.fromString(fromRevision), toRevisionId, filter);
- }
-
- /**
- * Same as <code>diff</code>, with typed <code>Id</code> arguments instead of strings.
- *
- * @see #diff(String, String, String)
- */
- private String diff(Id fromRevisionId, Id toRevisionId, String filter) throws MicroKernelException {
// TODO extract and evaluate filter criteria (such as e.g. 'path') specified in 'filter' parameter
String path = "/";
- try {
- final JsopBuilder buff = new JsopBuilder();
- final RevisionProvider rp = rep.getRevisionStore();
- // maps (key: id of target node, value: path/to/target)
- // for tracking added/removed nodes; this allows us
- // to detect 'move' operations
- final HashMap<Id, String> addedNodes = new HashMap<Id, String>();
- final HashMap<Id, String> removedNodes = new HashMap<Id, String>();
- NodeState node1 = rep.getNodeState(fromRevisionId, path);
- NodeState node2 = rep.getNodeState(toRevisionId, path);
-
- if (node1 == null) {
- if (node2 != null) {
- buff.tag('+').key(path).object();
- toJson(buff, node2, Integer.MAX_VALUE, 0, -1, false);
- return buff.endObject().newline().toString();
- } else {
- throw new MicroKernelException("path doesn't exist in the specified revisions: " + path);
- }
- } else if (node2 == null) {
- buff.tag('-');
- buff.value(path);
- return buff.newline().toString();
- }
-
- TraversingNodeDiffHandler diffHandler = new TraversingNodeDiffHandler(rp) {
- @Override
- public void propertyAdded(PropertyState after) {
- buff.tag('+').
- key(PathUtils.concat(getCurrentPath(), after.getName())).
- encodedValue(after.getEncodedValue()).
- newline();
- }
-
- @Override
- public void propertyChanged(PropertyState before, PropertyState after) {
- buff.tag('^').
- key(PathUtils.concat(getCurrentPath(), after.getName())).
- encodedValue(after.getEncodedValue()).
- newline();
- }
-
- @Override
- public void propertyDeleted(PropertyState before) {
- // since property and node deletions can't be distinguished
- // using the "- <path>" notation we're representing
- // property deletions as "^ <path>:null"
- buff.tag('^').
- key(PathUtils.concat(getCurrentPath(), before.getName())).
- value(null).
- newline();
- }
-
- @Override
- public void childNodeAdded(String name, NodeState after) {
- addedNodes.put(rp.getId(after), PathUtils.concat(getCurrentPath(), name));
- buff.tag('+').
- key(PathUtils.concat(getCurrentPath(), name)).object();
- toJson(buff, after, Integer.MAX_VALUE, 0, -1, false);
- buff.endObject().newline();
- }
-
- @Override
- public void childNodeDeleted(String name, NodeState before) {
- removedNodes.put(rp.getId(before), PathUtils.concat(getCurrentPath(), name));
- buff.tag('-');
- buff.value(PathUtils.concat(getCurrentPath(), name));
- buff.newline();
- }
- };
- diffHandler.start(node1, node2, path);
-
- // check if this commit includes 'move' operations
- // by building intersection of added and removed nodes
- addedNodes.keySet().retainAll(removedNodes.keySet());
- if (!addedNodes.isEmpty()) {
- // this commit includes 'move' operations
- removedNodes.keySet().retainAll(addedNodes.keySet());
- // addedNodes & removedNodes now only contain information about moved nodes
-
- // re-build the diff in a 2nd pass, this time representing moves correctly
- buff.resetWriter();
-
- // TODO refactor code, avoid duplication
-
- diffHandler = new TraversingNodeDiffHandler(rp) {
- @Override
- public void propertyAdded(PropertyState after) {
- buff.tag('+').
- key(PathUtils.concat(getCurrentPath(), after.getName())).
- encodedValue(after.getEncodedValue()).
- newline();
- }
-
- @Override
- public void propertyChanged(PropertyState before, PropertyState after) {
- buff.tag('^').
- key(PathUtils.concat(getCurrentPath(), after.getName())).
- encodedValue(after.getEncodedValue()).
- newline();
- }
-
- @Override
- public void propertyDeleted(PropertyState before) {
- // since property and node deletions can't be distinguished
- // using the "- <path>" notation we're representing
- // property deletions as "^ <path>:null"
- buff.tag('^').
- key(PathUtils.concat(getCurrentPath(), before.getName())).
- value(null).
- newline();
- }
-
- @Override
- public void childNodeAdded(String name, NodeState after) {
- if (addedNodes.containsKey(rp.getId(after))) {
- // moved node, will be processed separately
- return;
- }
- buff.tag('+').
- key(PathUtils.concat(getCurrentPath(), name)).object();
- toJson(buff, after, Integer.MAX_VALUE, 0, -1, false);
- buff.endObject().newline();
- }
-
- @Override
- public void childNodeDeleted(String name, NodeState before) {
- if (addedNodes.containsKey(rp.getId(before))) {
- // moved node, will be processed separately
- return;
- }
- buff.tag('-');
- buff.value(PathUtils.concat(getCurrentPath(), name));
- buff.newline();
- }
-
- };
- diffHandler.start(node1, node2, path);
+ Id toRevisionId = toRevision == null ? getHeadRevisionId() : Id.fromString(toRevision);
- // finally process moved nodes
- for (Map.Entry<Id, String> entry : addedNodes.entrySet()) {
- buff.tag('>').
- // path/to/deleted/node
- key(removedNodes.get(entry.getKey())).
- // path/to/added/node
- value(entry.getValue()).
- newline();
- }
- }
- return buff.toString();
+ try {
+ NodeState before = rep.getNodeState(Id.fromString(fromRevision), path);
+ NodeState after = rep.getNodeState(toRevisionId, path);
+ return new DiffBuilder(before, after, path, rep.getRevisionStore(), filter).build();
} catch (Exception e) {
throw new MicroKernelException(e);
}
}
-
+
public boolean nodeExists(String path, String revisionId) throws MicroKernelException {
if (rep == null) {
throw new IllegalStateException("this instance has already been disposed");
@@ -569,6 +411,40 @@ public class MicroKernelImpl implements
}
}
+ public String branch(String publicRevisionId) throws MicroKernelException {
+ // create a private branch
+
+ if (rep == null) {
+ throw new IllegalStateException("this instance has already been disposed");
+ }
+
+ Id revId = publicRevisionId == null ? getHeadRevisionId() : Id.fromString(publicRevisionId);
+
+ try {
+ CommitBuilder cb = rep.getCommitBuilder(revId, "");
+ return cb.doCommit(true).toString();
+ } catch (Exception e) {
+ throw new MicroKernelException(e);
+ }
+ }
+
+ public String merge(String privateRevisionId) throws MicroKernelException {
+ // create a private branch
+
+ if (rep == null) {
+ throw new IllegalStateException("this instance has already been disposed");
+ }
+
+ Id revId = Id.fromString(privateRevisionId);
+
+ try {
+ CommitBuilder cb = rep.getCommitBuilder(revId, "");
+ return cb.doMerge().toString();
+ } catch (Exception e) {
+ throw new MicroKernelException(e);
+ }
+ }
+
public long getLength(String blobId) throws MicroKernelException {
if (rep == null) {
throw new IllegalStateException("this instance has already been disposed");
Modified: jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/AbstractCommit.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/AbstractCommit.java?rev=1327547&r1=1327546&r2=1327547&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/AbstractCommit.java (original)
+++ jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/AbstractCommit.java Wed Apr 18 15:42:14 2012
@@ -38,6 +38,9 @@ public abstract class AbstractCommit imp
// id of parent commit
protected Id parentId;
+ // id of branch root commit
+ protected Id branchRootId;
+
protected AbstractCommit() {
}
@@ -47,6 +50,7 @@ public abstract class AbstractCommit imp
this.msg = other.getMsg();
this.changes = other.getChanges();
this.commitTS = other.getCommitTS();
+ this.branchRootId = other.getBranchRootId();
}
public Id getParentId() {
@@ -69,11 +73,16 @@ public abstract class AbstractCommit imp
return changes;
}
+ public Id getBranchRootId() {
+ return branchRootId;
+ }
+
public void serialize(Binding binding) throws Exception {
binding.write("rootNodeId", rootNodeId.getBytes());
binding.write("commitTS", commitTS);
binding.write("msg", msg == null ? "" : msg);
binding.write("changes", changes == null ? "" : changes);
binding.write("parentId", parentId == null ? "" : parentId.toString());
+ binding.write("branchRootId", branchRootId == null ? "" : branchRootId.toString());
}
}
Modified: jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/Commit.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/Commit.java?rev=1327547&r1=1327546&r2=1327547&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/Commit.java (original)
+++ jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/Commit.java Wed Apr 18 15:42:14 2012
@@ -25,13 +25,25 @@ public interface Commit {
Id getRootNodeId();
- public Id getParentId();
+ Id getParentId();
- public long getCommitTS();
+ long getCommitTS();
- public String getMsg();
+ String getMsg();
- public String getChanges();
+ String getChanges();
+
+ /**
+ * Returns {@code null} if this commit does not represent a branch.
+ * <p/>
+ * Otherwise, returns the id of the branch root commit
+ * (i.e. the <i>public</i> commit that this <i>private</i> branch is based upon).
+ *
+ *
+ * @return the id of the branch root commit or {@code null} if this commit
+ * does not represent a branch.
+ */
+ Id getBranchRootId();
void serialize(Binding binding) throws Exception;
}
Modified: jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/CommitBuilder.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/CommitBuilder.java?rev=1327547&r1=1327546&r2=1327547&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/CommitBuilder.java (original)
+++ jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/CommitBuilder.java Wed Apr 18 15:42:14 2012
@@ -33,6 +33,7 @@ import org.apache.jackrabbit.mk.util.Pat
*/
public class CommitBuilder {
+ /** revision changes are based upon */
private Id baseRevId;
private final String msg;
@@ -86,47 +87,80 @@ public class CommitBuilder {
}
public Id /* new revId */ doCommit() throws Exception {
- if (staged.isEmpty()) {
+ return doCommit(false);
+ }
+
+ public Id /* new revId */ doCommit(boolean createBranch) throws Exception {
+ if (staged.isEmpty() && !createBranch) {
// nothing to commit
return baseRevId;
}
- Id currentHead = store.getHeadCommitId();
- if (!currentHead.equals(baseRevId)) {
- // todo gracefully handle certain conflicts (e.g. changes on moved sub-trees, competing deletes etc)
- // update base revision to new head
- baseRevId = currentHead;
- // clear staging area
- staged.clear();
- // replay change log on new base revision
- for (Change change : changeLog) {
- change.apply();
+ StoredCommit baseCommit = store.getCommit(baseRevId);
+ boolean privateCommit = createBranch || baseCommit.getBranchRootId() != null;
+
+
+ if (!privateCommit) {
+ Id currentHead = store.getHeadCommitId();
+ if (!currentHead.equals(baseRevId)) {
+ // todo gracefully handle certain conflicts (e.g. changes on moved sub-trees, competing deletes etc)
+ // update base revision to new head
+ baseRevId = currentHead;
+ // clear staging area
+ staged.clear();
+ // replay change log on new base revision
+ for (Change change : changeLog) {
+ change.apply();
+ }
}
}
- Id rootNodeId = persistStagedNodes();
+ Id rootNodeId =
+ changeLog.isEmpty() ? baseCommit.getRootNodeId() : persistStagedNodes();
Id newRevId;
- store.lockHead();
- try {
- currentHead = store.getHeadCommitId();
- if (!currentHead.equals(baseRevId)) {
- StoredNode baseRoot = store.getRootNode(baseRevId);
- StoredNode theirRoot = store.getRootNode(currentHead);
- StoredNode ourRoot = store.getNode(rootNodeId);
- rootNodeId = mergeTree(baseRoot, ourRoot, theirRoot);
+ if (!privateCommit) {
+ store.lockHead();
+ try {
+ Id currentHead = store.getHeadCommitId();
+ if (!currentHead.equals(baseRevId)) {
+ StoredNode baseRoot = store.getRootNode(baseRevId);
+ StoredNode theirRoot = store.getRootNode(currentHead);
+ StoredNode ourRoot = store.getNode(rootNodeId);
- baseRevId = currentHead;
- }
+ rootNodeId = mergeTree(baseRoot, ourRoot, theirRoot);
- if (store.getCommit(currentHead).getRootNodeId().equals(rootNodeId)) {
- // the commit didn't cause any changes,
- // no need to create new commit object/update head revision
- return currentHead;
+ baseRevId = currentHead;
+ }
+
+ if (store.getCommit(currentHead).getRootNodeId().equals(rootNodeId)) {
+ // the commit didn't cause any changes,
+ // no need to create new commit object/update head revision
+ return currentHead;
+ }
+ MutableCommit newCommit = new MutableCommit();
+ newCommit.setParentId(baseRevId);
+ newCommit.setCommitTS(System.currentTimeMillis());
+ newCommit.setMsg(msg);
+ StringBuilder diff = new StringBuilder();
+ for (Change change : changeLog) {
+ if (diff.length() > 0) {
+ diff.append('\n');
+ }
+ diff.append(change.asDiff());
+ }
+ newCommit.setChanges(diff.toString());
+ newCommit.setRootNodeId(rootNodeId);
+ newCommit.setBranchRootId(null);
+ newRevId = store.putHeadCommit(newCommit);
+ } finally {
+ store.unlockHead();
}
+ } else {
+ // private commit/branch
MutableCommit newCommit = new MutableCommit();
- newCommit.setParentId(baseRevId);
+ newCommit.setParentId(baseCommit.getId());
newCommit.setCommitTS(System.currentTimeMillis());
newCommit.setMsg(msg);
StringBuilder diff = new StringBuilder();
@@ -138,6 +172,57 @@ public class CommitBuilder {
}
newCommit.setChanges(diff.toString());
newCommit.setRootNodeId(rootNodeId);
+ if (createBranch) {
+ newCommit.setBranchRootId(baseCommit.getId());
+ } else {
+ newCommit.setBranchRootId(baseCommit.getBranchRootId());
+ }
+ newRevId = store.putCommit(newCommit);
+ }
+
+ // reset instance
+ staged.clear();
+ changeLog.clear();
+
+ return newRevId;
+ }
+
+ public Id /* new revId */ doMerge() throws Exception {
+ StoredCommit branchCommit = store.getCommit(baseRevId);
+ Id branchRootId = branchCommit.getBranchRootId();
+ if (branchRootId == null) {
+ throw new Exception("can only merge a private branch commit");
+ }
+
+ Id rootNodeId =
+ changeLog.isEmpty() ? branchCommit.getRootNodeId() : persistStagedNodes();
+
+ Id newRevId;
+
+ store.lockHead();
+ try {
+ Id currentHead = store.getHeadCommitId();
+
+ StoredNode baseRoot = store.getRootNode(branchRootId);
+ StoredNode theirRoot = store.getRootNode(currentHead);
+ StoredNode ourRoot = store.getNode(rootNodeId);
+
+ rootNodeId = mergeTree(baseRoot, ourRoot, theirRoot);
+
+ if (store.getCommit(currentHead).getRootNodeId().equals(rootNodeId)) {
+ // the merge didn't cause any changes,
+ // no need to create new commit object/update head revision
+ return currentHead;
+ }
+ MutableCommit newCommit = new MutableCommit();
+ newCommit.setParentId(currentHead);
+ newCommit.setCommitTS(System.currentTimeMillis());
+ newCommit.setMsg(msg);
+ // dynamically build diff of merged commit
+ String diff = new DiffBuilder(store.getNodeState(theirRoot), store.getNodeState(ourRoot), "/", store, "").build();
+ newCommit.setChanges(diff);
+ newCommit.setRootNodeId(rootNodeId);
+ newCommit.setBranchRootId(null);
newRevId = store.putHeadCommit(newCommit);
} finally {
store.unlockHead();
@@ -150,7 +235,7 @@ public class CommitBuilder {
return newRevId;
}
- //--------------------------------------------------------< inner classes >
+ //-------------------------------------------------------< implementation >
MutableNode getOrCreateStagedNode(String nodePath) throws Exception {
MutableNode node = staged.get(nodePath);
Added: jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/DiffBuilder.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/DiffBuilder.java?rev=1327547&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/DiffBuilder.java (added)
+++ jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/DiffBuilder.java Wed Apr 18 15:42:14 2012
@@ -0,0 +1,206 @@
+/*
+ * 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.mk.model;
+
+import org.apache.jackrabbit.mk.json.JsopBuilder;
+import org.apache.jackrabbit.mk.util.PathUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * JSOP Diff Builder
+ */
+public class DiffBuilder {
+
+ private final NodeState before;
+ private final NodeState after;
+ private final String path;
+ private final String filter;
+ private final NodeStore store;
+
+ public DiffBuilder(NodeState before, NodeState after, String path,
+ NodeStore store, String filter) {
+ this.before = before;
+ this.after = after;
+ this.path = path;
+ this.store = store;
+ this.filter = filter;
+ }
+
+ public String build() throws Exception {
+ // TODO extract and evaluate filter criteria specified in 'filter' parameter
+
+ final JsopBuilder buff = new JsopBuilder();
+ // maps (key: id of target node, value: path/to/target)
+ // for tracking added/removed nodes; this allows us
+ // to detect 'move' operations
+ final HashMap<NodeState, String> addedNodes = new HashMap<NodeState, String>();
+ final HashMap<NodeState, String> removedNodes = new HashMap<NodeState, String>();
+
+ if (before == null) {
+ if (after != null) {
+ buff.tag('+').key(path).object();
+ toJson(buff, after);
+ return buff.endObject().newline().toString();
+ } else {
+ throw new Exception("path doesn't exist in the specified revisions: " + path);
+ }
+ } else if (after == null) {
+ buff.tag('-');
+ buff.value(path);
+ return buff.newline().toString();
+ }
+
+ TraversingNodeDiffHandler diffHandler = new TraversingNodeDiffHandler(store) {
+ @Override
+ public void propertyAdded(PropertyState after) {
+ buff.tag('+').
+ key(PathUtils.concat(getCurrentPath(), after.getName())).
+ encodedValue(after.getEncodedValue()).
+ newline();
+ }
+
+ @Override
+ public void propertyChanged(PropertyState before, PropertyState after) {
+ buff.tag('^').
+ key(PathUtils.concat(getCurrentPath(), after.getName())).
+ encodedValue(after.getEncodedValue()).
+ newline();
+ }
+
+ @Override
+ public void propertyDeleted(PropertyState before) {
+ // since property and node deletions can't be distinguished
+ // using the "- <path>" notation we're representing
+ // property deletions as "^ <path>:null"
+ buff.tag('^').
+ key(PathUtils.concat(getCurrentPath(), before.getName())).
+ value(null).
+ newline();
+ }
+
+ @Override
+ public void childNodeAdded(String name, NodeState after) {
+ addedNodes.put(after, PathUtils.concat(getCurrentPath(), name));
+ buff.tag('+').
+ key(PathUtils.concat(getCurrentPath(), name)).object();
+ toJson(buff, after);
+ buff.endObject().newline();
+ }
+
+ @Override
+ public void childNodeDeleted(String name, NodeState before) {
+ removedNodes.put(before, PathUtils.concat(getCurrentPath(), name));
+ buff.tag('-');
+ buff.value(PathUtils.concat(getCurrentPath(), name));
+ buff.newline();
+ }
+ };
+ diffHandler.start(before, after, path);
+
+ // check if this commit includes 'move' operations
+ // by building intersection of added and removed nodes
+ addedNodes.keySet().retainAll(removedNodes.keySet());
+ if (!addedNodes.isEmpty()) {
+ // this commit includes 'move' operations
+ removedNodes.keySet().retainAll(addedNodes.keySet());
+ // addedNodes & removedNodes now only contain information about moved nodes
+
+ // re-build the diff in a 2nd pass, this time representing moves correctly
+ buff.resetWriter();
+
+ // TODO refactor code, avoid duplication
+
+ diffHandler = new TraversingNodeDiffHandler(store) {
+ @Override
+ public void propertyAdded(PropertyState after) {
+ buff.tag('+').
+ key(PathUtils.concat(getCurrentPath(), after.getName())).
+ encodedValue(after.getEncodedValue()).
+ newline();
+ }
+
+ @Override
+ public void propertyChanged(PropertyState before, PropertyState after) {
+ buff.tag('^').
+ key(PathUtils.concat(getCurrentPath(), after.getName())).
+ encodedValue(after.getEncodedValue()).
+ newline();
+ }
+
+ @Override
+ public void propertyDeleted(PropertyState before) {
+ // since property and node deletions can't be distinguished
+ // using the "- <path>" notation we're representing
+ // property deletions as "^ <path>:null"
+ buff.tag('^').
+ key(PathUtils.concat(getCurrentPath(), before.getName())).
+ value(null).
+ newline();
+ }
+
+ @Override
+ public void childNodeAdded(String name, NodeState after) {
+ if (addedNodes.containsKey(after)) {
+ // moved node, will be processed separately
+ return;
+ }
+ buff.tag('+').
+ key(PathUtils.concat(getCurrentPath(), name)).object();
+ toJson(buff, after);
+ buff.endObject().newline();
+ }
+
+ @Override
+ public void childNodeDeleted(String name, NodeState before) {
+ if (addedNodes.containsKey(before)) {
+ // moved node, will be processed separately
+ return;
+ }
+ buff.tag('-');
+ buff.value(PathUtils.concat(getCurrentPath(), name));
+ buff.newline();
+ }
+
+ };
+ diffHandler.start(before, after, path);
+
+ // finally process moved nodes
+ for (Map.Entry<NodeState, String> entry : addedNodes.entrySet()) {
+ buff.tag('>').
+ // path/to/deleted/node
+ key(removedNodes.get(entry.getKey())).
+ // path/to/added/node
+ value(entry.getValue()).
+ newline();
+ }
+ }
+ return buff.toString();
+ }
+
+ private void toJson(JsopBuilder builder, NodeState node) {
+ for (PropertyState property : node.getProperties()) {
+ builder.key(property.getName()).encodedValue(property.getEncodedValue());
+ }
+ for (ChildNodeEntry entry : node.getChildNodeEntries(0, -1)) {
+ builder.key(entry.getName()).object();
+ toJson(builder, entry.getNode());
+ builder.endObject();
+ }
+ }
+}
Modified: jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/MutableCommit.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/MutableCommit.java?rev=1327547&r1=1327546&r2=1327547&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/MutableCommit.java (original)
+++ jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/MutableCommit.java Wed Apr 18 15:42:14 2012
@@ -40,6 +40,7 @@ public class MutableCommit extends Abstr
setCommitTS(other.getCommitTS());
setMsg(other.getMsg());
setChanges(other.getChanges());
+ setBranchRootId(other.getBranchRootId());
this.id = other.getId();
}
@@ -63,6 +64,10 @@ public class MutableCommit extends Abstr
this.changes = changes;
}
+ public void setBranchRootId(Id branchRootId) {
+ this.branchRootId = branchRootId;
+ }
+
/**
* Return the commit id.
*
Modified: jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/StoredCommit.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/StoredCommit.java?rev=1327547&r1=1327546&r2=1327547&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/StoredCommit.java (original)
+++ jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/StoredCommit.java Wed Apr 18 15:42:14 2012
@@ -31,17 +31,20 @@ public class StoredCommit extends Abstra
String msg = binding.readStringValue("msg");
String changes = binding.readStringValue("changes");
String parentId = binding.readStringValue("parentId");
+ String branchRootId = binding.readStringValue("branchRootId");
return new StoredCommit(id, "".equals(parentId) ? null : Id.fromString(parentId),
- commitTS, rootNodeId, "".equals(msg) ? null : msg, changes);
+ commitTS, rootNodeId, "".equals(msg) ? null : msg, changes,
+ "".equals(parentId) ? null : Id.fromString(branchRootId));
}
- public StoredCommit(Id id, Id parentId, long commitTS, Id rootNodeId, String msg, String changes) {
+ public StoredCommit(Id id, Id parentId, long commitTS, Id rootNodeId, String msg, String changes, Id branchRootId) {
this.id = id;
this.parentId = parentId;
this.commitTS = commitTS;
this.rootNodeId = rootNodeId;
this.msg = msg;
this.changes = changes;
+ this.branchRootId = branchRootId;
}
public StoredCommit(Id id, Commit commit) {
Modified: jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/store/CopyingGC.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/store/CopyingGC.java?rev=1327547&r1=1327546&r2=1327547&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/store/CopyingGC.java (original)
+++ jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/store/CopyingGC.java Wed Apr 18 15:42:14 2012
@@ -204,6 +204,11 @@ public class CopyingGC extends AbstractR
return runState.get() == STARTED ? rsTo.putHeadCommit(commit) : rsFrom.putHeadCommit(commit);
}
+ public Id putCommit(MutableCommit commit) throws Exception {
+ // TODO: review, should GC ignore private branch commits?
+ return runState.get() == STARTED ? rsTo.putCommit(commit) : rsFrom.putCommit(commit);
+ }
+
// TODO: potentially dangerous, if lock & unlock interfere with GC start
public void unlockHead() {
if (runState.get() == STARTED) {
Modified: jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/store/DefaultRevisionStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/store/DefaultRevisionStore.java?rev=1327547&r1=1327546&r2=1327547&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/store/DefaultRevisionStore.java (original)
+++ jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/store/DefaultRevisionStore.java Wed Apr 18 15:42:14 2012
@@ -19,6 +19,7 @@ package org.apache.jackrabbit.mk.store;
import java.io.Closeable;
import java.util.Collections;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.jackrabbit.mk.model.ChildNode;
@@ -48,7 +49,7 @@ public class DefaultRevisionStore extend
private boolean initialized;
private Id head;
- private long headCounter;
+ private AtomicLong commitCounter;
private final ReentrantReadWriteLock headLock = new ReentrantReadWriteLock();
private final Persistence pm;
@@ -56,6 +57,7 @@ public class DefaultRevisionStore extend
public DefaultRevisionStore(Persistence pm) {
this.pm = pm;
+ commitCounter = new AtomicLong();
}
public void initialize() throws Exception {
@@ -69,7 +71,7 @@ public class DefaultRevisionStore extend
head = pm.readHead();
if (head == null || head.getBytes().length == 0) {
// assume virgin repository
- byte[] rawHead = longToBytes(++headCounter);
+ byte[] rawHead = longToBytes(commitCounter.incrementAndGet());
head = new Id(rawHead);
Id rootNodeId = pm.writeNode(new MutableNode(this, "/"));
@@ -79,7 +81,7 @@ public class DefaultRevisionStore extend
pm.writeCommit(head, initialCommit);
pm.writeHead(head);
} else {
- headCounter = Long.parseLong(head.toString(), 16);
+ commitCounter.set(Long.parseLong(head.toString(), 16));
}
initialized = true;
@@ -171,38 +173,19 @@ public class DefaultRevisionStore extend
public Id putHeadCommit(MutableCommit commit) throws Exception {
verifyInitialized();
if (!headLock.writeLock().isHeldByCurrentThread()) {
- throw new IllegalStateException("putCommit called without holding write lock.");
+ throw new IllegalStateException("putHeadCommit called without holding write lock.");
}
- PersistHook callback = null;
- if (commit instanceof PersistHook) {
- callback = (PersistHook) commit;
- callback.prePersist(this);
- }
-
- Id id = commit.getId();
- if (id == null) {
- id = new Id(longToBytes(++headCounter));
- }
- pm.writeCommit(id, commit);
+ Id id = writeCommit(commit);
setHeadCommitId(id);
- if (callback != null) {
- callback.postPersist(this);
- }
- cache.put(id, new StoredCommit(id, commit));
-
return id;
}
- private void setHeadCommitId(Id id) throws Exception {
- pm.writeHead(id);
- head = id;
-
- long headCounter = Long.parseLong(id.toString(), 16);
- if (headCounter > this.headCounter) {
- this.headCounter = headCounter;
- }
+ public Id putCommit(MutableCommit commit) throws Exception {
+ verifyInitialized();
+
+ return writeCommit(commit);
}
public void unlockHead() {
@@ -275,8 +258,41 @@ public class DefaultRevisionStore extend
}
}
- //------------------------------------------------------------< overrides >
+ //-------------------------------------------------------< implementation >
+ private Id writeCommit(MutableCommit commit) throws Exception {
+ PersistHook callback = null;
+ if (commit instanceof PersistHook) {
+ callback = (PersistHook) commit;
+ callback.prePersist(this);
+ }
+
+ Id id = commit.getId();
+ if (id == null) {
+ id = new Id(longToBytes(commitCounter.incrementAndGet()));
+ }
+ pm.writeCommit(id, commit);
+
+ if (callback != null) {
+ callback.postPersist(this);
+ }
+ cache.put(id, new StoredCommit(id, commit));
+ return id;
+ }
+
+ private void setHeadCommitId(Id id) throws Exception {
+ // non-synchronized since we're called from putHeadCommit
+ // which requires a write lock
+ pm.writeHead(id);
+ head = id;
+
+ long counter = Long.parseLong(id.toString(), 16);
+ if (counter > commitCounter.get()) {
+ commitCounter.set(counter);
+ }
+ }
+
+ //------------------------------------------------------------< overrides >
@Override
public void compare(final NodeState before, final NodeState after, final NodeStateDiff diff) {
Modified: jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/store/RevisionStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/store/RevisionStore.java?rev=1327547&r1=1327546&r2=1327547&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/store/RevisionStore.java (original)
+++ jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/store/RevisionStore.java Wed Apr 18 15:42:14 2012
@@ -33,6 +33,7 @@ public interface RevisionStore extends R
* Lock the head. Must be called prior to putting a new head commit.
*
* @see #putHeadCommit(MutableCommit)
+ * @see #unlockHead()
*/
void lockHead();
@@ -49,6 +50,21 @@ public interface RevisionStore extends R
/**
* Unlock the head.
+ *
+ * @see #lockHead()
*/
void unlockHead();
+
+ /**
+ * Store a new commit.
+ * <p/>
+ * Unlike {@code putHeadCommit(MutableCommit)}, this method
+ * does not affect the current head commit and therefore doesn't
+ * require a lock on the head.
+ *
+ * @param commit commit
+ * @return new commit id
+ * @throws Exception if an error occurs
+ */
+ Id /*id*/ putCommit(MutableCommit commit) throws Exception;
}