View Javadoc
1   /*
2    * Copyright (C) 2010, Google Inc.
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  
44  package org.eclipse.jgit.notes;
45  
46  import static org.junit.Assert.assertEquals;
47  import static org.junit.Assert.assertFalse;
48  import static org.junit.Assert.assertNotNull;
49  import static org.junit.Assert.assertNotSame;
50  import static org.junit.Assert.assertNull;
51  import static org.junit.Assert.assertSame;
52  import static org.junit.Assert.assertTrue;
53  
54  import java.io.IOException;
55  import java.util.Iterator;
56  
57  import org.eclipse.jgit.junit.RepositoryTestCase;
58  import org.eclipse.jgit.junit.TestRepository;
59  import org.eclipse.jgit.lib.CommitBuilder;
60  import org.eclipse.jgit.lib.Constants;
61  import org.eclipse.jgit.lib.MutableObjectId;
62  import org.eclipse.jgit.lib.ObjectInserter;
63  import org.eclipse.jgit.lib.ObjectReader;
64  import org.eclipse.jgit.lib.Repository;
65  import org.eclipse.jgit.revwalk.RevBlob;
66  import org.eclipse.jgit.revwalk.RevCommit;
67  import org.eclipse.jgit.revwalk.RevTree;
68  import org.eclipse.jgit.treewalk.TreeWalk;
69  import org.eclipse.jgit.util.RawParseUtils;
70  import org.junit.After;
71  import org.junit.Before;
72  import org.junit.Test;
73  
74  public class NoteMapTest extends RepositoryTestCase {
75  	private TestRepository<Repository> tr;
76  
77  	private ObjectReader reader;
78  
79  	private ObjectInserter inserter;
80  
81  	@Override
82  	@Before
83  	public void setUp() throws Exception {
84  		super.setUp();
85  
86  		tr = new TestRepository<Repository>(db);
87  		reader = db.newObjectReader();
88  		inserter = db.newObjectInserter();
89  	}
90  
91  	@Override
92  	@After
93  	public void tearDown() throws Exception {
94  		reader.close();
95  		inserter.close();
96  		super.tearDown();
97  	}
98  
99  	@Test
100 	public void testReadFlatTwoNotes() throws Exception {
101 		RevBlob a = tr.blob("a");
102 		RevBlob b = tr.blob("b");
103 		RevBlob data1 = tr.blob("data1");
104 		RevBlob data2 = tr.blob("data2");
105 
106 		RevCommit r = tr.commit() //
107 				.add(a.name(), data1) //
108 				.add(b.name(), data2) //
109 				.create();
110 		tr.parseBody(r);
111 
112 		NoteMap map = NoteMap.read(reader, r);
113 		assertNotNull("have map", map);
114 
115 		assertTrue("has note for a", map.contains(a));
116 		assertTrue("has note for b", map.contains(b));
117 		assertEquals(data1, map.get(a));
118 		assertEquals(data2, map.get(b));
119 
120 		assertFalse("no note for data1", map.contains(data1));
121 		assertNull("no note for data1", map.get(data1));
122 	}
123 
124 	@Test
125 	public void testReadFanout2_38() throws Exception {
126 		RevBlob a = tr.blob("a");
127 		RevBlob b = tr.blob("b");
128 		RevBlob data1 = tr.blob("data1");
129 		RevBlob data2 = tr.blob("data2");
130 
131 		RevCommit r = tr.commit() //
132 				.add(fanout(2, a.name()), data1) //
133 				.add(fanout(2, b.name()), data2) //
134 				.create();
135 		tr.parseBody(r);
136 
137 		NoteMap map = NoteMap.read(reader, r);
138 		assertNotNull("have map", map);
139 
140 		assertTrue("has note for a", map.contains(a));
141 		assertTrue("has note for b", map.contains(b));
142 		assertEquals(data1, map.get(a));
143 		assertEquals(data2, map.get(b));
144 
145 		assertFalse("no note for data1", map.contains(data1));
146 		assertNull("no note for data1", map.get(data1));
147 	}
148 
149 	@Test
150 	public void testReadFanout2_2_36() throws Exception {
151 		RevBlob a = tr.blob("a");
152 		RevBlob b = tr.blob("b");
153 		RevBlob data1 = tr.blob("data1");
154 		RevBlob data2 = tr.blob("data2");
155 
156 		RevCommit r = tr.commit() //
157 				.add(fanout(4, a.name()), data1) //
158 				.add(fanout(4, b.name()), data2) //
159 				.create();
160 		tr.parseBody(r);
161 
162 		NoteMap map = NoteMap.read(reader, r);
163 		assertNotNull("have map", map);
164 
165 		assertTrue("has note for a", map.contains(a));
166 		assertTrue("has note for b", map.contains(b));
167 		assertEquals(data1, map.get(a));
168 		assertEquals(data2, map.get(b));
169 
170 		assertFalse("no note for data1", map.contains(data1));
171 		assertNull("no note for data1", map.get(data1));
172 	}
173 
174 	@Test
175 	public void testReadFullyFannedOut() throws Exception {
176 		RevBlob a = tr.blob("a");
177 		RevBlob b = tr.blob("b");
178 		RevBlob data1 = tr.blob("data1");
179 		RevBlob data2 = tr.blob("data2");
180 
181 		RevCommit r = tr.commit() //
182 				.add(fanout(38, a.name()), data1) //
183 				.add(fanout(38, b.name()), data2) //
184 				.create();
185 		tr.parseBody(r);
186 
187 		NoteMap map = NoteMap.read(reader, r);
188 		assertNotNull("have map", map);
189 
190 		assertTrue("has note for a", map.contains(a));
191 		assertTrue("has note for b", map.contains(b));
192 		assertEquals(data1, map.get(a));
193 		assertEquals(data2, map.get(b));
194 
195 		assertFalse("no note for data1", map.contains(data1));
196 		assertNull("no note for data1", map.get(data1));
197 	}
198 
199 	@Test
200 	public void testGetCachedBytes() throws Exception {
201 		final String exp = "this is test data";
202 		RevBlob a = tr.blob("a");
203 		RevBlob data = tr.blob(exp);
204 
205 		RevCommit r = tr.commit() //
206 				.add(a.name(), data) //
207 				.create();
208 		tr.parseBody(r);
209 
210 		NoteMap map = NoteMap.read(reader, r);
211 		byte[] act = map.getCachedBytes(a, exp.length() * 4);
212 		assertNotNull("has data for a", act);
213 		assertEquals(exp, RawParseUtils.decode(act));
214 	}
215 
216 	@Test
217 	public void testWriteUnchangedFlat() throws Exception {
218 		RevBlob a = tr.blob("a");
219 		RevBlob b = tr.blob("b");
220 		RevBlob data1 = tr.blob("data1");
221 		RevBlob data2 = tr.blob("data2");
222 
223 		RevCommit r = tr.commit() //
224 				.add(a.name(), data1) //
225 				.add(b.name(), data2) //
226 				.add(".gitignore", "") //
227 				.add("zoo-animals.txt", "") //
228 				.create();
229 		tr.parseBody(r);
230 
231 		NoteMap map = NoteMap.read(reader, r);
232 		assertTrue("has note for a", map.contains(a));
233 		assertTrue("has note for b", map.contains(b));
234 
235 		RevCommit n = commitNoteMap(map);
236 		assertNotSame("is new commit", r, n);
237 		assertSame("same tree", r.getTree(), n.getTree());
238 	}
239 
240 	@Test
241 	public void testWriteUnchangedFanout2_38() throws Exception {
242 		RevBlob a = tr.blob("a");
243 		RevBlob b = tr.blob("b");
244 		RevBlob data1 = tr.blob("data1");
245 		RevBlob data2 = tr.blob("data2");
246 
247 		RevCommit r = tr.commit() //
248 				.add(fanout(2, a.name()), data1) //
249 				.add(fanout(2, b.name()), data2) //
250 				.add(".gitignore", "") //
251 				.add("zoo-animals.txt", "") //
252 				.create();
253 		tr.parseBody(r);
254 
255 		NoteMap map = NoteMap.read(reader, r);
256 		assertTrue("has note for a", map.contains(a));
257 		assertTrue("has note for b", map.contains(b));
258 
259 		// This is a non-lazy map, so we'll be looking at the leaf buckets.
260 		RevCommit n = commitNoteMap(map);
261 		assertNotSame("is new commit", r, n);
262 		assertSame("same tree", r.getTree(), n.getTree());
263 
264 		// Use a lazy-map for the next round of the same test.
265 		map = NoteMap.read(reader, r);
266 		n = commitNoteMap(map);
267 		assertNotSame("is new commit", r, n);
268 		assertSame("same tree", r.getTree(), n.getTree());
269 	}
270 
271 	@Test
272 	public void testCreateFromEmpty() throws Exception {
273 		RevBlob a = tr.blob("a");
274 		RevBlob b = tr.blob("b");
275 		RevBlob data1 = tr.blob("data1");
276 		RevBlob data2 = tr.blob("data2");
277 
278 		NoteMap map = NoteMap.newEmptyMap();
279 		assertFalse("no a", map.contains(a));
280 		assertFalse("no b", map.contains(b));
281 
282 		map.set(a, data1);
283 		map.set(b, data2);
284 
285 		assertEquals(data1, map.get(a));
286 		assertEquals(data2, map.get(b));
287 
288 		map.remove(a);
289 		map.remove(b);
290 
291 		assertFalse("no a", map.contains(a));
292 		assertFalse("no b", map.contains(b));
293 
294 		map.set(a, "data1", inserter);
295 		assertEquals(data1, map.get(a));
296 
297 		map.set(a, null, inserter);
298 		assertFalse("no a", map.contains(a));
299 	}
300 
301 	@Test
302 	public void testEditFlat() throws Exception {
303 		RevBlob a = tr.blob("a");
304 		RevBlob b = tr.blob("b");
305 		RevBlob data1 = tr.blob("data1");
306 		RevBlob data2 = tr.blob("data2");
307 
308 		RevCommit r = tr.commit() //
309 				.add(a.name(), data1) //
310 				.add(b.name(), data2) //
311 				.add(".gitignore", "") //
312 				.add("zoo-animals.txt", b) //
313 				.create();
314 		tr.parseBody(r);
315 
316 		NoteMap map = NoteMap.read(reader, r);
317 		map.set(a, data2);
318 		map.set(b, null);
319 		map.set(data1, b);
320 		map.set(data2, null);
321 
322 		assertEquals(data2, map.get(a));
323 		assertEquals(b, map.get(data1));
324 		assertFalse("no b", map.contains(b));
325 		assertFalse("no data2", map.contains(data2));
326 
327 		MutableObjectId id = new MutableObjectId();
328 		for (int p = 42; p > 0; p--) {
329 			id.setByte(1, p);
330 			map.set(id, data1);
331 		}
332 
333 		for (int p = 42; p > 0; p--) {
334 			id.setByte(1, p);
335 			assertTrue("contains " + id, map.contains(id));
336 		}
337 
338 		RevCommit n = commitNoteMap(map);
339 		map = NoteMap.read(reader, n);
340 		assertEquals(data2, map.get(a));
341 		assertEquals(b, map.get(data1));
342 		assertFalse("no b", map.contains(b));
343 		assertFalse("no data2", map.contains(data2));
344 		assertEquals(b, TreeWalk
345 				.forPath(reader, "zoo-animals.txt", n.getTree()).getObjectId(0));
346 	}
347 
348 	@Test
349 	public void testEditFanout2_38() throws Exception {
350 		RevBlob a = tr.blob("a");
351 		RevBlob b = tr.blob("b");
352 		RevBlob data1 = tr.blob("data1");
353 		RevBlob data2 = tr.blob("data2");
354 
355 		RevCommit r = tr.commit() //
356 				.add(fanout(2, a.name()), data1) //
357 				.add(fanout(2, b.name()), data2) //
358 				.add(".gitignore", "") //
359 				.add("zoo-animals.txt", b) //
360 				.create();
361 		tr.parseBody(r);
362 
363 		NoteMap map = NoteMap.read(reader, r);
364 		map.set(a, data2);
365 		map.set(b, null);
366 		map.set(data1, b);
367 		map.set(data2, null);
368 
369 		assertEquals(data2, map.get(a));
370 		assertEquals(b, map.get(data1));
371 		assertFalse("no b", map.contains(b));
372 		assertFalse("no data2", map.contains(data2));
373 		RevCommit n = commitNoteMap(map);
374 
375 		map.set(a, null);
376 		map.set(data1, null);
377 		assertFalse("no a", map.contains(a));
378 		assertFalse("no data1", map.contains(data1));
379 
380 		map = NoteMap.read(reader, n);
381 		assertEquals(data2, map.get(a));
382 		assertEquals(b, map.get(data1));
383 		assertFalse("no b", map.contains(b));
384 		assertFalse("no data2", map.contains(data2));
385 		assertEquals(b, TreeWalk
386 				.forPath(reader, "zoo-animals.txt", n.getTree()).getObjectId(0));
387 	}
388 
389 	@Test
390 	public void testLeafSplitsWhenFull() throws Exception {
391 		RevBlob data1 = tr.blob("data1");
392 		MutableObjectId idBuf = new MutableObjectId();
393 
394 		RevCommit r = tr.commit() //
395 				.add(data1.name(), data1) //
396 				.create();
397 		tr.parseBody(r);
398 
399 		NoteMap map = NoteMap.read(reader, r);
400 		for (int i = 0; i < 254; i++) {
401 			idBuf.setByte(Constants.OBJECT_ID_LENGTH - 1, i);
402 			map.set(idBuf, data1);
403 		}
404 
405 		RevCommit n = commitNoteMap(map);
406 		TreeWalk tw = new TreeWalk(reader);
407 		tw.reset(n.getTree());
408 		while (tw.next())
409 			assertFalse("no fan-out subtree", tw.isSubtree());
410 
411 		for (int i = 254; i < 256; i++) {
412 			idBuf.setByte(Constants.OBJECT_ID_LENGTH - 1, i);
413 			map.set(idBuf, data1);
414 		}
415 		idBuf.setByte(Constants.OBJECT_ID_LENGTH - 2, 1);
416 		map.set(idBuf, data1);
417 		n = commitNoteMap(map);
418 
419 		// The 00 bucket is fully split.
420 		String path = fanout(38, idBuf.name());
421 		tw = TreeWalk.forPath(reader, path, n.getTree());
422 		assertNotNull("has " + path, tw);
423 
424 		// The other bucket is not.
425 		path = fanout(2, data1.name());
426 		tw = TreeWalk.forPath(reader, path, n.getTree());
427 		assertNotNull("has " + path, tw);
428 	}
429 
430 	@Test
431 	public void testRemoveDeletesTreeFanout2_38() throws Exception {
432 		RevBlob a = tr.blob("a");
433 		RevBlob data1 = tr.blob("data1");
434 		RevTree empty = tr.tree();
435 
436 		RevCommit r = tr.commit() //
437 				.add(fanout(2, a.name()), data1) //
438 				.create();
439 		tr.parseBody(r);
440 
441 		NoteMap map = NoteMap.read(reader, r);
442 		map.set(a, null);
443 
444 		RevCommit n = commitNoteMap(map);
445 		assertEquals("empty tree", empty, n.getTree());
446 	}
447 
448 	public void testIteratorEmptyMap() {
449 		Iterator<Note> it = NoteMap.newEmptyMap().iterator();
450 		assertFalse(it.hasNext());
451 	}
452 
453 	public void testIteratorFlatTree() throws Exception {
454 		RevBlob a = tr.blob("a");
455 		RevBlob b = tr.blob("b");
456 		RevBlob data1 = tr.blob("data1");
457 		RevBlob data2 = tr.blob("data2");
458 		RevBlob nonNote = tr.blob("non note");
459 
460 		RevCommit r = tr.commit() //
461 				.add(a.name(), data1) //
462 				.add(b.name(), data2) //
463 				.add("nonNote", nonNote) //
464 				.create();
465 		tr.parseBody(r);
466 
467 		Iterator it = NoteMap.read(reader, r).iterator();
468 		assertEquals(2, count(it));
469 	}
470 
471 	public void testIteratorFanoutTree2_38() throws Exception {
472 		RevBlob a = tr.blob("a");
473 		RevBlob b = tr.blob("b");
474 		RevBlob data1 = tr.blob("data1");
475 		RevBlob data2 = tr.blob("data2");
476 		RevBlob nonNote = tr.blob("non note");
477 
478 		RevCommit r = tr.commit() //
479 				.add(fanout(2, a.name()), data1) //
480 				.add(fanout(2, b.name()), data2) //
481 				.add("nonNote", nonNote) //
482 				.create();
483 		tr.parseBody(r);
484 
485 		Iterator it = NoteMap.read(reader, r).iterator();
486 		assertEquals(2, count(it));
487 	}
488 
489 	public void testIteratorFanoutTree2_2_36() throws Exception {
490 		RevBlob a = tr.blob("a");
491 		RevBlob b = tr.blob("b");
492 		RevBlob data1 = tr.blob("data1");
493 		RevBlob data2 = tr.blob("data2");
494 		RevBlob nonNote = tr.blob("non note");
495 
496 		RevCommit r = tr.commit() //
497 				.add(fanout(4, a.name()), data1) //
498 				.add(fanout(4, b.name()), data2) //
499 				.add("nonNote", nonNote) //
500 				.create();
501 		tr.parseBody(r);
502 
503 		Iterator it = NoteMap.read(reader, r).iterator();
504 		assertEquals(2, count(it));
505 	}
506 
507 	public void testIteratorFullyFannedOut() throws Exception {
508 		RevBlob a = tr.blob("a");
509 		RevBlob b = tr.blob("b");
510 		RevBlob data1 = tr.blob("data1");
511 		RevBlob data2 = tr.blob("data2");
512 		RevBlob nonNote = tr.blob("non note");
513 
514 		RevCommit r = tr.commit() //
515 				.add(fanout(38, a.name()), data1) //
516 				.add(fanout(38, b.name()), data2) //
517 				.add("nonNote", nonNote) //
518 				.create();
519 		tr.parseBody(r);
520 
521 		Iterator it = NoteMap.read(reader, r).iterator();
522 		assertEquals(2, count(it));
523 	}
524 
525 	public void testShorteningNoteRefName() throws Exception {
526 		String expectedShortName = "review";
527 		String noteRefName = Constants.R_NOTES + expectedShortName;
528 		assertEquals(expectedShortName, NoteMap.shortenRefName(noteRefName));
529 		String nonNoteRefName = Constants.R_HEADS + expectedShortName;
530 		assertEquals(nonNoteRefName, NoteMap.shortenRefName(expectedShortName));
531 	}
532 
533 	private RevCommit commitNoteMap(NoteMap map) throws IOException {
534 		tr.tick(600);
535 
536 		CommitBuilder builder = new CommitBuilder();
537 		builder.setTreeId(map.writeTree(inserter));
538 		tr.setAuthorAndCommitter(builder);
539 		return tr.getRevWalk().parseCommit(inserter.insert(builder));
540 	}
541 
542 	private static String fanout(int prefix, String name) {
543 		StringBuilder r = new StringBuilder();
544 		int i = 0;
545 		for (; i < prefix && i < name.length(); i += 2) {
546 			if (i != 0)
547 				r.append('/');
548 			r.append(name.charAt(i + 0));
549 			r.append(name.charAt(i + 1));
550 		}
551 		if (i < name.length()) {
552 			if (i != 0)
553 				r.append('/');
554 			r.append(name.substring(i));
555 		}
556 		return r.toString();
557 	}
558 
559 	private static int count(Iterator it) {
560 		int c = 0;
561 		while (it.hasNext()) {
562 			c++;
563 			it.next();
564 		}
565 		return c;
566 	}
567 }