Browse Source

Split note leaf buckets at 256 elements

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>
stable-0.10
Shawn O. Pearce 14 years ago
parent
commit
2b0df15f7f
  1. 41
      org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/NoteMapTest.java
  2. 18
      org.eclipse.jgit/src/org/eclipse/jgit/notes/FanoutBucket.java
  3. 2
      org.eclipse.jgit/src/org/eclipse/jgit/notes/InMemoryNoteBucket.java
  4. 45
      org.eclipse.jgit/src/org/eclipse/jgit/notes/LeafBucket.java

41
org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/NoteMapTest.java

@ -47,6 +47,7 @@ import java.io.IOException;
import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.MutableObjectId;
import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ObjectReader;
@ -361,6 +362,46 @@ public class NoteMapTest extends RepositoryTestCase {
.forPath(reader, "zoo-animals.txt", n.getTree()).getObjectId(0)); .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 { public void testRemoveDeletesTreeFanout2_38() throws Exception {
RevBlob a = tr.blob("a"); RevBlob a = tr.blob("a");
RevBlob data1 = tr.blob("data1"); RevBlob data1 = tr.blob("data1");

18
org.eclipse.jgit/src/org/eclipse/jgit/notes/FanoutBucket.java

@ -228,6 +228,24 @@ class FanoutBucket extends InMemoryNoteBucket {
return sz; 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) { private int cell(AnyObjectId id) {
return id.getByte(prefixLen >> 1); return id.getByte(prefixLen >> 1);
} }

2
org.eclipse.jgit/src/org/eclipse/jgit/notes/InMemoryNoteBucket.java

@ -68,4 +68,6 @@ abstract class InMemoryNoteBucket extends NoteBucket {
InMemoryNoteBucket(int prefixLen) { InMemoryNoteBucket(int prefixLen) {
this.prefixLen = prefixLen; this.prefixLen = prefixLen;
} }
abstract InMemoryNoteBucket append(Note note);
} }

45
org.eclipse.jgit/src/org/eclipse/jgit/notes/LeafBucket.java

@ -73,6 +73,8 @@ import org.eclipse.jgit.lib.TreeFormatter;
* A LeafBucket must be parsed from a tree object by {@link NoteParser}. * A LeafBucket must be parsed from a tree object by {@link NoteParser}.
*/ */
class LeafBucket extends InMemoryNoteBucket { class LeafBucket extends InMemoryNoteBucket {
static final int MAX_SIZE = 256;
/** All note blobs in this bucket, sorted sequentially. */ /** All note blobs in this bucket, sorted sequentially. */
private Note[] notes; private Note[] notes;
@ -142,13 +144,18 @@ class LeafBucket extends InMemoryNoteBucket {
} }
} else if (noteData != null) { } else if (noteData != null) {
growIfFull(); if (shouldSplit()) {
p = -(p + 1); return split().set(noteOn, noteData, or);
if (p < cnt)
System.arraycopy(notes, p, notes, p + 1, cnt - p); } else {
notes[p] = new Note(noteOn, noteData.copy()); growIfFull();
cnt++; p = -(p + 1);
return this; if (p < cnt)
System.arraycopy(notes, p, notes, p + 1, cnt - p);
notes[p] = new Note(noteOn, noteData.copy());
cnt++;
return this;
}
} else { } else {
return this; return this;
@ -193,6 +200,18 @@ class LeafBucket extends InMemoryNoteBucket {
notes[cnt++] = new Note(noteOn, noteData.copy()); 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() { private void growIfFull() {
if (notes.length == cnt) { if (notes.length == cnt) {
Note[] n = new Note[notes.length * 2]; Note[] n = new Note[notes.length * 2];
@ -200,4 +219,16 @@ class LeafBucket extends InMemoryNoteBucket {
notes = n; 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;
}
} }

Loading…
Cancel
Save