]> source.dussan.org Git - jgit.git/commitdiff
Allow writing a NoteMap back to the repository 52/1852/5
authorShawn O. Pearce <spearce@spearce.org>
Fri, 5 Nov 2010 01:14:00 +0000 (18:14 -0700)
committerShawn O. Pearce <spearce@spearce.org>
Fri, 12 Nov 2010 22:01:28 +0000 (14:01 -0800)
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>
org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
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/LeafBucket.java
org.eclipse.jgit/src/org/eclipse/jgit/notes/NonNoteEntry.java
org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteBucket.java
org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMap.java

index 7a95ccd3681890a84d43de218de90c2b7a38cda6..e74af8cf71a4a4a4f14f0173a7d79b2eabb39f5a 100644 (file)
@@ -183,6 +183,17 @@ public class TestRepository<R extends Repository> {
                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.
         *
@@ -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;
index e1a6d9bf862d195c11d5736482e828f4bcaa18cd..f22b02098929173bf409aa34009956b216a2645d 100644 (file)
 
 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) {
index 760528618cab29522c28a25023ff9b2b7418571a..20856769000af258f9551015b0aa2c9e7668c4a0 100644 (file)
 
 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);
index ce4feae2597d8b3d4ae130c86845ae87fb17970a..40be45f433f62e65ccbbcee5c43702507a67c5ec 100644 (file)
 
 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());
index 20fd3a88ad2b23823f53c53eec7a5bdb9cd5292f..6a2d44bca9f0953c9c3e38a4a2233c2f3baf4f84 100644 (file)
@@ -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';
+       }
 }
index f75fc1f942def877a6633182149e9173f423efec..f5465148a8814dec87ee5813ec13a4c9c30102c7 100644 (file)
@@ -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;
 }
index 88ab48179435abedcaac7a1b4299469b37e21885..6a7b5cffbea6c0b1f97374739dd9539ad703d458 100644 (file)
@@ -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("");