diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/NoteMapTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/NoteMapTest.java index f22b02098..d740ffaf1 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/NoteMapTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/NoteMapTest.java @@ -47,6 +47,7 @@ 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 void testEditFanout2_38() throws Exception { .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"); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/notes/FanoutBucket.java b/org.eclipse.jgit/src/org/eclipse/jgit/notes/FanoutBucket.java index 5d35355ba..ae34938ee 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/notes/FanoutBucket.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/notes/FanoutBucket.java @@ -228,6 +228,24 @@ private int treeSize() { 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); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/notes/InMemoryNoteBucket.java b/org.eclipse.jgit/src/org/eclipse/jgit/notes/InMemoryNoteBucket.java index 4630f9b0f..0f45f8223 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/notes/InMemoryNoteBucket.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/notes/InMemoryNoteBucket.java @@ -68,4 +68,6 @@ abstract class InMemoryNoteBucket extends NoteBucket { InMemoryNoteBucket(int prefixLen) { this.prefixLen = prefixLen; } + + abstract InMemoryNoteBucket append(Note note); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/notes/LeafBucket.java b/org.eclipse.jgit/src/org/eclipse/jgit/notes/LeafBucket.java index 67bb3c8a0..068a4c24c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/notes/LeafBucket.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/notes/LeafBucket.java @@ -73,6 +73,8 @@ * 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 @@ InMemoryNoteBucket set(AnyObjectId noteOn, AnyObjectId noteData, } } 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 @@ void parseOneEntry(AnyObjectId noteOn, AnyObjectId noteData) { 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 @@ private void growIfFull() { 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; + } }