summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShawn O. Pearce <spearce@spearce.org>2010-11-04 18:14:00 -0700
committerShawn O. Pearce <spearce@spearce.org>2010-11-12 14:01:28 -0800
commit3e2b9b691ed30d53750cc08c2b89577b78e6d19a (patch)
treee66a5e0b60a6a723066a6855d1a4618333fe6862
parentfaa0747cce9826137b3841661a5b745030fb55fe (diff)
downloadjgit-3e2b9b691ed30d53750cc08c2b89577b78e6d19a.tar.gz
jgit-3e2b9b691ed30d53750cc08c2b89577b78e6d19a.zip
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>
-rw-r--r--org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java14
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/NoteMapTest.java106
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/notes/FanoutBucket.java46
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/notes/LeafBucket.java38
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/notes/NonNoteEntry.java29
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteBucket.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMap.java15
7 files changed, 249 insertions, 2 deletions
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
index 7a95ccd368..e74af8cf71 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
@@ -184,6 +184,17 @@ public class TestRepository<R extends Repository> {
}
/**
+ * 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.
*
* @param content
@@ -815,8 +826,7 @@ public class TestRepository<R extends Repository> {
c = new org.eclipse.jgit.lib.CommitBuilder();
c.setParentIds(parents);
- c.setAuthor(new PersonIdent(author, new Date(now)));
- c.setCommitter(new PersonIdent(committer, new Date(now)));
+ setAuthorAndCommitter(c);
c.setMessage(message);
ObjectId commitId;
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 e1a6d9bf86..f22b020989 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
@@ -43,7 +43,10 @@
package org.eclipse.jgit.notes;
+import java.io.IOException;
+
import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.MutableObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
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.revwalk.RevBlob;
import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.util.RawParseUtils;
public class NoteMapTest extends RepositoryTestCase {
@@ -188,6 +193,59 @@ public class NoteMapTest extends RepositoryTestCase {
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 {
RevBlob a = tr.blob("a");
RevBlob b = tr.blob("b");
@@ -226,6 +284,8 @@ public class NoteMapTest extends RepositoryTestCase {
RevCommit r = tr.commit() //
.add(a.name(), data1) //
.add(b.name(), data2) //
+ .add(".gitignore", "") //
+ .add("zoo-animals.txt", b) //
.create();
tr.parseBody(r);
@@ -250,6 +310,15 @@ public class NoteMapTest extends RepositoryTestCase {
id.setByte(1, p);
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 {
@@ -261,6 +330,8 @@ public class NoteMapTest extends RepositoryTestCase {
RevCommit r = tr.commit() //
.add(fanout(2, a.name()), data1) //
.add(fanout(2, b.name()), data2) //
+ .add(".gitignore", "") //
+ .add("zoo-animals.txt", b) //
.create();
tr.parseBody(r);
@@ -274,11 +345,46 @@ public class NoteMapTest extends RepositoryTestCase {
assertEquals(b, map.get(data1));
assertFalse("no b", map.contains(b));
assertFalse("no data2", map.contains(data2));
+ RevCommit n = commitNoteMap(map);
map.set(a, null);
map.set(data1, null);
assertFalse("no a", map.contains(a));
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) {
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 760528618c..2085676900 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/notes/FanoutBucket.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/notes/FanoutBucket.java
@@ -43,12 +43,16 @@
package org.eclipse.jgit.notes;
+import static org.eclipse.jgit.lib.FileMode.TREE;
+
import java.io.IOException;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
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.
@@ -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) {
return id.getByte(prefixLen >> 1);
}
@@ -158,6 +199,11 @@ class FanoutBucket extends InMemoryNoteBucket {
return load(noteOn, or).set(noteOn, noteData, or);
}
+ @Override
+ ObjectId writeTree(ObjectInserter inserter) {
+ return treeId;
+ }
+
private NoteBucket load(AnyObjectId objId, ObjectReader or)
throws IOException {
AbbreviatedObjectId p = objId.abbreviate(prefixLen + 2);
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 ce4feae259..40be45f433 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/notes/LeafBucket.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/notes/LeafBucket.java
@@ -43,11 +43,16 @@
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 org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.TreeFormatter;
/**
* 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) {
growIfFull();
notes[cnt++] = new Note(noteOn, noteData.copy());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/notes/NonNoteEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/notes/NonNoteEntry.java
index 20fd3a88ad..6a2d44bca9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/notes/NonNoteEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/notes/NonNoteEntry.java
@@ -68,4 +68,33 @@ class NonNoteEntry extends ObjectId {
void format(TreeFormatter fmt) {
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';
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteBucket.java b/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteBucket.java
index f75fc1f942..f5465148a8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteBucket.java
+++ b/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.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectReader;
/**
@@ -61,4 +62,6 @@ abstract class NoteBucket {
abstract InMemoryNoteBucket set(AnyObjectId noteOn, AnyObjectId noteData,
ObjectReader reader) throws IOException;
+
+ abstract ObjectId writeTree(ObjectInserter inserter) throws IOException;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMap.java b/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMap.java
index 88ab481794..6a7b5cffbe 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMap.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMap.java
@@ -309,6 +309,21 @@ public class NoteMap {
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,
IncorrectObjectTypeException, CorruptObjectException, IOException {
AbbreviatedObjectId none = AbbreviatedObjectId.fromString("");