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