--- /dev/null
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.notes;
+
+import java.io.IOException;
+
+import junit.framework.TestCase;
+
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.MutableObjectId;
+
+public class LeafBucketTest extends TestCase {
+ public void testEmpty() {
+ LeafBucket b = new LeafBucket(0);
+ assertNull(b.get(id(0x00), null));
+ assertNull(b.get(id(0x01), null));
+ assertNull(b.get(id(0xfe), null));
+ }
+
+ public void testParseFive() {
+ LeafBucket b = new LeafBucket(0);
+
+ b.parseOneEntry(id(0x11), id(0x81));
+ b.parseOneEntry(id(0x22), id(0x82));
+ b.parseOneEntry(id(0x33), id(0x83));
+ b.parseOneEntry(id(0x44), id(0x84));
+ b.parseOneEntry(id(0x55), id(0x85));
+
+ assertNull(b.get(id(0x01), null));
+ assertEquals(id(0x81), b.get(id(0x11), null));
+ assertEquals(id(0x82), b.get(id(0x22), null));
+ assertEquals(id(0x83), b.get(id(0x33), null));
+ assertEquals(id(0x84), b.get(id(0x44), null));
+ assertEquals(id(0x85), b.get(id(0x55), null));
+ assertNull(b.get(id(0x66), null));
+ }
+
+ public void testSetFive_InOrder() throws IOException {
+ LeafBucket b = new LeafBucket(0);
+
+ assertSame(b, b.set(id(0x11), id(0x81), null));
+ assertSame(b, b.set(id(0x22), id(0x82), null));
+ assertSame(b, b.set(id(0x33), id(0x83), null));
+ assertSame(b, b.set(id(0x44), id(0x84), null));
+ assertSame(b, b.set(id(0x55), id(0x85), null));
+
+ assertNull(b.get(id(0x01), null));
+ assertEquals(id(0x81), b.get(id(0x11), null));
+ assertEquals(id(0x82), b.get(id(0x22), null));
+ assertEquals(id(0x83), b.get(id(0x33), null));
+ assertEquals(id(0x84), b.get(id(0x44), null));
+ assertEquals(id(0x85), b.get(id(0x55), null));
+ assertNull(b.get(id(0x66), null));
+ }
+
+ public void testSetFive_ReverseOrder() throws IOException {
+ LeafBucket b = new LeafBucket(0);
+
+ assertSame(b, b.set(id(0x55), id(0x85), null));
+ assertSame(b, b.set(id(0x44), id(0x84), null));
+ assertSame(b, b.set(id(0x33), id(0x83), null));
+ assertSame(b, b.set(id(0x22), id(0x82), null));
+ assertSame(b, b.set(id(0x11), id(0x81), null));
+
+ assertNull(b.get(id(0x01), null));
+ assertEquals(id(0x81), b.get(id(0x11), null));
+ assertEquals(id(0x82), b.get(id(0x22), null));
+ assertEquals(id(0x83), b.get(id(0x33), null));
+ assertEquals(id(0x84), b.get(id(0x44), null));
+ assertEquals(id(0x85), b.get(id(0x55), null));
+ assertNull(b.get(id(0x66), null));
+ }
+
+ public void testSetFive_MixedOrder() throws IOException {
+ LeafBucket b = new LeafBucket(0);
+
+ assertSame(b, b.set(id(0x11), id(0x81), null));
+ assertSame(b, b.set(id(0x33), id(0x83), null));
+ assertSame(b, b.set(id(0x55), id(0x85), null));
+
+ assertSame(b, b.set(id(0x22), id(0x82), null));
+ assertSame(b, b.set(id(0x44), id(0x84), null));
+
+ assertNull(b.get(id(0x01), null));
+ assertEquals(id(0x81), b.get(id(0x11), null));
+ assertEquals(id(0x82), b.get(id(0x22), null));
+ assertEquals(id(0x83), b.get(id(0x33), null));
+ assertEquals(id(0x84), b.get(id(0x44), null));
+ assertEquals(id(0x85), b.get(id(0x55), null));
+ assertNull(b.get(id(0x66), null));
+ }
+
+ public void testSet_Replace() throws IOException {
+ LeafBucket b = new LeafBucket(0);
+
+ assertSame(b, b.set(id(0x11), id(0x81), null));
+ assertEquals(id(0x81), b.get(id(0x11), null));
+
+ assertSame(b, b.set(id(0x11), id(0x01), null));
+ assertEquals(id(0x01), b.get(id(0x11), null));
+ }
+
+ public void testRemoveMissingNote() throws IOException {
+ LeafBucket b = new LeafBucket(0);
+ assertNull(b.get(id(0x11), null));
+ assertSame(b, b.set(id(0x11), null, null));
+ assertNull(b.get(id(0x11), null));
+ }
+
+ public void testRemoveFirst() throws IOException {
+ LeafBucket b = new LeafBucket(0);
+
+ assertSame(b, b.set(id(0x11), id(0x81), null));
+ assertSame(b, b.set(id(0x22), id(0x82), null));
+ assertSame(b, b.set(id(0x33), id(0x83), null));
+ assertSame(b, b.set(id(0x44), id(0x84), null));
+ assertSame(b, b.set(id(0x55), id(0x85), null));
+
+ assertSame(b, b.set(id(0x11), null, null));
+
+ assertNull(b.get(id(0x01), null));
+ assertNull(b.get(id(0x11), null));
+ assertEquals(id(0x82), b.get(id(0x22), null));
+ assertEquals(id(0x83), b.get(id(0x33), null));
+ assertEquals(id(0x84), b.get(id(0x44), null));
+ assertEquals(id(0x85), b.get(id(0x55), null));
+ assertNull(b.get(id(0x66), null));
+ }
+
+ public void testRemoveMiddle() throws IOException {
+ LeafBucket b = new LeafBucket(0);
+
+ assertSame(b, b.set(id(0x11), id(0x81), null));
+ assertSame(b, b.set(id(0x22), id(0x82), null));
+ assertSame(b, b.set(id(0x33), id(0x83), null));
+ assertSame(b, b.set(id(0x44), id(0x84), null));
+ assertSame(b, b.set(id(0x55), id(0x85), null));
+
+ assertSame(b, b.set(id(0x33), null, null));
+
+ assertNull(b.get(id(0x01), null));
+ assertEquals(id(0x81), b.get(id(0x11), null));
+ assertEquals(id(0x82), b.get(id(0x22), null));
+ assertNull(b.get(id(0x33), null));
+ assertEquals(id(0x84), b.get(id(0x44), null));
+ assertEquals(id(0x85), b.get(id(0x55), null));
+ assertNull(b.get(id(0x66), null));
+ }
+
+ public void testRemoveLast() throws IOException {
+ LeafBucket b = new LeafBucket(0);
+
+ assertSame(b, b.set(id(0x11), id(0x81), null));
+ assertSame(b, b.set(id(0x22), id(0x82), null));
+ assertSame(b, b.set(id(0x33), id(0x83), null));
+ assertSame(b, b.set(id(0x44), id(0x84), null));
+ assertSame(b, b.set(id(0x55), id(0x85), null));
+
+ assertSame(b, b.set(id(0x55), null, null));
+
+ assertNull(b.get(id(0x01), null));
+ assertEquals(id(0x81), b.get(id(0x11), null));
+ assertEquals(id(0x82), b.get(id(0x22), null));
+ assertEquals(id(0x83), b.get(id(0x33), null));
+ assertEquals(id(0x84), b.get(id(0x44), null));
+ assertNull(b.get(id(0x55), null));
+ assertNull(b.get(id(0x66), null));
+ }
+
+ public void testRemoveMakesEmpty() throws IOException {
+ LeafBucket b = new LeafBucket(0);
+
+ assertSame(b, b.set(id(0x11), id(0x81), null));
+ assertEquals(id(0x81), b.get(id(0x11), null));
+
+ assertNull(b.set(id(0x11), null, null));
+ assertNull(b.get(id(0x11), null));
+ }
+
+ private static AnyObjectId id(int first) {
+ MutableObjectId id = new MutableObjectId();
+ id.setByte(1, first);
+ return id;
+ }
+}
package org.eclipse.jgit.notes;
import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.MutableObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryTestCase;
private ObjectReader reader;
+ private ObjectInserter inserter;
+
@Override
protected void setUp() throws Exception {
super.setUp();
tr = new TestRepository<Repository>(db);
reader = db.newObjectReader();
+ inserter = db.newObjectInserter();
}
@Override
protected void tearDown() throws Exception {
reader.release();
+ inserter.release();
super.tearDown();
}
assertEquals(exp, RawParseUtils.decode(act));
}
+ public void testCreateFromEmpty() throws Exception {
+ RevBlob a = tr.blob("a");
+ RevBlob b = tr.blob("b");
+ RevBlob data1 = tr.blob("data1");
+ RevBlob data2 = tr.blob("data2");
+
+ NoteMap map = NoteMap.newEmptyMap();
+ assertFalse("no a", map.contains(a));
+ assertFalse("no b", map.contains(b));
+
+ map.set(a, data1);
+ map.set(b, data2);
+
+ assertEquals(data1, map.get(a));
+ assertEquals(data2, map.get(b));
+
+ map.remove(a);
+ map.remove(b);
+
+ assertFalse("no a", map.contains(a));
+ assertFalse("no b", map.contains(b));
+
+ map.set(a, "data1", inserter);
+ assertEquals(data1, map.get(a));
+
+ map.set(a, null, inserter);
+ assertFalse("no a", map.contains(a));
+ }
+
+ public void testEditFlat() 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) //
+ .create();
+ tr.parseBody(r);
+
+ NoteMap map = NoteMap.read(reader, r);
+ map.set(a, data2);
+ map.set(b, null);
+ map.set(data1, b);
+ map.set(data2, null);
+
+ assertEquals(data2, map.get(a));
+ assertEquals(b, map.get(data1));
+ assertFalse("no b", map.contains(b));
+ assertFalse("no data2", map.contains(data2));
+
+ MutableObjectId id = new MutableObjectId();
+ for (int p = 42; p > 0; p--) {
+ id.setByte(1, p);
+ map.set(id, data1);
+ }
+
+ for (int p = 42; p > 0; p--) {
+ id.setByte(1, p);
+ assertTrue("contains " + id, map.contains(id));
+ }
+ }
+
+ public void testEditFanout2_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) //
+ .create();
+ tr.parseBody(r);
+
+ NoteMap map = NoteMap.read(reader, r);
+ map.set(a, data2);
+ map.set(b, null);
+ map.set(data1, b);
+ map.set(data2, null);
+
+ assertEquals(data2, map.get(a));
+ assertEquals(b, map.get(data1));
+ assertFalse("no b", map.contains(b));
+ assertFalse("no data2", map.contains(data2));
+
+ map.set(a, null);
+ map.set(data1, null);
+ assertFalse("no a", map.contains(a));
+ assertFalse("no data1", map.contains(data1));
+ }
+
private static String fanout(int prefix, String name) {
StringBuilder r = new StringBuilder();
int i = 0;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
* shared {@code ObjectReader} at the proper times.
*/
public class NoteMap {
+ /**
+ * Construct a new empty note map.
+ *
+ * @return an empty note map.
+ */
+ public static NoteMap newEmptyMap() {
+ NoteMap r = new NoteMap(null /* no reader */);
+ r.root = new LeafBucket(0);
+ return r;
+ }
+
/**
* Load a collection of notes from a branch.
*
return null;
}
+ /**
+ * Attach (or remove) a note on an object.
+ *
+ * If no note exists, a new note is stored. If a note already exists for the
+ * given object, it is replaced (or removed).
+ *
+ * This method only updates the map in memory.
+ *
+ * If the caller wants to attach a UTF-8 encoded string message to an
+ * object, {@link #set(AnyObjectId, String, ObjectInserter)} is a convenient
+ * way to encode and update a note in one step.
+ *
+ * @param noteOn
+ * the object to attach the note to. This same ObjectId can later
+ * be used as an argument to {@link #get(AnyObjectId)} or
+ * {@link #getCachedBytes(AnyObjectId, int)} to read back the
+ * {@code noteData}.
+ * @param noteData
+ * data to associate with the note. This must be the ObjectId of
+ * a blob that already exists in the repository. If null the note
+ * will be deleted, if present.
+ * @throws IOException
+ * a portion of the note space is not accessible.
+ */
+ public void set(AnyObjectId noteOn, ObjectId noteData) throws IOException {
+ InMemoryNoteBucket newRoot = root.set(noteOn, noteData, reader);
+ if (newRoot == null) {
+ newRoot = new LeafBucket(0);
+ newRoot.nonNotes = root.nonNotes;
+ }
+ root = newRoot;
+ }
+
+ /**
+ * Attach a note to an object.
+ *
+ * If no note exists, a new note is stored. If a note already exists for the
+ * given object, it is replaced (or removed).
+ *
+ * @param noteOn
+ * the object to attach the note to. This same ObjectId can later
+ * be used as an argument to {@link #get(AnyObjectId)} or
+ * {@link #getCachedBytes(AnyObjectId, int)} to read back the
+ * {@code noteData}.
+ * @param noteData
+ * text to store in the note. The text will be UTF-8 encoded when
+ * stored in the repository. If null the note will be deleted, if
+ * the empty string a note with the empty string will be stored.
+ * @param ins
+ * inserter to write the encoded {@code noteData} out as a blob.
+ * The caller must ensure the inserter is flushed before the
+ * updated note map is made available for reading.
+ * @throws IOException
+ * the note data could not be stored in the repository.
+ */
+ public void set(AnyObjectId noteOn, String noteData, ObjectInserter ins)
+ throws IOException {
+ ObjectId dataId;
+ if (noteData != null) {
+ byte[] dataUTF8 = Constants.encode(noteData);
+ dataId = ins.insert(Constants.OBJ_BLOB, dataUTF8);
+ } else {
+ dataId = null;
+ }
+ set(noteOn, dataId);
+ }
+
+ /**
+ * Remove a note from an object.
+ *
+ * If no note exists, no action is performed.
+ *
+ * This method only updates the map in memory.
+ *
+ * @param noteOn
+ * the object to remove the note from.
+ * @throws IOException
+ * a portion of the note space is not accessible.
+ */
+ public void remove(AnyObjectId noteOn) throws IOException {
+ set(noteOn, null);
+ }
+
private void load(ObjectId rootTree) throws MissingObjectException,
IncorrectObjectTypeException, CorruptObjectException, IOException {
AbbreviatedObjectId none = AbbreviatedObjectId.fromString("");