View Javadoc
1   /*
2    * Copyright (C) 2010, 2014 Christian Halstrick <christian.halstrick@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  package org.eclipse.jgit.revplot;
44  
45  import static org.junit.Assert.assertArrayEquals;
46  import static org.junit.Assert.assertEquals;
47  import static org.junit.Assert.assertNotEquals;
48  import static org.junit.Assert.assertTrue;
49  
50  import java.util.HashSet;
51  import java.util.Set;
52  
53  import org.eclipse.jgit.revwalk.RevCommit;
54  import org.eclipse.jgit.revwalk.RevWalkTestCase;
55  import org.junit.Test;
56  
57  public class PlotCommitListTest extends RevWalkTestCase {
58  
59  	class CommitListAssert {
60  		private PlotCommitList<PlotLane> pcl;
61  		private PlotCommit<PlotLane> current;
62  		private int nextIndex = 0;
63  
64  		CommitListAssert(PlotCommitList<PlotLane> pcl) {
65  			this.pcl = pcl;
66  		}
67  
68  		public CommitListAssert commit(RevCommit id) {
69  			assertTrue("Unexpected end of list at pos#"+nextIndex, pcl.size()>nextIndex);
70  			current = pcl.get(nextIndex++);
71  			assertEquals("Expected commit not found at pos#" + (nextIndex - 1),
72  					id.getId(), current.getId());
73  			return this;
74  		}
75  
76  		public CommitListAssert lanePos(int pos) {
77  			PlotLane lane = current.getLane();
78  			assertEquals("Position of lane of commit #" + (nextIndex - 1)
79  					+ " not as expected.", pos, lane.getPosition());
80  			return this;
81  		}
82  
83  		public int getLanePos() {
84  			return current.getLane().position;
85  		}
86  
87  		/**
88  		 * Checks that the current position is valid and consumes this position.
89  		 *
90  		 * @param allowedPositions
91  		 * @return this
92  		 */
93  		public CommitListAssert lanePos(Set<Integer> allowedPositions) {
94  			PlotLane lane = current.getLane();
95  			@SuppressWarnings("boxing")
96  			boolean found = allowedPositions.remove(lane.getPosition());
97  			assertTrue("Position of lane of commit #" + (nextIndex - 1)
98  					+ " not as expected. Expecting one of: " + allowedPositions + " Actual: "+ lane.getPosition(), found);
99  			return this;
100 		}
101 
102 		public CommitListAssert nrOfPassingLanes(int lanes) {
103 			assertEquals("Number of passing lanes of commit #"
104 					+ (nextIndex - 1)
105 					+ " not as expected.", lanes, current.passingLanes.length);
106 			return this;
107 		}
108 
109 		public CommitListAssert parents(RevCommit... parents) {
110 			assertEquals("Number of parents of commit #" + (nextIndex - 1)
111 					+ " not as expected.", parents.length,
112 					current.getParentCount());
113 			for (int i = 0; i < parents.length; i++)
114 				assertEquals("Unexpected parent of commit #" + (nextIndex - 1),
115 						parents[i], current.getParent(i));
116 			return this;
117 		}
118 
119 		public CommitListAssert noMoreCommits() {
120 			assertEquals("Unexpected size of list", nextIndex, pcl.size());
121 			return this;
122 		}
123 	}
124 
125 	private static Set<Integer> asSet(int... numbers) {
126 		Set<Integer> result = new HashSet<Integer>();
127 		for (int n : numbers)
128 			result.add(Integer.valueOf(n));
129 		return result;
130 	}
131 
132 	@Test
133 	public void testLinear() throws Exception {
134 		final RevCommit a = commit();
135 		final RevCommit b = commit(a);
136 		final RevCommit c = commit(b);
137 
138 		PlotWalk pw = new PlotWalk(db);
139 		pw.markStart(pw.lookupCommit(c.getId()));
140 
141 		PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
142 		pcl.source(pw);
143 		pcl.fillTo(Integer.MAX_VALUE);
144 
145 		CommitListAssert test = new CommitListAssert(pcl);
146 		test.commit(c).lanePos(0).parents(b);
147 		test.commit(b).lanePos(0).parents(a);
148 		test.commit(a).lanePos(0).parents();
149 		test.noMoreCommits();
150 	}
151 
152 	@Test
153 	public void testMerged() throws Exception {
154 		final RevCommit a = commit();
155 		final RevCommit b = commit(a);
156 		final RevCommit c = commit(a);
157 		final RevCommit d = commit(b, c);
158 
159 		PlotWalk pw = new PlotWalk(db);
160 		pw.markStart(pw.lookupCommit(d.getId()));
161 
162 		PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
163 		pcl.source(pw);
164 		pcl.fillTo(Integer.MAX_VALUE);
165 
166 		CommitListAssert test = new CommitListAssert(pcl);
167 		test.commit(d).lanePos(0).parents(b, c);
168 		test.commit(c).lanePos(1).parents(a);
169 		test.commit(b).lanePos(0).parents(a);
170 		test.commit(a).lanePos(0).parents();
171 		test.noMoreCommits();
172 	}
173 
174 	@Test
175 	public void testSideBranch() throws Exception {
176 		final RevCommit a = commit();
177 		final RevCommit b = commit(a);
178 		final RevCommit c = commit(a);
179 
180 		PlotWalk pw = new PlotWalk(db);
181 		pw.markStart(pw.lookupCommit(b.getId()));
182 		pw.markStart(pw.lookupCommit(c.getId()));
183 
184 		PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
185 		pcl.source(pw);
186 		pcl.fillTo(Integer.MAX_VALUE);
187 
188 		Set<Integer> childPositions = asSet(0, 1);
189 		CommitListAssert test = new CommitListAssert(pcl);
190 		test.commit(c).lanePos(childPositions).parents(a);
191 		test.commit(b).lanePos(childPositions).parents(a);
192 		test.commit(a).lanePos(0).parents();
193 		test.noMoreCommits();
194 	}
195 
196 	@Test
197 	public void test2SideBranches() throws Exception {
198 		final RevCommit a = commit();
199 		final RevCommit b = commit(a);
200 		final RevCommit c = commit(a);
201 		final RevCommit d = commit(a);
202 
203 		PlotWalk pw = new PlotWalk(db);
204 		pw.markStart(pw.lookupCommit(b.getId()));
205 		pw.markStart(pw.lookupCommit(c.getId()));
206 		pw.markStart(pw.lookupCommit(d.getId()));
207 
208 		PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
209 		pcl.source(pw);
210 		pcl.fillTo(Integer.MAX_VALUE);
211 
212 		Set<Integer> childPositions = asSet(0, 1, 2);
213 		CommitListAssert test = new CommitListAssert(pcl);
214 		test.commit(d).lanePos(childPositions).parents(a);
215 		test.commit(c).lanePos(childPositions).parents(a);
216 		test.commit(b).lanePos(childPositions).parents(a);
217 		test.commit(a).lanePos(0).parents();
218 		test.noMoreCommits();
219 	}
220 
221 	@Test
222 	public void testBug300282_1() throws Exception {
223 		final RevCommit a = commit();
224 		final RevCommit b = commit(a);
225 		final RevCommit c = commit(a);
226 		final RevCommit d = commit(a);
227 		final RevCommit e = commit(a);
228 		final RevCommit f = commit(a);
229 		final RevCommit g = commit(f);
230 
231 		PlotWalk pw = new PlotWalk(db);
232 		// TODO: when we add unnecessary commit's as tips (e.g. a commit which
233 		// is a parent of another tip) the walk will return those commits twice.
234 		// Find out why!
235 		// pw.markStart(pw.lookupCommit(a.getId()));
236 		pw.markStart(pw.lookupCommit(b.getId()));
237 		pw.markStart(pw.lookupCommit(c.getId()));
238 		pw.markStart(pw.lookupCommit(d.getId()));
239 		pw.markStart(pw.lookupCommit(e.getId()));
240 		// pw.markStart(pw.lookupCommit(f.getId()));
241 		pw.markStart(pw.lookupCommit(g.getId()));
242 
243 		PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
244 		pcl.source(pw);
245 		pcl.fillTo(Integer.MAX_VALUE);
246 
247 		Set<Integer> childPositions = asSet(0, 1, 2, 3, 4);
248 		CommitListAssert test = new CommitListAssert(pcl);
249 		int posG = test.commit(g).lanePos(childPositions).parents(f)
250 				.getLanePos();
251 		test.commit(f).lanePos(posG).parents(a);
252 
253 		test.commit(e).lanePos(childPositions).parents(a);
254 		test.commit(d).lanePos(childPositions).parents(a);
255 		test.commit(c).lanePos(childPositions).parents(a);
256 		test.commit(b).lanePos(childPositions).parents(a);
257 		test.commit(a).lanePos(0).parents();
258 		test.noMoreCommits();
259 	}
260 
261 	@Test
262 	public void testBug368927() throws Exception {
263 		final RevCommit a = commit();
264 		final RevCommit b = commit(a);
265 		final RevCommit c = commit(b);
266 		final RevCommit d = commit(b);
267 		final RevCommit e = commit(c);
268 		final RevCommit f = commit(e, d);
269 		final RevCommit g = commit(a);
270 		final RevCommit h = commit(f);
271 		final RevCommit i = commit(h);
272 
273 		PlotWalk pw = new PlotWalk(db);
274 		pw.markStart(pw.lookupCommit(i.getId()));
275 		pw.markStart(pw.lookupCommit(g.getId()));
276 
277 		PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
278 		pcl.source(pw);
279 		pcl.fillTo(Integer.MAX_VALUE);
280 		Set<Integer> childPositions = asSet(0, 1);
281 		CommitListAssert test = new CommitListAssert(pcl);
282 		int posI = test.commit(i).lanePos(childPositions).parents(h)
283 				.getLanePos();
284 		test.commit(h).lanePos(posI).parents(f);
285 		test.commit(g).lanePos(childPositions).parents(a);
286 		test.commit(f).lanePos(posI).parents(e, d);
287 		test.commit(e).lanePos(posI).parents(c);
288 		test.commit(d).lanePos(2).parents(b);
289 		test.commit(c).lanePos(posI).parents(b);
290 		test.commit(b).lanePos(posI).parents(a);
291 		test.commit(a).lanePos(0).parents();
292 	}
293 
294 	// test the history of the egit project between 9fdaf3c1 and e76ad9170f
295 	@Test
296 	public void testEgitHistory() throws Exception {
297 		final RevCommit merge_fix = commit();
298 		final RevCommit add_simple = commit(merge_fix);
299 		final RevCommit remove_unused = commit(merge_fix);
300 		final RevCommit merge_remove = commit(add_simple, remove_unused);
301 		final RevCommit resolve_handler = commit(merge_fix);
302 		final RevCommit clear_repositorycache = commit(merge_remove);
303 		final RevCommit add_Maven = commit(clear_repositorycache);
304 		final RevCommit use_remote = commit(clear_repositorycache);
305 		final RevCommit findToolBar_layout = commit(clear_repositorycache);
306 		final RevCommit merge_add_Maven = commit(findToolBar_layout, add_Maven);
307 		final RevCommit update_eclipse_iplog = commit(merge_add_Maven);
308 		final RevCommit changeset_implementation = commit(clear_repositorycache);
309 		final RevCommit merge_use_remote = commit(update_eclipse_iplog,
310 				use_remote);
311 		final RevCommit disable_source = commit(merge_use_remote);
312 		final RevCommit update_eclipse_iplog2 = commit(merge_use_remote);
313 		final RevCommit merge_disable_source = commit(update_eclipse_iplog2,
314 				disable_source);
315 		final RevCommit merge_changeset_implementation = commit(
316 				merge_disable_source, changeset_implementation);
317 		final RevCommit clone_operation = commit(merge_changeset_implementation);
318 		final RevCommit update_eclipse = commit(add_Maven);
319 		final RevCommit merge_resolve_handler = commit(clone_operation,
320 				resolve_handler);
321 		final RevCommit disable_comment = commit(clone_operation);
322 		final RevCommit merge_disable_comment = commit(merge_resolve_handler,
323 				disable_comment);
324 		final RevCommit fix_broken = commit(merge_disable_comment);
325 		final RevCommit add_a_clear = commit(fix_broken);
326 		final RevCommit merge_update_eclipse = commit(add_a_clear,
327 				update_eclipse);
328 		final RevCommit sort_roots = commit(merge_update_eclipse);
329 		final RevCommit fix_logged_npe = commit(merge_changeset_implementation);
330 		final RevCommit merge_fixed_logged_npe = commit(sort_roots,
331 				fix_logged_npe);
332 
333 		PlotWalk pw = new PlotWalk(db);
334 		pw.markStart(pw.lookupCommit(merge_fixed_logged_npe.getId()));
335 
336 		PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
337 		pcl.source(pw);
338 		pcl.fillTo(Integer.MAX_VALUE);
339 
340 		CommitListAssert test = new CommitListAssert(pcl);
341 
342 		// Note: all positions of side branches are rather arbitrary, but some
343 		// may not overlap. Testing for the positions yielded by the current
344 		// implementation, which was manually checked to not overlap.
345 		final int mainPos = 0;
346 		test.commit(merge_fixed_logged_npe).parents(sort_roots, fix_logged_npe)
347 				.lanePos(mainPos);
348 		test.commit(fix_logged_npe).parents(merge_changeset_implementation)
349 				.lanePos(1);
350 		test.commit(sort_roots).parents(merge_update_eclipse).lanePos(mainPos);
351 		test.commit(merge_update_eclipse).parents(add_a_clear, update_eclipse)
352 				.lanePos(mainPos);
353 		test.commit(add_a_clear).parents(fix_broken).lanePos(mainPos);
354 		test.commit(fix_broken).parents(merge_disable_comment).lanePos(mainPos);
355 		test.commit(merge_disable_comment)
356 				.parents(merge_resolve_handler, disable_comment)
357 				.lanePos(mainPos);
358 		test.commit(disable_comment).parents(clone_operation).lanePos(2);
359 		test.commit(merge_resolve_handler)
360 				.parents(clone_operation, resolve_handler).lanePos(mainPos);
361 		test.commit(update_eclipse).parents(add_Maven).lanePos(3);
362 		test.commit(clone_operation).parents(merge_changeset_implementation)
363 				.lanePos(mainPos);
364 		test.commit(merge_changeset_implementation)
365 				.parents(merge_disable_source, changeset_implementation)
366 				.lanePos(mainPos);
367 		test.commit(merge_disable_source)
368 				.parents(update_eclipse_iplog2, disable_source)
369 				.lanePos(mainPos);
370 		test.commit(update_eclipse_iplog2).parents(merge_use_remote)
371 				.lanePos(mainPos);
372 		test.commit(disable_source).parents(merge_use_remote).lanePos(1);
373 		test.commit(merge_use_remote).parents(update_eclipse_iplog, use_remote)
374 				.lanePos(mainPos);
375 		test.commit(changeset_implementation).parents(clear_repositorycache)
376 				.lanePos(2);
377 		test.commit(update_eclipse_iplog).parents(merge_add_Maven)
378 				.lanePos(mainPos);
379 		test.commit(merge_add_Maven).parents(findToolBar_layout, add_Maven)
380 				.lanePos(mainPos);
381 		test.commit(findToolBar_layout).parents(clear_repositorycache)
382 				.lanePos(mainPos);
383 		test.commit(use_remote).parents(clear_repositorycache).lanePos(1);
384 		test.commit(add_Maven).parents(clear_repositorycache).lanePos(3);
385 		test.commit(clear_repositorycache).parents(merge_remove)
386 				.lanePos(mainPos);
387 		test.commit(resolve_handler).parents(merge_fix).lanePos(4);
388 		test.commit(merge_remove).parents(add_simple, remove_unused)
389 				.lanePos(mainPos);
390 		test.commit(remove_unused).parents(merge_fix).lanePos(1);
391 		test.commit(add_simple).parents(merge_fix).lanePos(mainPos);
392 		test.commit(merge_fix).parents().lanePos(mainPos);
393 		test.noMoreCommits();
394 	}
395 
396 	// test a history where a merge commit has two time the same parent
397 	@Test
398 	public void testDuplicateParents() throws Exception {
399 		final RevCommit m1 = commit();
400 		final RevCommit m2 = commit(m1);
401 		final RevCommit m3 = commit(m2, m2);
402 
403 		final RevCommit s1 = commit(m2);
404 		final RevCommit s2 = commit(s1);
405 
406 		PlotWalk pw = new PlotWalk(db);
407 		pw.markStart(pw.lookupCommit(m3));
408 		pw.markStart(pw.lookupCommit(s2));
409 		PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
410 		pcl.source(pw);
411 		pcl.fillTo(Integer.MAX_VALUE);
412 
413 		CommitListAssert test = new CommitListAssert(pcl);
414 		test.commit(s2).nrOfPassingLanes(0);
415 		test.commit(s1).nrOfPassingLanes(0);
416 		test.commit(m3).nrOfPassingLanes(1);
417 		test.commit(m2).nrOfPassingLanes(0);
418 		test.commit(m1).nrOfPassingLanes(0);
419 		test.noMoreCommits();
420 	}
421 
422 	/**
423 	 * The graph shows the problematic original positioning. Due to this some
424 	 * lanes are no straight lines here, but they are with the new layout code)
425 	 *
426 	 * <pre>
427 	 * a5
428 	 * | \
429 	 * |  a4
430 	 * | /
431 	 * a3
432 	 * |
433 	 * |  e
434 	 * |    \
435 	 * |     |
436 	 * |  b3 |
437 	 * |  |  d
438 	 * |  |/
439 	 * | /|
440 	 * |/ |
441 	 * a2 |
442 	 * |  b2
443 	 * |    \
444 	 * |  c |
445 	 * | /  /
446 	 * |/ b1
447 	 * a1
448 	 * </pre>
449 	 *
450 	 * @throws Exception
451 	 */
452 	@Test
453 	public void testBug419359() throws Exception {
454 		// this may not be the exact situation of bug 419359 but it shows
455 		// similar behavior
456 		final RevCommit a1 = commit();
457 		final RevCommit b1 = commit();
458 		final RevCommit c = commit(a1);
459 		final RevCommit b2 = commit(b1);
460 		final RevCommit a2 = commit(a1);
461 		final RevCommit d = commit(a2);
462 		final RevCommit b3 = commit(b2);
463 		final RevCommit e = commit(d);
464 		final RevCommit a3 = commit(a2);
465 		final RevCommit a4 = commit(a3);
466 		final RevCommit a5 = commit(a3, a4);
467 
468 		PlotWalk pw = new PlotWalk(db);
469 		pw.markStart(pw.lookupCommit(b3.getId()));
470 		pw.markStart(pw.lookupCommit(c.getId()));
471 		pw.markStart(pw.lookupCommit(e.getId()));
472 		pw.markStart(pw.lookupCommit(a5.getId()));
473 
474 		PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
475 		pcl.source(pw);
476 		pcl.fillTo(Integer.MAX_VALUE);
477 
478 		// test that the commits b1, b2 and b3 are on the same position
479 		int bPos = pcl.get(9).lane.position; // b1
480 		assertEquals("b2 is an a different position", bPos,
481 				pcl.get(7).lane.position);
482 		assertEquals("b3 is on a different position", bPos,
483 				pcl.get(4).lane.position);
484 
485 		// test that nothing blocks the connections between b1, b2 and b3
486 		assertNotEquals("b lane is blocked by c", bPos,
487 				pcl.get(8).lane.position);
488 		assertNotEquals("b lane is blocked by a2", bPos,
489 				pcl.get(6).lane.position);
490 		assertNotEquals("b lane is blocked by d", bPos,
491 				pcl.get(5).lane.position);
492 	}
493 
494 	/**
495 	 * <pre>
496 	 *    b3
497 	 * a4 |
498 	 * | \|
499 	 * |  b2
500 	 * a3 |
501 	 * | \|
502 	 * a2 |
503 	 * |  b1
504 	 * | /
505 	 * a1
506 	 * </pre>
507 	 *
508 	 * @throws Exception
509 	 */
510 	@Test
511 	public void testMultipleMerges() throws Exception {
512 		final RevCommit a1 = commit();
513 		final RevCommit b1 = commit(a1);
514 		final RevCommit a2 = commit(a1);
515 		final RevCommit a3 = commit(a2, b1);
516 		final RevCommit b2 = commit(b1);
517 		final RevCommit a4 = commit(a3, b2);
518 		final RevCommit b3 = commit(b2);
519 
520 		PlotWalk pw = new PlotWalk(db);
521 		pw.markStart(pw.lookupCommit(a4));
522 		pw.markStart(pw.lookupCommit(b3));
523 		PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
524 		pcl.source(pw);
525 		pcl.fillTo(Integer.MAX_VALUE);
526 
527 		Set<Integer> positions = asSet(0, 1);
528 		CommitListAssert test = new CommitListAssert(pcl);
529 		int posB = test.commit(b3).lanePos(positions).getLanePos();
530 		int posA = test.commit(a4).lanePos(positions).getLanePos();
531 		test.commit(b2).lanePos(posB);
532 		test.commit(a3).lanePos(posA);
533 		test.commit(a2).lanePos(posA);
534 		test.commit(b1).lanePos(posB);
535 		test.commit(a1).lanePos(posA);
536 		test.noMoreCommits();
537 	}
538 
539 	/**
540 	 * <pre>
541 	 * a4
542 	 * |   b3
543 	 * a3  |
544 	 * | \\|
545 	 * |   |\\
546 	 * |   b2||
547 	 * a2  | //
548 	 * |   b1
549 	 * | /
550 	 * a1
551 	 * </pre>
552 	 *
553 	 * @throws Exception
554 	 */
555 	@Test
556 	public void testMergeBlockedBySelf() throws Exception {
557 		final RevCommit a1 = commit();
558 		final RevCommit b1 = commit(a1);
559 		final RevCommit a2 = commit(a1);
560 		final RevCommit b2 = commit(b1); // blocks merging arc
561 		final RevCommit a3 = commit(a2, b1);
562 		final RevCommit b3 = commit(b2);
563 		final RevCommit a4 = commit(a3);
564 
565 		PlotWalk pw = new PlotWalk(db);
566 		pw.markStart(pw.lookupCommit(a4));
567 		pw.markStart(pw.lookupCommit(b3));
568 		PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
569 		pcl.source(pw);
570 		pcl.fillTo(Integer.MAX_VALUE);
571 
572 		Set<Integer> positions = asSet(0, 1);
573 		CommitListAssert test = new CommitListAssert(pcl);
574 		int posA = test.commit(a4).lanePos(positions).getLanePos();
575 		int posB = test.commit(b3).lanePos(positions).getLanePos();
576 		test.commit(a3).lanePos(posA);
577 		test.commit(b2).lanePos(posB);
578 		test.commit(a2).lanePos(posA);
579 		// b1 is not repositioned, uses "detour lane"
580 		// (drawn as a double arc in the ascii graph above)
581 		test.commit(b1).lanePos(posB);
582 		test.commit(a1).lanePos(posA);
583 		test.noMoreCommits();
584 	}
585 
586 	/**
587 	 * <pre>
588 	 *      b2
589 	 * a4   |
590 	 * |  \ |
591 	 * a3  \|
592 	 * | \  |
593 	 * |  c |
594 	 * | /  |
595 	 * a2   |
596 	 * |    b1
597 	 *     /
598 	 * |  /
599 	 * a1
600 	 * </pre>
601 	 *
602 	 * @throws Exception
603 	 */
604 	@Test
605 	public void testMergeBlockedByOther() throws Exception {
606 		final RevCommit a1 = commit();
607 		final RevCommit b1 = commit(a1);
608 		final RevCommit a2 = commit(a1);
609 		final RevCommit c = commit(a2);// blocks merging arc
610 		final RevCommit a3 = commit(a2, c);
611 		final RevCommit a4 = commit(a3, b1);
612 		final RevCommit b2 = commit(b1);
613 
614 		PlotWalk pw = new PlotWalk(db);
615 		pw.markStart(pw.lookupCommit(a4));
616 		pw.markStart(pw.lookupCommit(b2));
617 		pw.markStart(pw.lookupCommit(c));
618 		PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
619 		pcl.source(pw);
620 		pcl.fillTo(Integer.MAX_VALUE);
621 
622 		Set<Integer> positions = asSet(0, 1, 2);
623 		CommitListAssert test = new CommitListAssert(pcl);
624 		int posB = test.commit(b2).lanePos(positions).getLanePos();
625 		int posA = test.commit(a4).lanePos(positions).getLanePos();
626 		test.commit(a3).lanePos(posA);
627 		test.commit(c).lanePos(positions);
628 		test.commit(a2).lanePos(posA);
629 		test.commit(b1).lanePos(posB); // repositioned to go around c
630 		test.commit(a1).lanePos(posA);
631 		test.noMoreCommits();
632 	}
633 
634 	/**
635 	 * <pre>
636 	 *     b1
637 	 * a3  |
638 	 * |   |
639 	 * a2  |
640 	 * -- processing stops here --
641 	 * |  /
642 	 * a1
643 	 * </pre>
644 	 *
645 	 * @throws Exception
646 	 */
647 	@Test
648 	public void testDanglingCommitShouldContinueLane() throws Exception {
649 		final RevCommit a1 = commit();
650 		final RevCommit a2 = commit(a1);
651 		final RevCommit a3 = commit(a2);
652 		final RevCommit b1 = commit(a1);
653 
654 		PlotWalk pw = new PlotWalk(db);
655 		pw.markStart(pw.lookupCommit(a3));
656 		pw.markStart(pw.lookupCommit(b1));
657 		PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
658 		pcl.source(pw);
659 		pcl.fillTo(2); // don't process a1
660 
661 		Set<Integer> positions = asSet(0, 1);
662 		CommitListAssert test = new CommitListAssert(pcl);
663 		PlotLane laneB = test.commit(b1).lanePos(positions).current.getLane();
664 		int posA = test.commit(a3).lanePos(positions).getLanePos();
665 		test.commit(a2).lanePos(posA);
666 		assertArrayEquals(
667 				"Although the parent of b1, a1, is not processed yet, the b lane should still be drawn",
668 				new PlotLane[] { laneB }, test.current.passingLanes);
669 		test.noMoreCommits();
670 	}
671 
672 	@Test
673 	public void testTwoRoots1() throws Exception {
674 		final RevCommit a = commit();
675 		final RevCommit b = commit();
676 
677 		PlotWalk pw = new PlotWalk(db);
678 		pw.markStart(pw.lookupCommit(a));
679 		pw.markStart(pw.lookupCommit(b));
680 		PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
681 		pcl.source(pw);
682 		pcl.fillTo(Integer.MAX_VALUE);
683 
684 		CommitListAssert test = new CommitListAssert(pcl);
685 		test.commit(b).lanePos(0);
686 		test.commit(a).lanePos(0);
687 		test.noMoreCommits();
688 	}
689 
690 	@Test
691 	public void testTwoRoots2() throws Exception {
692 		final RevCommit a = commit();
693 		final RevCommit b1 = commit();
694 		final RevCommit b2 = commit(b1);
695 
696 		PlotWalk pw = new PlotWalk(db);
697 		pw.markStart(pw.lookupCommit(a));
698 		pw.markStart(pw.lookupCommit(b2));
699 		PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
700 		pcl.source(pw);
701 		pcl.fillTo(Integer.MAX_VALUE);
702 
703 		CommitListAssert test = new CommitListAssert(pcl);
704 		test.commit(b2).lanePos(0);
705 		test.commit(b1).lanePos(0);
706 		test.commit(a).lanePos(0);
707 		test.noMoreCommits();
708 	}
709 }