]> source.dussan.org Git - jgit.git/commitdiff
Split note leaf buckets at 256 elements 54/1854/4
authorShawn O. Pearce <spearce@spearce.org>
Fri, 5 Nov 2010 01:56:40 +0000 (18:56 -0700)
committerShawn O. Pearce <spearce@spearce.org>
Fri, 12 Nov 2010 22:01:28 +0000 (14:01 -0800)
Leaf level notes trees are split into a new fan-out tree if an
insertion occurs and the tree already contains >= 256 notes in it.

The splitting may occur multiple times if all of the notes have the
same prefix; in the worst case this produces a tree path such as
"00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/be" if all
of the notes begin with zeros.

Change-Id: I2d7d98f35108def9ec49936ddbdc34b13822a3c7
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/NoteMapTest.java
org.eclipse.jgit/src/org/eclipse/jgit/notes/FanoutBucket.java
org.eclipse.jgit/src/org/eclipse/jgit/notes/InMemoryNoteBucket.java
org.eclipse.jgit/src/org/eclipse/jgit/notes/LeafBucket.java

index f22b02098929173bf409aa34009956b216a2645d..d740ffaf15692ea6cf595fce4a440aacaaa75283 100644 (file)
@@ -47,6 +47,7 @@ import java.io.IOException;
 
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.MutableObjectId;
 import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.ObjectReader;
@@ -361,6 +362,46 @@ public class NoteMapTest extends RepositoryTestCase {
                                .forPath(reader, "zoo-animals.txt", n.getTree()).getObjectId(0));
        }
 
+       public void testLeafSplitsWhenFull() throws Exception {
+               RevBlob data1 = tr.blob("data1");
+               MutableObjectId idBuf = new MutableObjectId();
+
+               RevCommit r = tr.commit() //
+                               .add(data1.name(), data1) //
+                               .create();
+               tr.parseBody(r);
+
+               NoteMap map = NoteMap.read(reader, r);
+               for (int i = 0; i < 254; i++) {
+                       idBuf.setByte(Constants.OBJECT_ID_LENGTH - 1, i);
+                       map.set(idBuf, data1);
+               }
+
+               RevCommit n = commitNoteMap(map);
+               TreeWalk tw = new TreeWalk(reader);
+               tw.reset(n.getTree());
+               while (tw.next())
+                       assertFalse("no fan-out subtree", tw.isSubtree());
+
+               for (int i = 254; i < 256; i++) {
+                       idBuf.setByte(Constants.OBJECT_ID_LENGTH - 1, i);
+                       map.set(idBuf, data1);
+               }
+               idBuf.setByte(Constants.OBJECT_ID_LENGTH - 2, 1);
+               map.set(idBuf, data1);
+               n = commitNoteMap(map);
+
+               // The 00 bucket is fully split.
+               String path = fanout(38, idBuf.name());
+               tw = TreeWalk.forPath(reader, path, n.getTree());
+               assertNotNull("has " + path, tw);
+
+               // The other bucket is not.
+               path = fanout(2, data1.name());
+               tw = TreeWalk.forPath(reader, path, n.getTree());
+               assertNotNull("has " + path, tw);
+       }
+
        public void testRemoveDeletesTreeFanout2_38() throws Exception {
                RevBlob a = tr.blob("a");
                RevBlob data1 = tr.blob("data1");
index 5d35355ba9e702e3645e55796b3b15c8fb4630f5..ae34938eecffbf375e1835d91662b9a02a5c730f 100644 (file)
@@ -228,6 +228,24 @@ class FanoutBucket extends InMemoryNoteBucket {
                return sz;
        }
 
+       @Override
+       InMemoryNoteBucket append(Note note) {
+               int cell = cell(note);
+               InMemoryNoteBucket b = (InMemoryNoteBucket) table[cell];
+
+               if (b == null) {
+                       LeafBucket n = new LeafBucket(prefixLen + 2);
+                       table[cell] = n.append(note);
+                       cnt++;
+
+               } else {
+                       InMemoryNoteBucket n = b.append(note);
+                       if (n != b)
+                               table[cell] = n;
+               }
+               return this;
+       }
+
        private int cell(AnyObjectId id) {
                return id.getByte(prefixLen >> 1);
        }
index 4630f9b0fd4833503d8723c68f76da0ab7fb3570..0f45f822306f3b2f8666d12f4c2842118be045cc 100644 (file)
@@ -68,4 +68,6 @@ abstract class InMemoryNoteBucket extends NoteBucket {
        InMemoryNoteBucket(int prefixLen) {
                this.prefixLen = prefixLen;
        }
+
+       abstract InMemoryNoteBucket append(Note note);
 }
index 67bb3c8a046227c841bf750f7cee7028e4a44232..068a4c24c87e2ce52bcd42ba787479c8e210dad2 100644 (file)
@@ -73,6 +73,8 @@ import org.eclipse.jgit.lib.TreeFormatter;
  * A LeafBucket must be parsed from a tree object by {@link NoteParser}.
  */
 class LeafBucket extends InMemoryNoteBucket {
+       static final int MAX_SIZE = 256;
+
        /** All note blobs in this bucket, sorted sequentially. */
        private Note[] notes;
 
@@ -142,13 +144,18 @@ class LeafBucket extends InMemoryNoteBucket {
                        }
 
                } else if (noteData != null) {
-                       growIfFull();
-                       p = -(p + 1);
-                       if (p < cnt)
-                               System.arraycopy(notes, p, notes, p + 1, cnt - p);
-                       notes[p] = new Note(noteOn, noteData.copy());
-                       cnt++;
-                       return this;
+                       if (shouldSplit()) {
+                               return split().set(noteOn, noteData, or);
+
+                       } else {
+                               growIfFull();
+                               p = -(p + 1);
+                               if (p < cnt)
+                                       System.arraycopy(notes, p, notes, p + 1, cnt - p);
+                               notes[p] = new Note(noteOn, noteData.copy());
+                               cnt++;
+                               return this;
+                       }
 
                } else {
                        return this;
@@ -193,6 +200,18 @@ class LeafBucket extends InMemoryNoteBucket {
                notes[cnt++] = new Note(noteOn, noteData.copy());
        }
 
+       @Override
+       InMemoryNoteBucket append(Note note) {
+               if (shouldSplit()) {
+                       return split().append(note);
+
+               } else {
+                       growIfFull();
+                       notes[cnt++] = note;
+                       return this;
+               }
+       }
+
        private void growIfFull() {
                if (notes.length == cnt) {
                        Note[] n = new Note[notes.length * 2];
@@ -200,4 +219,16 @@ class LeafBucket extends InMemoryNoteBucket {
                        notes = n;
                }
        }
+
+       private boolean shouldSplit() {
+               return MAX_SIZE <= cnt && prefixLen + 2 < OBJECT_ID_STRING_LENGTH;
+       }
+
+       private InMemoryNoteBucket split() {
+               FanoutBucket n = new FanoutBucket(prefixLen);
+               for (int i = 0; i < cnt; i++)
+                       n.append(notes[i]);
+               n.nonNotes = nonNotes;
+               return n;
+       }
 }