View Javadoc
1   /*
2    * Copyright (C) 2010, Sasa Zivkov <sasa.zivkov@sap.com>
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.assertTrue;
48  import static org.junit.Assert.fail;
49  
50  import java.io.IOException;
51  import java.util.Iterator;
52  
53  import org.eclipse.jgit.junit.RepositoryTestCase;
54  import org.eclipse.jgit.junit.TestRepository;
55  import org.eclipse.jgit.lib.ObjectInserter;
56  import org.eclipse.jgit.lib.ObjectReader;
57  import org.eclipse.jgit.lib.Repository;
58  import org.eclipse.jgit.merge.MergeStrategy;
59  import org.eclipse.jgit.revwalk.RevBlob;
60  import org.eclipse.jgit.revwalk.RevCommit;
61  import org.junit.After;
62  import org.junit.Before;
63  import org.junit.Test;
64  
65  public class NoteMapMergerTest extends RepositoryTestCase {
66  	private TestRepository<Repository> tr;
67  
68  	private ObjectReader reader;
69  
70  	private ObjectInserter inserter;
71  
72  	private NoteMap noRoot;
73  
74  	private NoteMap empty;
75  
76  	private NoteMap map_a;
77  
78  	private NoteMap map_a_b;
79  
80  	private RevBlob noteAId;
81  
82  	private String noteAContent;
83  
84  	private RevBlob noteABlob;
85  
86  	private RevBlob noteBId;
87  
88  	private String noteBContent;
89  
90  	private RevBlob noteBBlob;
91  
92  	private RevCommit sampleTree_a;
93  
94  	private RevCommit sampleTree_a_b;
95  
96  	@Override
97  	@Before
98  	public void setUp() throws Exception {
99  		super.setUp();
100 		tr = new TestRepository<>(db);
101 		reader = db.newObjectReader();
102 		inserter = db.newObjectInserter();
103 
104 		noRoot = NoteMap.newMap(null, reader);
105 		empty = NoteMap.newEmptyMap();
106 
107 		noteAId = tr.blob("a");
108 		noteAContent = "noteAContent";
109 		noteABlob = tr.blob(noteAContent);
110 		sampleTree_a = tr.commit()
111 				.add(noteAId.name(), noteABlob)
112 				.create();
113 		tr.parseBody(sampleTree_a);
114 		map_a = NoteMap.read(reader, sampleTree_a);
115 
116 		noteBId = tr.blob("b");
117 		noteBContent = "noteBContent";
118 		noteBBlob = tr.blob(noteBContent);
119 		sampleTree_a_b = tr.commit()
120 				.add(noteAId.name(), noteABlob)
121 				.add(noteBId.name(), noteBBlob)
122 				.create();
123 		tr.parseBody(sampleTree_a_b);
124 		map_a_b = NoteMap.read(reader, sampleTree_a_b);
125 	}
126 
127 	@Override
128 	@After
129 	public void tearDown() throws Exception {
130 		reader.close();
131 		inserter.close();
132 		super.tearDown();
133 	}
134 
135 	@Test
136 	public void testNoChange() throws IOException {
137 		NoteMapMerger merger = new NoteMapMerger(db, null, null);
138 		NoteMap result;
139 
140 		assertEquals(0, countNotes(merger.merge(noRoot, noRoot, noRoot)));
141 		assertEquals(0, countNotes(merger.merge(empty, empty, empty)));
142 
143 		result = merger.merge(map_a, map_a, map_a);
144 		assertEquals(1, countNotes(result));
145 		assertEquals(noteABlob, result.get(noteAId));
146 	}
147 
148 	@Test
149 	public void testOursEqualsTheirs() throws Exception {
150 		NoteMapMerger merger = new NoteMapMerger(db, null, null);
151 		NoteMap result;
152 
153 		assertEquals(0, countNotes(merger.merge(empty, noRoot, noRoot)));
154 		assertEquals(0, countNotes(merger.merge(map_a, noRoot, noRoot)));
155 
156 		assertEquals(0, countNotes(merger.merge(noRoot, empty, empty)));
157 		assertEquals(0, countNotes(merger.merge(map_a, empty, empty)));
158 
159 		result = merger.merge(noRoot, map_a, map_a);
160 		assertEquals(1, countNotes(result));
161 		assertEquals(noteABlob, result.get(noteAId));
162 
163 		result = merger.merge(empty, map_a, map_a);
164 		assertEquals(1, countNotes(result));
165 		assertEquals(noteABlob, result.get(noteAId));
166 
167 		result = merger.merge(map_a_b, map_a, map_a);
168 		assertEquals(1, countNotes(result));
169 		assertEquals(noteABlob, result.get(noteAId));
170 
171 		result = merger.merge(map_a, map_a_b, map_a_b);
172 		assertEquals(2, countNotes(result));
173 		assertEquals(noteABlob, result.get(noteAId));
174 		assertEquals(noteBBlob, result.get(noteBId));
175 	}
176 
177 	@Test
178 	public void testBaseEqualsOurs() throws Exception {
179 		NoteMapMerger merger = new NoteMapMerger(db, null, null);
180 		NoteMap result;
181 
182 		assertEquals(0, countNotes(merger.merge(noRoot, noRoot, empty)));
183 		result = merger.merge(noRoot, noRoot, map_a);
184 		assertEquals(1, countNotes(result));
185 		assertEquals(noteABlob, result.get(noteAId));
186 
187 		assertEquals(0, countNotes(merger.merge(empty, empty, noRoot)));
188 		result = merger.merge(empty, empty, map_a);
189 		assertEquals(1, countNotes(result));
190 		assertEquals(noteABlob, result.get(noteAId));
191 
192 		assertEquals(0, countNotes(merger.merge(map_a, map_a, noRoot)));
193 		assertEquals(0, countNotes(merger.merge(map_a, map_a, empty)));
194 		result = merger.merge(map_a, map_a, map_a_b);
195 		assertEquals(2, countNotes(result));
196 		assertEquals(noteABlob, result.get(noteAId));
197 		assertEquals(noteBBlob, result.get(noteBId));
198 	}
199 
200 	@Test
201 	public void testBaseEqualsTheirs() throws Exception {
202 		NoteMapMerger merger = new NoteMapMerger(db, null, null);
203 		NoteMap result;
204 
205 		assertEquals(0, countNotes(merger.merge(noRoot, empty, noRoot)));
206 		result = merger.merge(noRoot, map_a, noRoot);
207 		assertEquals(1, countNotes(result));
208 		assertEquals(noteABlob, result.get(noteAId));
209 
210 		assertEquals(0, countNotes(merger.merge(empty, noRoot, empty)));
211 		result = merger.merge(empty, map_a, empty);
212 		assertEquals(1, countNotes(result));
213 		assertEquals(noteABlob, result.get(noteAId));
214 
215 		assertEquals(0, countNotes(merger.merge(map_a, noRoot, map_a)));
216 		assertEquals(0, countNotes(merger.merge(map_a, empty, map_a)));
217 		result = merger.merge(map_a, map_a_b, map_a);
218 		assertEquals(2, countNotes(result));
219 		assertEquals(noteABlob, result.get(noteAId));
220 		assertEquals(noteBBlob, result.get(noteBId));
221 	}
222 
223 	@Test
224 	public void testAddDifferentNotes() throws Exception {
225 		NoteMapMerger merger = new NoteMapMerger(db, null, null);
226 		NoteMap result;
227 
228 		NoteMap map_a_c = NoteMap.read(reader, sampleTree_a);
229 		RevBlob noteCId = tr.blob("c");
230 		RevBlob noteCBlob = tr.blob("noteCContent");
231 		map_a_c.set(noteCId, noteCBlob);
232 		map_a_c.writeTree(inserter);
233 
234 		result = merger.merge(map_a, map_a_b, map_a_c);
235 		assertEquals(3, countNotes(result));
236 		assertEquals(noteABlob, result.get(noteAId));
237 		assertEquals(noteBBlob, result.get(noteBId));
238 		assertEquals(noteCBlob, result.get(noteCId));
239 	}
240 
241 	@Test
242 	public void testAddSameNoteDifferentContent() throws Exception {
243 		NoteMapMerger merger = new NoteMapMerger(db, new DefaultNoteMerger(),
244 				null);
245 		NoteMap result;
246 
247 		NoteMap map_a_b1 = NoteMap.read(reader, sampleTree_a);
248 		String noteBContent1 = noteBContent + "change";
249 		RevBlob noteBBlob1 = tr.blob(noteBContent1);
250 		map_a_b1.set(noteBId, noteBBlob1);
251 		map_a_b1.writeTree(inserter);
252 
253 		result = merger.merge(map_a, map_a_b, map_a_b1);
254 		assertEquals(2, countNotes(result));
255 		assertEquals(noteABlob, result.get(noteAId));
256 		assertEquals(tr.blob(noteBContent + noteBContent1), result.get(noteBId));
257 	}
258 
259 	@Test
260 	public void testEditSameNoteDifferentContent() throws Exception {
261 		NoteMapMerger merger = new NoteMapMerger(db, new DefaultNoteMerger(),
262 				null);
263 		NoteMap result;
264 
265 		NoteMap map_a1 = NoteMap.read(reader, sampleTree_a);
266 		String noteAContent1 = noteAContent + "change1";
267 		RevBlob noteABlob1 = tr.blob(noteAContent1);
268 		map_a1.set(noteAId, noteABlob1);
269 		map_a1.writeTree(inserter);
270 
271 		NoteMap map_a2 = NoteMap.read(reader, sampleTree_a);
272 		String noteAContent2 = noteAContent + "change2";
273 		RevBlob noteABlob2 = tr.blob(noteAContent2);
274 		map_a2.set(noteAId, noteABlob2);
275 		map_a2.writeTree(inserter);
276 
277 		result = merger.merge(map_a, map_a1, map_a2);
278 		assertEquals(1, countNotes(result));
279 		assertEquals(tr.blob(noteAContent1 + noteAContent2),
280 				result.get(noteAId));
281 	}
282 
283 	@Test
284 	public void testEditDifferentNotes() throws Exception {
285 		NoteMapMerger merger = new NoteMapMerger(db, null, null);
286 		NoteMap result;
287 
288 		NoteMap map_a1_b = NoteMap.read(reader, sampleTree_a_b);
289 		String noteAContent1 = noteAContent + "change";
290 		RevBlob noteABlob1 = tr.blob(noteAContent1);
291 		map_a1_b.set(noteAId, noteABlob1);
292 		map_a1_b.writeTree(inserter);
293 
294 		NoteMap map_a_b1 = NoteMap.read(reader, sampleTree_a_b);
295 		String noteBContent1 = noteBContent + "change";
296 		RevBlob noteBBlob1 = tr.blob(noteBContent1);
297 		map_a_b1.set(noteBId, noteBBlob1);
298 		map_a_b1.writeTree(inserter);
299 
300 		result = merger.merge(map_a_b, map_a1_b, map_a_b1);
301 		assertEquals(2, countNotes(result));
302 		assertEquals(noteABlob1, result.get(noteAId));
303 		assertEquals(noteBBlob1, result.get(noteBId));
304 	}
305 
306 	@Test
307 	public void testDeleteDifferentNotes() throws Exception {
308 		NoteMapMerger merger = new NoteMapMerger(db, null, null);
309 
310 		NoteMap map_b = NoteMap.read(reader, sampleTree_a_b);
311 		map_b.set(noteAId, null); // delete note a
312 		map_b.writeTree(inserter);
313 
314 		assertEquals(0, countNotes(merger.merge(map_a_b, map_a, map_b)));
315 	}
316 
317 	@Test
318 	public void testEditDeleteConflict() throws Exception {
319 		NoteMapMerger merger = new NoteMapMerger(db, new DefaultNoteMerger(),
320 				null);
321 		NoteMap result;
322 
323 		NoteMap map_a_b1 = NoteMap.read(reader, sampleTree_a_b);
324 		String noteBContent1 = noteBContent + "change";
325 		RevBlob noteBBlob1 = tr.blob(noteBContent1);
326 		map_a_b1.set(noteBId, noteBBlob1);
327 		map_a_b1.writeTree(inserter);
328 
329 		result = merger.merge(map_a_b, map_a_b1, map_a);
330 		assertEquals(2, countNotes(result));
331 		assertEquals(noteABlob, result.get(noteAId));
332 		assertEquals(noteBBlob1, result.get(noteBId));
333 	}
334 
335 	@Test
336 	public void testLargeTreesWithoutConflict() throws Exception {
337 		NoteMapMerger merger = new NoteMapMerger(db, null, null);
338 		NoteMap map1 = createLargeNoteMap("note_1_", "content_1_", 300, 0);
339 		NoteMap map2 = createLargeNoteMap("note_2_", "content_2_", 300, 0);
340 
341 		NoteMap result = merger.merge(empty, map1, map2);
342 		assertEquals(600, countNotes(result));
343 		// check a few random notes
344 		assertEquals(tr.blob("content_1_59"), result.get(tr.blob("note_1_59")));
345 		assertEquals(tr.blob("content_2_10"), result.get(tr.blob("note_2_10")));
346 		assertEquals(tr.blob("content_2_99"), result.get(tr.blob("note_2_99")));
347 	}
348 
349 	@Test
350 	public void testLargeTreesWithConflict() throws Exception {
351 		NoteMapMerger merger = new NoteMapMerger(db, new DefaultNoteMerger(),
352 				null);
353 		NoteMap largeTree1 = createLargeNoteMap("note_1_", "content_1_", 300, 0);
354 		NoteMap largeTree2 = createLargeNoteMap("note_1_", "content_2_", 300, 0);
355 
356 		NoteMap result = merger.merge(empty, largeTree1, largeTree2);
357 		assertEquals(300, countNotes(result));
358 		// check a few random notes
359 		assertEquals(tr.blob("content_1_59content_2_59"),
360 				result.get(tr.blob("note_1_59")));
361 		assertEquals(tr.blob("content_1_10content_2_10"),
362 				result.get(tr.blob("note_1_10")));
363 		assertEquals(tr.blob("content_1_99content_2_99"),
364 				result.get(tr.blob("note_1_99")));
365 	}
366 
367 	private NoteMap createLargeNoteMap(String noteNamePrefix,
368 			String noteContentPrefix, int notesCount, int firstIndex)
369 			throws Exception {
370 		NoteMap result = NoteMap.newEmptyMap();
371 		for (int i = 0; i < notesCount; i++) {
372 			result.set(tr.blob(noteNamePrefix + (firstIndex + i)),
373 					tr.blob(noteContentPrefix + (firstIndex + i)));
374 		}
375 		result.writeTree(inserter);
376 		return result;
377 	}
378 
379 	@Test
380 	public void testFanoutAndLeafWithoutConflict() throws Exception {
381 		NoteMapMerger merger = new NoteMapMerger(db, null, null);
382 
383 		NoteMap largeTree = createLargeNoteMap("note_1_", "content_1_", 300, 0);
384 		NoteMap result = merger.merge(map_a, map_a_b, largeTree);
385 		assertEquals(301, countNotes(result));
386 	}
387 
388 	@Test
389 	public void testFanoutAndLeafWitConflict() throws Exception {
390 		NoteMapMerger merger = new NoteMapMerger(db, new DefaultNoteMerger(),
391 				null);
392 
393 		NoteMap largeTree_b1 = createLargeNoteMap("note_1_", "content_1_", 300,
394 				0);
395 		String noteBContent1 = noteBContent + "change";
396 		largeTree_b1.set(noteBId, tr.blob(noteBContent1));
397 		largeTree_b1.writeTree(inserter);
398 
399 		NoteMap result = merger.merge(map_a, map_a_b, largeTree_b1);
400 		assertEquals(301, countNotes(result));
401 		assertEquals(tr.blob(noteBContent + noteBContent1), result.get(noteBId));
402 	}
403 
404 	@Test
405 	public void testCollapseFanoutAfterMerge() throws Exception {
406 		NoteMapMerger merger = new NoteMapMerger(db, null, null);
407 
408 		NoteMap largeTree = createLargeNoteMap("note_", "content_", 257, 0);
409 		assertTrue(largeTree.getRoot() instanceof FanoutBucket);
410 		NoteMap deleteFirstHundredNotes = createLargeNoteMap("note_", "content_", 157,
411 				100);
412 		NoteMap deleteLastHundredNotes = createLargeNoteMap("note_",
413 				"content_", 157, 0);
414 		NoteMap result = merger.merge(largeTree, deleteFirstHundredNotes,
415 				deleteLastHundredNotes);
416 		assertEquals(57, countNotes(result));
417 		assertTrue(result.getRoot() instanceof LeafBucket);
418 	}
419 
420 	@Test
421 	public void testNonNotesWithoutNonNoteConflict() throws Exception {
422 		NoteMapMerger merger = new NoteMapMerger(db, null,
423 				MergeStrategy.RESOLVE);
424 		RevCommit treeWithNonNotes =
425 			tr.commit()
426 				.add(noteAId.name(), noteABlob) // this is a note
427 				.add("a.txt", tr.blob("content of a.txt")) // this is a non-note
428 				.create();
429 		tr.parseBody(treeWithNonNotes);
430 		NoteMap base = NoteMap.read(reader, treeWithNonNotes);
431 
432 		treeWithNonNotes =
433 			tr.commit()
434 				.add(noteAId.name(), noteABlob)
435 				.add("a.txt", tr.blob("content of a.txt"))
436 				.add("b.txt", tr.blob("content of b.txt"))
437 				.create();
438 		tr.parseBody(treeWithNonNotes);
439 		NoteMap ours = NoteMap.read(reader, treeWithNonNotes);
440 
441 		treeWithNonNotes =
442 			tr.commit()
443 				.add(noteAId.name(), noteABlob)
444 				.add("a.txt", tr.blob("content of a.txt"))
445 				.add("c.txt", tr.blob("content of c.txt"))
446 				.create();
447 		tr.parseBody(treeWithNonNotes);
448 		NoteMap theirs = NoteMap.read(reader, treeWithNonNotes);
449 
450 		NoteMap result = merger.merge(base, ours, theirs);
451 		assertEquals(3, countNonNotes(result));
452 	}
453 
454 	@Test
455 	public void testNonNotesWithNonNoteConflict() throws Exception {
456 		NoteMapMerger merger = new NoteMapMerger(db, null,
457 				MergeStrategy.RESOLVE);
458 		RevCommit treeWithNonNotes =
459 			tr.commit()
460 				.add(noteAId.name(), noteABlob) // this is a note
461 				.add("a.txt", tr.blob("content of a.txt")) // this is a non-note
462 				.create();
463 		tr.parseBody(treeWithNonNotes);
464 		NoteMap base = NoteMap.read(reader, treeWithNonNotes);
465 
466 		treeWithNonNotes =
467 			tr.commit()
468 				.add(noteAId.name(), noteABlob)
469 				.add("a.txt", tr.blob("change 1"))
470 				.create();
471 		tr.parseBody(treeWithNonNotes);
472 		NoteMap ours = NoteMap.read(reader, treeWithNonNotes);
473 
474 		treeWithNonNotes =
475 			tr.commit()
476 				.add(noteAId.name(), noteABlob)
477 				.add("a.txt", tr.blob("change 2"))
478 				.create();
479 		tr.parseBody(treeWithNonNotes);
480 		NoteMap theirs = NoteMap.read(reader, treeWithNonNotes);
481 
482 		try {
483 			merger.merge(base, ours, theirs);
484 			fail("NotesMergeConflictException was expected");
485 		} catch (NotesMergeConflictException e) {
486 			// expected
487 		}
488 	}
489 
490 	private static int countNotes(NoteMap map) {
491 		int c = 0;
492 		Iterator<Note> it = map.iterator();
493 		while (it.hasNext()) {
494 			it.next();
495 			c++;
496 		}
497 		return c;
498 	}
499 
500 	private static int countNonNotes(NoteMap map) {
501 		int c = 0;
502 		NonNoteEntry nonNotes = map.getRoot().nonNotes;
503 		while (nonNotes != null) {
504 			c++;
505 			nonNotes = nonNotes.next;
506 		}
507 		return c;
508 	}
509 }