Browse Source

Allow writing a NoteMap back to the repository

This is necessary to allow applications to wrap the note tree in
a commit and update the note branch with the new state.

Change-Id: Idbd7ead4a1b16ae2b64a30a4a01a29cfed548cdf
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
stable-0.10
Shawn O. Pearce 14 years ago
parent
commit
3e2b9b691e
  1. 14
      org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
  2. 106
      org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/NoteMapTest.java
  3. 46
      org.eclipse.jgit/src/org/eclipse/jgit/notes/FanoutBucket.java
  4. 38
      org.eclipse.jgit/src/org/eclipse/jgit/notes/LeafBucket.java
  5. 29
      org.eclipse.jgit/src/org/eclipse/jgit/notes/NonNoteEntry.java
  6. 3
      org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteBucket.java
  7. 15
      org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMap.java

14
org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java

@ -183,6 +183,17 @@ public class TestRepository<R extends Repository> {
now += secDelta * 1000L; now += secDelta * 1000L;
} }
/**
* Set the author and committer using {@link #getClock()}.
*
* @param c
* the commit builder to store.
*/
public void setAuthorAndCommitter(org.eclipse.jgit.lib.CommitBuilder c) {
c.setAuthor(new PersonIdent(author, new Date(now)));
c.setCommitter(new PersonIdent(committer, new Date(now)));
}
/** /**
* Create a new blob object in the repository. * Create a new blob object in the repository.
* *
@ -815,8 +826,7 @@ public class TestRepository<R extends Repository> {
c = new org.eclipse.jgit.lib.CommitBuilder(); c = new org.eclipse.jgit.lib.CommitBuilder();
c.setParentIds(parents); c.setParentIds(parents);
c.setAuthor(new PersonIdent(author, new Date(now))); setAuthorAndCommitter(c);
c.setCommitter(new PersonIdent(committer, new Date(now)));
c.setMessage(message); c.setMessage(message);
ObjectId commitId; ObjectId commitId;

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

@ -43,7 +43,10 @@
package org.eclipse.jgit.notes; package org.eclipse.jgit.notes;
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.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;
@ -51,6 +54,8 @@ import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryTestCase; import org.eclipse.jgit.lib.RepositoryTestCase;
import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevBlob;
import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.util.RawParseUtils; import org.eclipse.jgit.util.RawParseUtils;
public class NoteMapTest extends RepositoryTestCase { public class NoteMapTest extends RepositoryTestCase {
@ -188,6 +193,59 @@ public class NoteMapTest extends RepositoryTestCase {
assertEquals(exp, RawParseUtils.decode(act)); assertEquals(exp, RawParseUtils.decode(act));
} }
public void testWriteUnchangedFlat() throws Exception {
RevBlob a = tr.blob("a");
RevBlob b = tr.blob("b");
RevBlob data1 = tr.blob("data1");
RevBlob data2 = tr.blob("data2");
RevCommit r = tr.commit() //
.add(a.name(), data1) //
.add(b.name(), data2) //
.add(".gitignore", "") //
.add("zoo-animals.txt", "") //
.create();
tr.parseBody(r);
NoteMap map = NoteMap.read(reader, r);
assertTrue("has note for a", map.contains(a));
assertTrue("has note for b", map.contains(b));
RevCommit n = commitNoteMap(map);
assertNotSame("is new commit", r, n);
assertSame("same tree", r.getTree(), n.getTree());
}
public void testWriteUnchangedFanout2_38() throws Exception {
RevBlob a = tr.blob("a");
RevBlob b = tr.blob("b");
RevBlob data1 = tr.blob("data1");
RevBlob data2 = tr.blob("data2");
RevCommit r = tr.commit() //
.add(fanout(2, a.name()), data1) //
.add(fanout(2, b.name()), data2) //
.add(".gitignore", "") //
.add("zoo-animals.txt", "") //
.create();
tr.parseBody(r);
NoteMap map = NoteMap.read(reader, r);
assertTrue("has note for a", map.contains(a));
assertTrue("has note for b", map.contains(b));
// This is a non-lazy map, so we'll be looking at the leaf buckets.
RevCommit n = commitNoteMap(map);
assertNotSame("is new commit", r, n);
assertSame("same tree", r.getTree(), n.getTree());
// Use a lazy-map for the next round of the same test.
map = NoteMap.read(reader, r);
n = commitNoteMap(map);
assertNotSame("is new commit", r, n);
assertSame("same tree", r.getTree(), n.getTree());
}
public void testCreateFromEmpty() throws Exception { public void testCreateFromEmpty() throws Exception {
RevBlob a = tr.blob("a"); RevBlob a = tr.blob("a");
RevBlob b = tr.blob("b"); RevBlob b = tr.blob("b");
@ -226,6 +284,8 @@ public class NoteMapTest extends RepositoryTestCase {
RevCommit r = tr.commit() // RevCommit r = tr.commit() //
.add(a.name(), data1) // .add(a.name(), data1) //
.add(b.name(), data2) // .add(b.name(), data2) //
.add(".gitignore", "") //
.add("zoo-animals.txt", b) //
.create(); .create();
tr.parseBody(r); tr.parseBody(r);
@ -250,6 +310,15 @@ public class NoteMapTest extends RepositoryTestCase {
id.setByte(1, p); id.setByte(1, p);
assertTrue("contains " + id, map.contains(id)); assertTrue("contains " + id, map.contains(id));
} }
RevCommit n = commitNoteMap(map);
map = NoteMap.read(reader, n);
assertEquals(data2, map.get(a));
assertEquals(b, map.get(data1));
assertFalse("no b", map.contains(b));
assertFalse("no data2", map.contains(data2));
assertEquals(b, TreeWalk
.forPath(reader, "zoo-animals.txt", n.getTree()).getObjectId(0));
} }
public void testEditFanout2_38() throws Exception { public void testEditFanout2_38() throws Exception {
@ -261,6 +330,8 @@ public class NoteMapTest extends RepositoryTestCase {
RevCommit r = tr.commit() // RevCommit r = tr.commit() //
.add(fanout(2, a.name()), data1) // .add(fanout(2, a.name()), data1) //
.add(fanout(2, b.name()), data2) // .add(fanout(2, b.name()), data2) //
.add(".gitignore", "") //
.add("zoo-animals.txt", b) //
.create(); .create();
tr.parseBody(r); tr.parseBody(r);
@ -274,11 +345,46 @@ public class NoteMapTest extends RepositoryTestCase {
assertEquals(b, map.get(data1)); assertEquals(b, map.get(data1));
assertFalse("no b", map.contains(b)); assertFalse("no b", map.contains(b));
assertFalse("no data2", map.contains(data2)); assertFalse("no data2", map.contains(data2));
RevCommit n = commitNoteMap(map);
map.set(a, null); map.set(a, null);
map.set(data1, null); map.set(data1, null);
assertFalse("no a", map.contains(a)); assertFalse("no a", map.contains(a));
assertFalse("no data1", map.contains(data1)); assertFalse("no data1", map.contains(data1));
map = NoteMap.read(reader, n);
assertEquals(data2, map.get(a));
assertEquals(b, map.get(data1));
assertFalse("no b", map.contains(b));
assertFalse("no data2", map.contains(data2));
assertEquals(b, TreeWalk
.forPath(reader, "zoo-animals.txt", n.getTree()).getObjectId(0));
}
public void testRemoveDeletesTreeFanout2_38() throws Exception {
RevBlob a = tr.blob("a");
RevBlob data1 = tr.blob("data1");
RevTree empty = tr.tree();
RevCommit r = tr.commit() //
.add(fanout(2, a.name()), data1) //
.create();
tr.parseBody(r);
NoteMap map = NoteMap.read(reader, r);
map.set(a, null);
RevCommit n = commitNoteMap(map);
assertEquals("empty tree", empty, n.getTree());
}
private RevCommit commitNoteMap(NoteMap map) throws IOException {
tr.tick(600);
CommitBuilder builder = new CommitBuilder();
builder.setTreeId(map.writeTree(inserter));
tr.setAuthorAndCommitter(builder);
return tr.getRevWalk().parseCommit(inserter.insert(builder));
} }
private static String fanout(int prefix, String name) { private static String fanout(int prefix, String name) {

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

@ -43,12 +43,16 @@
package org.eclipse.jgit.notes; package org.eclipse.jgit.notes;
import static org.eclipse.jgit.lib.FileMode.TREE;
import java.io.IOException; import java.io.IOException;
import org.eclipse.jgit.lib.AbbreviatedObjectId; import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.TreeFormatter;
/** /**
* A note tree holding only note subtrees, each named using a 2 digit hex name. * A note tree holding only note subtrees, each named using a 2 digit hex name.
@ -136,6 +140,43 @@ class FanoutBucket extends InMemoryNoteBucket {
} }
} }
private static final byte[] hexchar = { '0', '1', '2', '3', '4', '5', '6',
'7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
@Override
ObjectId writeTree(ObjectInserter inserter) throws IOException {
byte[] nameBuf = new byte[2];
TreeFormatter fmt = new TreeFormatter(treeSize());
NonNoteEntry e = nonNotes;
for (int cell = 0; cell < 256; cell++) {
NoteBucket b = table[cell];
if (b == null)
continue;
nameBuf[0] = hexchar[cell >>> 4];
nameBuf[1] = hexchar[cell & 0x0f];
while (e != null && e.pathCompare(nameBuf, 0, 2, TREE) < 0) {
e.format(fmt);
e = e.next;
}
fmt.append(nameBuf, 0, 2, TREE, b.writeTree(inserter));
}
for (; e != null; e = e.next)
e.format(fmt);
return fmt.insert(inserter);
}
private int treeSize() {
int sz = cnt * TreeFormatter.entrySize(TREE, 2);
for (NonNoteEntry e = nonNotes; e != null; e = e.next)
sz += e.treeEntrySize();
return sz;
}
private int cell(AnyObjectId id) { private int cell(AnyObjectId id) {
return id.getByte(prefixLen >> 1); return id.getByte(prefixLen >> 1);
} }
@ -158,6 +199,11 @@ class FanoutBucket extends InMemoryNoteBucket {
return load(noteOn, or).set(noteOn, noteData, or); return load(noteOn, or).set(noteOn, noteData, or);
} }
@Override
ObjectId writeTree(ObjectInserter inserter) {
return treeId;
}
private NoteBucket load(AnyObjectId objId, ObjectReader or) private NoteBucket load(AnyObjectId objId, ObjectReader or)
throws IOException { throws IOException {
AbbreviatedObjectId p = objId.abbreviate(prefixLen + 2); AbbreviatedObjectId p = objId.abbreviate(prefixLen + 2);

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

@ -43,11 +43,16 @@
package org.eclipse.jgit.notes; package org.eclipse.jgit.notes;
import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
import static org.eclipse.jgit.lib.FileMode.REGULAR_FILE;
import java.io.IOException; import java.io.IOException;
import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.TreeFormatter;
/** /**
* A note tree holding only notes, with no subtrees. * A note tree holding only notes, with no subtrees.
@ -126,6 +131,39 @@ class LeafBucket extends InMemoryNoteBucket {
} }
} }
@Override
ObjectId writeTree(ObjectInserter inserter) throws IOException {
byte[] nameBuf = new byte[OBJECT_ID_STRING_LENGTH];
int nameLen = OBJECT_ID_STRING_LENGTH - prefixLen;
TreeFormatter fmt = new TreeFormatter(treeSize(nameLen));
NonNoteEntry e = nonNotes;
for (int i = 0; i < cnt; i++) {
Note n = notes[i];
n.copyTo(nameBuf, 0);
while (e != null
&& e.pathCompare(nameBuf, prefixLen, nameLen, REGULAR_FILE) < 0) {
e.format(fmt);
e = e.next;
}
fmt.append(nameBuf, prefixLen, nameLen, REGULAR_FILE, n.getData());
}
for (; e != null; e = e.next)
e.format(fmt);
return fmt.insert(inserter);
}
private int treeSize(final int nameLen) {
int sz = cnt * TreeFormatter.entrySize(REGULAR_FILE, nameLen);
for (NonNoteEntry e = nonNotes; e != null; e = e.next)
sz += e.treeEntrySize();
return sz;
}
void parseOneEntry(AnyObjectId noteOn, AnyObjectId noteData) { void parseOneEntry(AnyObjectId noteOn, AnyObjectId noteData) {
growIfFull(); growIfFull();
notes[cnt++] = new Note(noteOn, noteData.copy()); notes[cnt++] = new Note(noteOn, noteData.copy());

29
org.eclipse.jgit/src/org/eclipse/jgit/notes/NonNoteEntry.java

@ -68,4 +68,33 @@ class NonNoteEntry extends ObjectId {
void format(TreeFormatter fmt) { void format(TreeFormatter fmt) {
fmt.append(name, mode, this); fmt.append(name, mode, this);
} }
int treeEntrySize() {
return TreeFormatter.entrySize(mode, name.length);
}
int pathCompare(byte[] bBuf, int bPos, int bLen, FileMode bMode) {
return pathCompare(name, 0, name.length, mode, //
bBuf, bPos, bLen, bMode);
}
private static int pathCompare(final byte[] aBuf, int aPos, final int aEnd,
final FileMode aMode, final byte[] bBuf, int bPos, final int bEnd,
final FileMode bMode) {
while (aPos < aEnd && bPos < bEnd) {
int cmp = (aBuf[aPos++] & 0xff) - (bBuf[bPos++] & 0xff);
if (cmp != 0)
return cmp;
}
if (aPos < aEnd)
return (aBuf[aPos] & 0xff) - lastPathChar(bMode);
if (bPos < bEnd)
return lastPathChar(aMode) - (bBuf[bPos] & 0xff);
return 0;
}
private static int lastPathChar(final FileMode mode) {
return FileMode.TREE.equals(mode.getBits()) ? '/' : '\0';
}
} }

3
org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteBucket.java

@ -47,6 +47,7 @@ import java.io.IOException;
import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ObjectReader;
/** /**
@ -61,4 +62,6 @@ abstract class NoteBucket {
abstract InMemoryNoteBucket set(AnyObjectId noteOn, AnyObjectId noteData, abstract InMemoryNoteBucket set(AnyObjectId noteOn, AnyObjectId noteData,
ObjectReader reader) throws IOException; ObjectReader reader) throws IOException;
abstract ObjectId writeTree(ObjectInserter inserter) throws IOException;
} }

15
org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMap.java

@ -309,6 +309,21 @@ public class NoteMap {
set(noteOn, null); set(noteOn, null);
} }
/**
* Write this note map as a tree.
*
* @param inserter
* inserter to use when writing trees to the object database.
* Caller is responsible for flushing the inserter before trying
* to read the objects, or exposing them through a reference.
* @return the top level tree.
* @throws IOException
* a tree could not be written.
*/
public ObjectId writeTree(ObjectInserter inserter) throws IOException {
return root.writeTree(inserter);
}
private void load(ObjectId rootTree) throws MissingObjectException, private void load(ObjectId rootTree) throws MissingObjectException,
IncorrectObjectTypeException, CorruptObjectException, IOException { IncorrectObjectTypeException, CorruptObjectException, IOException {
AbbreviatedObjectId none = AbbreviatedObjectId.fromString(""); AbbreviatedObjectId none = AbbreviatedObjectId.fromString("");

Loading…
Cancel
Save