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