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 ch...@apache.org on 2016/06/21 06:19:59 UTC
svn commit: r1749435 - in /jackrabbit/oak/trunk/oak-core/src:
main/java/org/apache/jackrabbit/oak/plugins/document/
main/java/org/apache/jackrabbit/oak/plugins/document/secondary/
test/java/org/apache/jackrabbit/oak/plugins/document/secondary/
Author: chetanm
Date: Tue Jun 21 06:19:59 2016
New Revision: 1749435
URL: http://svn.apache.org/viewvc?rev=1749435&view=rev
Log:
OAK-4180 - Use another NodeStore as a local cache for a remote Document store
-- Changed cache logic to only provide a path for given lastRev. No need to work at parent lastRev
-- Changed DocumentNodeState to also check with cache for getChildNodeEntries call
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStateCache.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCache.java
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCacheTest.java
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java?rev=1749435&r1=1749434&r2=1749435&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java Tue Jun 21 06:19:59 2016
@@ -25,6 +25,7 @@ import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
+import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -85,6 +86,8 @@ public class DocumentNodeState extends A
private final DocumentNodeStore store;
+ private AbstractDocumentNodeState cachedSecondaryState;
+
DocumentNodeState(@Nonnull DocumentNodeStore store,
@Nonnull String path,
@Nonnull RevisionVector readRevision) {
@@ -222,8 +225,7 @@ public class DocumentNodeState extends A
if (!hasChildren || !isValidName(name)) {
return false;
} else {
- String p = PathUtils.concat(getPath(), name);
- return store.getNode(p, rootRevision, lastRevision) != null;
+ return getChildNodeDoc(name) != null;
}
}
@@ -234,8 +236,7 @@ public class DocumentNodeState extends A
checkValidName(name);
return EmptyNodeState.MISSING_NODE;
}
- String p = PathUtils.concat(getPath(), name);
- AbstractDocumentNodeState child = store.getNode(p, rootRevision, lastRevision);
+ AbstractDocumentNodeState child = getChildNodeDoc(name);
if (child == null) {
checkValidName(name);
return EmptyNodeState.MISSING_NODE;
@@ -273,6 +274,12 @@ public class DocumentNodeState extends A
if (!hasChildren) {
return Collections.emptyList();
}
+
+ AbstractDocumentNodeState secondaryState = getSecondaryNodeState();
+ if (secondaryState != null){
+ return secondaryState.getChildNodeEntries();
+ }
+
return new Iterable<ChildNodeEntry>() {
@Override
public Iterator<ChildNodeEntry> iterator() {
@@ -433,6 +440,29 @@ public class DocumentNodeState extends A
//------------------------------< internal >--------------------------------
+ @CheckForNull
+ private AbstractDocumentNodeState getChildNodeDoc(String childNodeName){
+ AbstractDocumentNodeState secondaryState = getSecondaryNodeState();
+ if (secondaryState != null){
+ NodeState result = secondaryState.getChildNode(childNodeName);
+ //If given child node exist then cast it and return
+ //else return null
+ if (result.exists()){
+ return (AbstractDocumentNodeState) result;
+ }
+ return null;
+ }
+ return store.getNode(PathUtils.concat(getPath(), childNodeName), lastRevision);
+ }
+
+ @CheckForNull
+ private AbstractDocumentNodeState getSecondaryNodeState(){
+ if (cachedSecondaryState == null){
+ cachedSecondaryState = store.getSecondaryNodeState(getPath(), rootRevision, lastRevision);
+ }
+ return cachedSecondaryState;
+ }
+
/**
* Returns {@code true} if this state has the same revision as the
* {@code other} state. This method first compares the {@link #readRevision}
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStateCache.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStateCache.java?rev=1749435&r1=1749434&r2=1749435&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStateCache.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStateCache.java Tue Jun 21 06:19:59 2016
@@ -19,68 +19,31 @@
package org.apache.jackrabbit.oak.plugins.document;
-import javax.annotation.Nonnull;
+import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
-import static com.google.common.base.Preconditions.checkState;
public interface DocumentNodeStateCache {
DocumentNodeStateCache NOOP = new DocumentNodeStateCache() {
- @Nonnull
@Override
- public NodeStateCacheEntry getDocumentNodeState(String path, @Nullable RevisionVector rootRevision,
- RevisionVector parentLastRev) {
- return UNKNOWN;
+ public AbstractDocumentNodeState getDocumentNodeState(String path, @Nullable RevisionVector rootRevision,
+ RevisionVector lastRev) {
+ return null;
}
};
- NodeStateCacheEntry MISSING = new NodeStateCacheEntry(NodeStateCacheEntry.EntryType.MISSING);
-
- NodeStateCacheEntry UNKNOWN = new NodeStateCacheEntry(NodeStateCacheEntry.EntryType.UNKNOWN);
-
/**
* Get the node for the given path and revision.
*
* @param path the path of the node.
- * @param rootRevision
- * @param readRevision the read revision.
- * @return the node or {@link MISSING} if no state is there for given path and revision or {@link UNKNOWN} if
- * cache does not have any knowledge of nodeState for given parameters
+ * @param rootRevision revision of root NodeState
+ * @param lastRev last revision of the node at given path
+ *
+ * @return nodeState at given path or null. If given revision is not present or the
+ * path is not cached then <code>null</code> would be returned
*/
- @Nonnull
- NodeStateCacheEntry getDocumentNodeState(String path, RevisionVector rootRevision,
- RevisionVector parentLastRev);
-
- class NodeStateCacheEntry {
- private enum EntryType {FOUND, MISSING, UNKNOWN}
- private final AbstractDocumentNodeState state;
- private final EntryType entryType;
-
- public NodeStateCacheEntry(AbstractDocumentNodeState state) {
- this.state = state;
- this.entryType = EntryType.FOUND;
- }
-
- private NodeStateCacheEntry(EntryType entryType) {
- this.state = null;
- this.entryType = entryType;
- }
+ @CheckForNull
+ AbstractDocumentNodeState getDocumentNodeState(String path, RevisionVector rootRevision, RevisionVector lastRev);
- public AbstractDocumentNodeState getState(){
- checkState(entryType == EntryType.FOUND, "Cannot read state from an entry of type [%s]", entryType);
- return state;
- }
-
- public boolean isUnknown(){
- return entryType == EntryType.UNKNOWN;
- }
- public boolean isMissing(){
- return entryType == EntryType.MISSING;
- }
-
- public boolean isFound(){
- return entryType == EntryType.FOUND;
- }
- }
}
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java?rev=1749435&r1=1749434&r2=1749435&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java Tue Jun 21 06:19:59 2016
@@ -872,18 +872,11 @@ public final class DocumentNodeStore
}
@CheckForNull
- AbstractDocumentNodeState getNode(@Nonnull final String path,
+ AbstractDocumentNodeState getSecondaryNodeState(@Nonnull final String path,
@Nonnull final RevisionVector rootRevision,
@Nonnull final RevisionVector rev) {
//Check secondary cache first
- NodeStateCacheEntry entry = nodeStateCache.getDocumentNodeState(path, rootRevision, rev);
- if (entry.isMissing()){
- return null;
- } else if (entry.isFound()){
- return entry.getState();
- }
-
- return getNode(path, rev);
+ return nodeStateCache.getDocumentNodeState(path, rootRevision, rev);
}
/**
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCache.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCache.java?rev=1749435&r1=1749434&r2=1749435&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCache.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCache.java Tue Jun 21 06:19:59 2016
@@ -76,29 +76,28 @@ class SecondaryStoreCache implements Doc
this.queue = EvictingQueue.create(maxSize);
}
- @Nonnull
+ @CheckForNull
@Override
- public NodeStateCacheEntry getDocumentNodeState(String path, RevisionVector rootRevision,
- RevisionVector parentLastRev) {
+ public AbstractDocumentNodeState getDocumentNodeState(String path, RevisionVector rootRevision,
+ RevisionVector lastRev) {
//TODO We might need skip the calls if they occur due to SecondaryStoreObserver
//doing the diff or in the startup when we try to sync the state
PathFilter.Result result = pathFilter.filter(path);
if (result != PathFilter.Result.INCLUDE) {
unknownPaths.mark();
- return DocumentNodeStateCache.UNKNOWN;
+ return null;
}
if (!DelegatingDocumentNodeState.hasMetaProps(store.getRoot())){
- return DocumentNodeStateCache.UNKNOWN;
+ return null;
}
AbstractDocumentNodeState currentRoot = DelegatingDocumentNodeState.wrap(store.getRoot(), differ);
- NodeStateCacheEntry cacheEntryResult = findByMatchingParentLastRev(currentRoot, path,
- rootRevision, parentLastRev);
- if (cacheEntryResult != DocumentNodeStateCache.UNKNOWN){
+ AbstractDocumentNodeState nodeState = findByMatchingLastRev(currentRoot, path, lastRev);
+ if (nodeState != null){
headRevMatched.mark();
- return cacheEntryResult;
+ return nodeState;
}
AbstractDocumentNodeState matchingRoot = findMatchingRoot(rootRevision);
@@ -107,45 +106,32 @@ class SecondaryStoreCache implements Doc
if (state.exists()){
AbstractDocumentNodeState docState = (AbstractDocumentNodeState) state;
prevRevMatched.mark();
- return new NodeStateCacheEntry(docState);
- } else {
- return DocumentNodeStateCache.MISSING;
+ return docState;
}
}
- //TODO Check in tail if rootRevision is not in our maintained list of root
knownMissed.mark();
- return DocumentNodeStateCache.UNKNOWN;
+ return null;
}
- @Nonnull
- private NodeStateCacheEntry findByMatchingParentLastRev(AbstractDocumentNodeState root, String path,
- RevisionVector rootRevision, RevisionVector parentLastRev){
- NodeState parentNodeState = root;
+ @CheckForNull
+ private AbstractDocumentNodeState findByMatchingLastRev(AbstractDocumentNodeState root, String path,
+ RevisionVector lastRev){
NodeState state = root;
- //Get the parent node state
for (String name : PathUtils.elements(path)) {
- parentNodeState = state;
state = state.getChildNode(name);
}
- if (parentNodeState.exists()) {
- AbstractDocumentNodeState parentDocState = (AbstractDocumentNodeState) parentNodeState;
- //So parent state exists and matches the expected revision
- if (parentLastRev.equals(parentDocState.getLastRevision())) {
+ if (state.exists()) {
+ AbstractDocumentNodeState docState = (AbstractDocumentNodeState) state;
+ if (lastRev.equals(docState.getLastRevision())) {
headRevMatched.mark();
- if (state.exists()) {
- AbstractDocumentNodeState stateAtExpectedRootRev =
- ((AbstractDocumentNodeState) state).withRootRevision(rootRevision, false);
- return new NodeStateCacheEntry(stateAtExpectedRootRev);
- } else {
- return DocumentNodeStateCache.MISSING;
- }
+ return docState;
}
}
- return DocumentNodeStateCache.UNKNOWN;
+ return null;
}
@CheckForNull
Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCacheTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCacheTest.java?rev=1749435&r1=1749434&r2=1749435&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCacheTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCacheTest.java Tue Jun 21 06:19:59 2016
@@ -25,10 +25,9 @@ import java.util.List;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
import org.apache.jackrabbit.oak.plugins.document.AbstractDocumentNodeState;
import org.apache.jackrabbit.oak.plugins.document.DocumentMKBuilderProvider;
-import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStateCache;
-import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStateCache.NodeStateCacheEntry;
import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore;
import org.apache.jackrabbit.oak.plugins.document.Revision;
import org.apache.jackrabbit.oak.plugins.document.RevisionVector;
@@ -48,7 +47,10 @@ import static com.google.common.collect.
import static org.apache.jackrabbit.oak.plugins.document.NodeStateDiffer.DEFAULT_DIFFER;
import static org.apache.jackrabbit.oak.plugins.document.secondary.SecondaryStoreObserverTest.create;
import static org.apache.jackrabbit.oak.plugins.document.secondary.SecondaryStoreObserverTest.documentState;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
public class SecondaryStoreCacheTest {
private final List<String> empty = Collections.emptyList();
@@ -66,104 +68,82 @@ public class SecondaryStoreCacheTest {
@Test
public void basicTest() throws Exception{
- PathFilter pathFilter = new PathFilter(of("/a"), empty);
- SecondaryStoreObserver observer = new SecondaryStoreObserver(secondary, pathFilter, DEFAULT_DIFFER);
- primary.addObserver(observer);
-
- SecondaryStoreCache cache = new SecondaryStoreCache(secondary, pathFilter, DEFAULT_DIFFER);
+ SecondaryStoreCache cache = createCache(new PathFilter(of("/a"), empty));
NodeBuilder nb = primary.getRoot().builder();
create(nb, "/a/b", "/a/c", "/x/y/z");
- primary.merge(nb, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+ merge(nb);
RevisionVector rv1 = new RevisionVector(new Revision(1,0,1));
RevisionVector rv2 = new RevisionVector(new Revision(1,0,3));
- assertSame(DocumentNodeStateCache.UNKNOWN, cache.getDocumentNodeState("/a/b", rv1, rv2));
- assertSame(DocumentNodeStateCache.UNKNOWN, cache.getDocumentNodeState("/x", rv1, rv2));
+ assertNull(cache.getDocumentNodeState("/a/b", rv1, rv2));
+ assertNull(cache.getDocumentNodeState("/x", rv1, rv2));
}
@Test
public void updateAndReadAtReadRev() throws Exception{
- PathFilter pathFilter = new PathFilter(of("/a"), empty);
- SecondaryStoreObserver observer = new SecondaryStoreObserver(secondary, pathFilter, DEFAULT_DIFFER);
- primary.addObserver(observer);
-
- SecondaryStoreCache cache = new SecondaryStoreCache(secondary, pathFilter, DEFAULT_DIFFER);
+ SecondaryStoreCache cache = createCache(new PathFilter(of("/a"), empty));
NodeBuilder nb = primary.getRoot().builder();
create(nb, "/a/b", "/a/c", "/x/y/z");
- AbstractDocumentNodeState r1 =
- (AbstractDocumentNodeState) primary.merge(nb, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+ AbstractDocumentNodeState r1 = merge(nb);
//Update some other part of tree i.e. which does not change lastRev for /a/c
nb = primary.getRoot().builder();
create(nb, "/a/e/d");
- AbstractDocumentNodeState r2 =
- (AbstractDocumentNodeState)primary.merge(nb, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+ AbstractDocumentNodeState r2 = merge(nb);
//Lookup should work fine
- AbstractDocumentNodeState a_r2 = documentState(r2, "/a");
- NodeStateCacheEntry result
+ AbstractDocumentNodeState a_r2 = documentState(r2, "/a/c");
+ AbstractDocumentNodeState result
= cache.getDocumentNodeState("/a/c", r2.getRootRevision(), a_r2.getLastRevision());
- assertTrue(EqualsDiff.equals(a_r2.getChildNode("c"), result.getState()));
+ assertTrue(EqualsDiff.equals(a_r2, result));
nb = primary.getRoot().builder();
nb.child("a").child("c").remove();
- primary.merge(nb, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+ AbstractDocumentNodeState r3 = merge(nb);
//Now look from older revision
- result = cache.getDocumentNodeState("/a/c", r1.getRootRevision(), a_r2.getLastRevision());
+ result = cache.getDocumentNodeState("/a/c", r3.getRootRevision(), a_r2.getLastRevision());
//now as its not visible from head it would not be visible
- assertSame(DocumentNodeStateCache.UNKNOWN, result);
+ assertNull(result);
}
@Test
public void updateAndReadAtPrevRevision() throws Exception {
- PathFilter pathFilter = new PathFilter(of("/a"), empty);
- SecondaryStoreCache cache = new SecondaryStoreCache(secondary, pathFilter, DEFAULT_DIFFER);
- SecondaryStoreObserver observer = new SecondaryStoreObserver(secondary, pathFilter, cache,
- DEFAULT_DIFFER, StatisticsProvider.NOOP);
- primary.addObserver(observer);
+ SecondaryStoreCache cache = createCache(new PathFilter(of("/a"), empty));
NodeBuilder nb = primary.getRoot().builder();
create(nb, "/a/b", "/a/c");
- AbstractDocumentNodeState r0 =
- (AbstractDocumentNodeState) primary.merge(nb, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+ AbstractDocumentNodeState r0 = merge(nb);
AbstractDocumentNodeState a_c_0 = documentState(primary.getRoot(), "/a/c");
//Update some other part of tree i.e. which does not change lastRev for /a/c
nb = primary.getRoot().builder();
create(nb, "/a/c/d");
- AbstractDocumentNodeState r1 =
- (AbstractDocumentNodeState)primary.merge(nb, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+ AbstractDocumentNodeState r1 = merge(nb);
AbstractDocumentNodeState a_c_1 = documentState(primary.getRoot(), "/a/c");
- NodeStateCacheEntry result
+ AbstractDocumentNodeState result
= cache.getDocumentNodeState("/a/c", r1.getRootRevision(), a_c_1.getLastRevision());
- assertTrue(EqualsDiff.equals(a_c_1, result.getState()));
+ assertTrue(EqualsDiff.equals(a_c_1, result));
//Read from older revision
- result
- = cache.getDocumentNodeState("/a/c", r0.getRootRevision(), a_c_0.getLastRevision());
- assertTrue(EqualsDiff.equals(a_c_0, result.getState()));
+ result = cache.getDocumentNodeState("/a/c", r0.getRootRevision(), a_c_0.getLastRevision());
+ assertTrue(EqualsDiff.equals(a_c_0, result));
}
@Test
public void binarySearch() throws Exception{
- PathFilter pathFilter = new PathFilter(of("/a"), empty);
- SecondaryStoreCache cache = new SecondaryStoreCache(secondary, pathFilter, DEFAULT_DIFFER);
- SecondaryStoreObserver observer = new SecondaryStoreObserver(secondary, pathFilter, cache,
- DEFAULT_DIFFER, StatisticsProvider.NOOP);
- primary.addObserver(observer);
+ SecondaryStoreCache cache = createCache(new PathFilter(of("/a"), empty));
List<AbstractDocumentNodeState> roots = Lists.newArrayList();
List<RevisionVector> revs = Lists.newArrayList();
for (int i = 0; i < 50; i++) {
NodeBuilder nb = primary.getRoot().builder();
create(nb, "/a/b"+i);
- AbstractDocumentNodeState r =
- (AbstractDocumentNodeState) primary.merge(nb, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+ AbstractDocumentNodeState r = merge(nb);
roots.add(r);
revs.add(r.getRootRevision());
}
@@ -179,11 +159,23 @@ public class SecondaryStoreCacheTest {
NodeBuilder nb = primary.getRoot().builder();
create(nb, "/a/m");
- AbstractDocumentNodeState r =
- (AbstractDocumentNodeState) primary.merge(nb, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+ AbstractDocumentNodeState r = merge(nb);
AbstractDocumentNodeState result = SecondaryStoreCache.findMatchingRoot(rootsArr, r.getRootRevision());
assertNull(result);
}
+ private SecondaryStoreCache createCache(PathFilter pathFilter){
+ SecondaryStoreCache cache = new SecondaryStoreCache(secondary, pathFilter, DEFAULT_DIFFER);
+ SecondaryStoreObserver observer = new SecondaryStoreObserver(secondary, pathFilter, cache,
+ DEFAULT_DIFFER, StatisticsProvider.NOOP);
+ primary.addObserver(observer);
+
+ return cache;
+ }
+
+ private AbstractDocumentNodeState merge(NodeBuilder nb) throws CommitFailedException {
+ return (AbstractDocumentNodeState) primary.merge(nb, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+ }
+
}
\ No newline at end of file