1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 package org.eclipse.jgit.diff;
45
46 import static org.junit.Assert.assertEquals;
47 import static org.junit.Assert.assertSame;
48 import static org.junit.Assert.assertTrue;
49 import static org.junit.Assert.fail;
50
51 import java.util.Arrays;
52 import java.util.List;
53
54 import org.eclipse.jgit.diff.DiffEntry.ChangeType;
55 import org.eclipse.jgit.junit.RepositoryTestCase;
56 import org.eclipse.jgit.junit.TestRepository;
57 import org.eclipse.jgit.lib.AbbreviatedObjectId;
58 import org.eclipse.jgit.lib.FileMode;
59 import org.eclipse.jgit.lib.ObjectId;
60 import org.eclipse.jgit.lib.Repository;
61 import org.junit.Before;
62 import org.junit.Test;
63
64 public class RenameDetectorTest extends RepositoryTestCase {
65 private static final String PATH_A = "src/A";
66 private static final String PATH_B = "src/B";
67 private static final String PATH_H = "src/H";
68 private static final String PATH_Q = "src/Q";
69
70 private RenameDetector rd;
71
72 private TestRepository<Repository> testDb;
73
74 @Override
75 @Before
76 public void setUp() throws Exception {
77 super.setUp();
78 testDb = new TestRepository<>(db);
79 rd = new RenameDetector(db);
80 }
81
82 @Test
83 public void testExactRename_OneRename() throws Exception {
84 ObjectId foo = blob("foo");
85
86 DiffEntry a = DiffEntry.add(PATH_A, foo);
87 DiffEntry b = DiffEntry.delete(PATH_Q, foo);
88
89 rd.add(a);
90 rd.add(b);
91
92 List<DiffEntry> entries = rd.compute();
93 assertEquals(1, entries.size());
94 assertRename(b, a, 100, entries.get(0));
95 }
96
97 @Test
98 public void testExactRename_DifferentObjects() throws Exception {
99 ObjectId foo = blob("foo");
100 ObjectId bar = blob("bar");
101
102 DiffEntry a = DiffEntry.add(PATH_A, foo);
103 DiffEntry h = DiffEntry.add(PATH_H, foo);
104 DiffEntry q = DiffEntry.delete(PATH_Q, bar);
105
106 rd.add(a);
107 rd.add(h);
108 rd.add(q);
109
110 List<DiffEntry> entries = rd.compute();
111 assertEquals(3, entries.size());
112 assertSame(a, entries.get(0));
113 assertSame(h, entries.get(1));
114 assertSame(q, entries.get(2));
115 }
116
117 @Test
118 public void testExactRename_OneRenameOneModify() throws Exception {
119 ObjectId foo = blob("foo");
120 ObjectId bar = blob("bar");
121
122 DiffEntry a = DiffEntry.add(PATH_A, foo);
123 DiffEntry b = DiffEntry.delete(PATH_Q, foo);
124
125 DiffEntry c = DiffEntry.modify(PATH_H);
126 c.newId = c.oldId = AbbreviatedObjectId.fromObjectId(bar);
127
128 rd.add(a);
129 rd.add(b);
130 rd.add(c);
131
132 List<DiffEntry> entries = rd.compute();
133 assertEquals(2, entries.size());
134 assertRename(b, a, 100, entries.get(0));
135 assertSame(c, entries.get(1));
136 }
137
138 @Test
139 public void testExactRename_ManyRenames() throws Exception {
140 ObjectId foo = blob("foo");
141 ObjectId bar = blob("bar");
142
143 DiffEntry a = DiffEntry.add(PATH_A, foo);
144 DiffEntry b = DiffEntry.delete(PATH_Q, foo);
145
146 DiffEntry c = DiffEntry.add(PATH_H, bar);
147 DiffEntry d = DiffEntry.delete(PATH_B, bar);
148
149 rd.add(a);
150 rd.add(b);
151 rd.add(c);
152 rd.add(d);
153
154 List<DiffEntry> entries = rd.compute();
155 assertEquals(2, entries.size());
156 assertRename(b, a, 100, entries.get(0));
157 assertRename(d, c, 100, entries.get(1));
158 }
159
160 @Test
161 public void testExactRename_MultipleIdenticalDeletes() throws Exception {
162 ObjectId foo = blob("foo");
163
164 DiffEntry a = DiffEntry.delete(PATH_A, foo);
165 DiffEntry b = DiffEntry.delete(PATH_B, foo);
166
167 DiffEntry c = DiffEntry.delete(PATH_H, foo);
168 DiffEntry d = DiffEntry.add(PATH_Q, foo);
169
170 rd.add(a);
171 rd.add(b);
172 rd.add(c);
173 rd.add(d);
174
175
176 List<DiffEntry> entries = rd.compute();
177 assertEquals(3, entries.size());
178 assertEquals(b, entries.get(0));
179 assertEquals(c, entries.get(1));
180 assertRename(a, d, 100, entries.get(2));
181 }
182
183 @Test
184 public void testExactRename_PathBreaksTie() throws Exception {
185 ObjectId foo = blob("foo");
186
187 DiffEntry a = DiffEntry.add("src/com/foo/a.java", foo);
188 DiffEntry b = DiffEntry.delete("src/com/foo/b.java", foo);
189
190 DiffEntry c = DiffEntry.add("c.txt", foo);
191 DiffEntry d = DiffEntry.delete("d.txt", foo);
192 DiffEntry e = DiffEntry.add("the_e_file.txt", foo);
193
194
195 rd.add(a);
196 rd.add(d);
197 rd.add(e);
198 rd.add(b);
199 rd.add(c);
200
201 List<DiffEntry> entries = rd.compute();
202 assertEquals(3, entries.size());
203 assertRename(d, c, 100, entries.get(0));
204 assertRename(b, a, 100, entries.get(1));
205 assertCopy(d, e, 100, entries.get(2));
206 }
207
208 @Test
209 public void testExactRename_OneDeleteManyAdds() throws Exception {
210 ObjectId foo = blob("foo");
211
212 DiffEntry a = DiffEntry.add("src/com/foo/a.java", foo);
213 DiffEntry b = DiffEntry.add("src/com/foo/b.java", foo);
214 DiffEntry c = DiffEntry.add("c.txt", foo);
215
216 DiffEntry d = DiffEntry.delete("d.txt", foo);
217
218 rd.add(a);
219 rd.add(b);
220 rd.add(c);
221 rd.add(d);
222
223 List<DiffEntry> entries = rd.compute();
224 assertEquals(3, entries.size());
225 assertRename(d, c, 100, entries.get(0));
226 assertCopy(d, a, 100, entries.get(1));
227 assertCopy(d, b, 100, entries.get(2));
228 }
229
230 @Test
231 public void testExactRename_UnstagedFile() throws Exception {
232 ObjectId aId = blob("foo");
233 DiffEntry a = DiffEntry.delete(PATH_A, aId);
234 DiffEntry b = DiffEntry.add(PATH_B, aId);
235
236 rd.addAll(Arrays.asList(a, b));
237 List<DiffEntry> entries = rd.compute();
238
239 assertEquals(1, entries.size());
240 assertRename(a, b, 100, entries.get(0));
241 }
242
243 @Test
244 public void testInexactRename_OnePair() throws Exception {
245 ObjectId aId = blob("foo\nbar\nbaz\nblarg\n");
246 ObjectId bId = blob("foo\nbar\nbaz\nblah\n");
247
248 DiffEntry a = DiffEntry.add(PATH_A, aId);
249 DiffEntry b = DiffEntry.delete(PATH_Q, bId);
250
251 rd.add(a);
252 rd.add(b);
253
254 List<DiffEntry> entries = rd.compute();
255 assertEquals(1, entries.size());
256 assertRename(b, a, 66, entries.get(0));
257 }
258
259 @Test
260 public void testInexactRename_OneRenameTwoUnrelatedFiles() throws Exception {
261 ObjectId aId = blob("foo\nbar\nbaz\nblarg\n");
262 ObjectId bId = blob("foo\nbar\nbaz\nblah\n");
263 DiffEntry a = DiffEntry.add(PATH_A, aId);
264 DiffEntry b = DiffEntry.delete(PATH_Q, bId);
265
266 ObjectId cId = blob("some\nsort\nof\ntext\n");
267 ObjectId dId = blob("completely\nunrelated\ntext\n");
268 DiffEntry c = DiffEntry.add(PATH_B, cId);
269 DiffEntry d = DiffEntry.delete(PATH_H, dId);
270
271 rd.add(a);
272 rd.add(b);
273 rd.add(c);
274 rd.add(d);
275
276 List<DiffEntry> entries = rd.compute();
277 assertEquals(3, entries.size());
278 assertRename(b, a, 66, entries.get(0));
279 assertSame(c, entries.get(1));
280 assertSame(d, entries.get(2));
281 }
282
283 @Test
284 public void testInexactRename_LastByteDifferent() throws Exception {
285 ObjectId aId = blob("foo\nbar\na");
286 ObjectId bId = blob("foo\nbar\nb");
287
288 DiffEntry a = DiffEntry.add(PATH_A, aId);
289 DiffEntry b = DiffEntry.delete(PATH_Q, bId);
290
291 rd.add(a);
292 rd.add(b);
293
294 List<DiffEntry> entries = rd.compute();
295 assertEquals(1, entries.size());
296 assertRename(b, a, 88, entries.get(0));
297 }
298
299 @Test
300 public void testInexactRename_NewlinesOnly() throws Exception {
301 ObjectId aId = blob("\n\n\n");
302 ObjectId bId = blob("\n\n\n\n");
303
304 DiffEntry a = DiffEntry.add(PATH_A, aId);
305 DiffEntry b = DiffEntry.delete(PATH_Q, bId);
306
307 rd.add(a);
308 rd.add(b);
309
310 List<DiffEntry> entries = rd.compute();
311 assertEquals(1, entries.size());
312 assertRename(b, a, 74, entries.get(0));
313 }
314
315 @Test
316 public void testInexactRename_SameContentMultipleTimes() throws Exception {
317 ObjectId aId = blob("a\na\na\na\n");
318 ObjectId bId = blob("a\na\na\n");
319
320 DiffEntry a = DiffEntry.add(PATH_A, aId);
321 DiffEntry b = DiffEntry.delete(PATH_Q, bId);
322
323 rd.add(a);
324 rd.add(b);
325
326 List<DiffEntry> entries = rd.compute();
327 assertEquals(1, entries.size());
328 assertRename(b, a, 74, entries.get(0));
329 }
330
331 @Test
332 public void testInexactRenames_OnePair2() throws Exception {
333 ObjectId aId = blob("ab\nab\nab\nac\nad\nae\n");
334 ObjectId bId = blob("ac\nab\nab\nab\naa\na0\na1\n");
335
336 DiffEntry a = DiffEntry.add(PATH_A, aId);
337 DiffEntry b = DiffEntry.delete(PATH_Q, bId);
338
339 rd.add(a);
340 rd.add(b);
341 rd.setRenameScore(50);
342
343 List<DiffEntry> entries = rd.compute();
344 assertEquals(1, entries.size());
345 assertRename(b, a, 57, entries.get(0));
346 }
347
348 @Test
349 public void testNoRenames_SingleByteFiles() throws Exception {
350 ObjectId aId = blob("a");
351 ObjectId bId = blob("b");
352
353 DiffEntry a = DiffEntry.add(PATH_A, aId);
354 DiffEntry b = DiffEntry.delete(PATH_Q, bId);
355
356 rd.add(a);
357 rd.add(b);
358
359 List<DiffEntry> entries = rd.compute();
360 assertEquals(2, entries.size());
361 assertSame(a, entries.get(0));
362 assertSame(b, entries.get(1));
363 }
364
365 @Test
366 public void testNoRenames_EmptyFile1() throws Exception {
367 ObjectId aId = blob("");
368 DiffEntry a = DiffEntry.add(PATH_A, aId);
369
370 rd.add(a);
371
372 List<DiffEntry> entries = rd.compute();
373 assertEquals(1, entries.size());
374 assertSame(a, entries.get(0));
375 }
376
377 @Test
378 public void testNoRenames_EmptyFile2() throws Exception {
379 ObjectId aId = blob("");
380 ObjectId bId = blob("blah");
381
382 DiffEntry a = DiffEntry.add(PATH_A, aId);
383 DiffEntry b = DiffEntry.delete(PATH_Q, bId);
384
385 rd.add(a);
386 rd.add(b);
387
388 List<DiffEntry> entries = rd.compute();
389 assertEquals(2, entries.size());
390 assertSame(a, entries.get(0));
391 assertSame(b, entries.get(1));
392 }
393
394 @Test
395 public void testNoRenames_SymlinkAndFile() throws Exception {
396 ObjectId aId = blob("src/dest");
397
398 DiffEntry a = DiffEntry.add(PATH_A, aId);
399 DiffEntry b = DiffEntry.delete(PATH_Q, aId);
400 b.oldMode = FileMode.SYMLINK;
401
402 rd.add(a);
403 rd.add(b);
404
405 List<DiffEntry> entries = rd.compute();
406 assertEquals(2, entries.size());
407 assertSame(a, entries.get(0));
408 assertSame(b, entries.get(1));
409 }
410
411 @Test
412 public void testNoRenames_GitlinkAndFile() throws Exception {
413 ObjectId aId = blob("src/dest");
414
415 DiffEntry a = DiffEntry.add(PATH_A, aId);
416 DiffEntry b = DiffEntry.delete(PATH_Q, aId);
417 b.oldMode = FileMode.GITLINK;
418
419 rd.add(a);
420 rd.add(b);
421
422 List<DiffEntry> entries = rd.compute();
423 assertEquals(2, entries.size());
424 assertSame(a, entries.get(0));
425 assertSame(b, entries.get(1));
426 }
427
428 @Test
429 public void testNoRenames_SymlinkAndFileSamePath() throws Exception {
430 ObjectId aId = blob("src/dest");
431
432 DiffEntry a = DiffEntry.delete(PATH_A, aId);
433 DiffEntry b = DiffEntry.add(PATH_A, aId);
434 a.oldMode = FileMode.SYMLINK;
435
436 rd.add(a);
437 rd.add(b);
438
439
440 List<DiffEntry> entries = rd.compute();
441 assertEquals(2, entries.size());
442 assertSame(a, entries.get(0));
443 assertSame(b, entries.get(1));
444 }
445
446 @Test
447 public void testNoRenames_UntrackedFile() throws Exception {
448 ObjectId aId = blob("foo");
449 ObjectId bId = ObjectId
450 .fromString("3049eb6eee7e1318f4e78e799bf33f1e54af9cbf");
451
452 DiffEntry a = DiffEntry.delete(PATH_A, aId);
453 DiffEntry b = DiffEntry.add(PATH_B, bId);
454
455 rd.addAll(Arrays.asList(a, b));
456 List<DiffEntry> entries = rd.compute();
457
458 assertEquals(2, entries.size());
459 assertSame(a, entries.get(0));
460 assertSame(b, entries.get(1));
461 }
462
463 @Test
464 public void testBreakModify_BreakAll() throws Exception {
465 ObjectId aId = blob("foo");
466 ObjectId bId = blob("bar");
467
468 DiffEntry m = DiffEntry.modify(PATH_A);
469 m.oldId = AbbreviatedObjectId.fromObjectId(aId);
470 m.newId = AbbreviatedObjectId.fromObjectId(bId);
471
472 DiffEntry a = DiffEntry.add(PATH_B, aId);
473
474 rd.add(a);
475 rd.add(m);
476
477 rd.setBreakScore(101);
478
479 List<DiffEntry> entries = rd.compute();
480 assertEquals(2, entries.size());
481 assertAdd(PATH_A, bId, FileMode.REGULAR_FILE, entries.get(0));
482 assertRename(DiffEntry.breakModify(m).get(0), a, 100, entries.get(1));
483 }
484
485 @Test
486 public void testBreakModify_BreakNone() throws Exception {
487 ObjectId aId = blob("foo");
488 ObjectId bId = blob("bar");
489
490 DiffEntry m = DiffEntry.modify(PATH_A);
491 m.oldId = AbbreviatedObjectId.fromObjectId(aId);
492 m.newId = AbbreviatedObjectId.fromObjectId(bId);
493
494 DiffEntry a = DiffEntry.add(PATH_B, aId);
495
496 rd.add(a);
497 rd.add(m);
498
499 rd.setBreakScore(-1);
500
501 List<DiffEntry> entries = rd.compute();
502 assertEquals(2, entries.size());
503 assertSame(m, entries.get(0));
504 assertSame(a, entries.get(1));
505 }
506
507 @Test
508 public void testBreakModify_BreakBelowScore() throws Exception {
509 ObjectId aId = blob("foo");
510 ObjectId bId = blob("bar");
511
512 DiffEntry m = DiffEntry.modify(PATH_A);
513 m.oldId = AbbreviatedObjectId.fromObjectId(aId);
514 m.newId = AbbreviatedObjectId.fromObjectId(bId);
515
516 DiffEntry a = DiffEntry.add(PATH_B, aId);
517
518 rd.add(a);
519 rd.add(m);
520
521 rd.setBreakScore(20);
522
523 List<DiffEntry> entries = rd.compute();
524 assertEquals(2, entries.size());
525 assertAdd(PATH_A, bId, FileMode.REGULAR_FILE, entries.get(0));
526 assertRename(DiffEntry.breakModify(m).get(0), a, 100, entries.get(1));
527 }
528
529 @Test
530 public void testBreakModify_DontBreakAboveScore() throws Exception {
531 ObjectId aId = blob("blah\nblah\nfoo");
532 ObjectId bId = blob("blah\nblah\nbar");
533
534 DiffEntry m = DiffEntry.modify(PATH_A);
535 m.oldId = AbbreviatedObjectId.fromObjectId(aId);
536 m.newId = AbbreviatedObjectId.fromObjectId(bId);
537
538 DiffEntry a = DiffEntry.add(PATH_B, aId);
539
540 rd.add(a);
541 rd.add(m);
542
543 rd.setBreakScore(20);
544
545 List<DiffEntry> entries = rd.compute();
546 assertEquals(2, entries.size());
547 assertSame(m, entries.get(0));
548 assertSame(a, entries.get(1));
549 }
550
551 @Test
552 public void testBreakModify_RejoinIfUnpaired() throws Exception {
553 ObjectId aId = blob("foo");
554 ObjectId bId = blob("bar");
555
556 DiffEntry m = DiffEntry.modify(PATH_A);
557 m.oldId = AbbreviatedObjectId.fromObjectId(aId);
558 m.newId = AbbreviatedObjectId.fromObjectId(bId);
559
560 rd.add(m);
561
562 rd.setBreakScore(101);
563
564 List<DiffEntry> entries = rd.compute();
565 assertEquals(1, entries.size());
566
567 DiffEntry modify = entries.get(0);
568 assertEquals(m.oldPath, modify.oldPath);
569 assertEquals(m.oldId, modify.oldId);
570 assertEquals(m.oldMode, modify.oldMode);
571 assertEquals(m.newPath, modify.newPath);
572 assertEquals(m.newId, modify.newId);
573 assertEquals(m.newMode, modify.newMode);
574 assertEquals(m.changeType, modify.changeType);
575 assertEquals(0, modify.score);
576 }
577
578 @Test
579 public void testSetRenameScore_IllegalArgs() throws Exception {
580 try {
581 rd.setRenameScore(-1);
582 fail();
583 } catch (IllegalArgumentException e) {
584
585 }
586
587 try {
588 rd.setRenameScore(101);
589 fail();
590 } catch (IllegalArgumentException e) {
591
592 }
593 }
594
595 @Test
596 public void testRenameLimit() throws Exception {
597 ObjectId aId = blob("foo\nbar\nbaz\nblarg\n");
598 ObjectId bId = blob("foo\nbar\nbaz\nblah\n");
599 DiffEntry a = DiffEntry.add(PATH_A, aId);
600 DiffEntry b = DiffEntry.delete(PATH_B, bId);
601
602 ObjectId cId = blob("a\nb\nc\nd\n");
603 ObjectId dId = blob("a\nb\nc\n");
604 DiffEntry c = DiffEntry.add(PATH_H, cId);
605 DiffEntry d = DiffEntry.delete(PATH_Q, dId);
606
607 rd.add(a);
608 rd.add(b);
609 rd.add(c);
610 rd.add(d);
611
612 rd.setRenameLimit(1);
613
614 assertTrue(rd.isOverRenameLimit());
615
616 List<DiffEntry> entries = rd.compute();
617 assertEquals(4, entries.size());
618 assertSame(a, entries.get(0));
619 assertSame(b, entries.get(1));
620 assertSame(c, entries.get(2));
621 assertSame(d, entries.get(3));
622 }
623
624 private ObjectId blob(String content) throws Exception {
625 return testDb.blob(content).copy();
626 }
627
628 private static void assertRename(DiffEntry o, DiffEntry n, int score,
629 DiffEntry rename) {
630 assertEquals(ChangeType.RENAME, rename.getChangeType());
631
632 assertEquals(o.getOldPath(), rename.getOldPath());
633 assertEquals(n.getNewPath(), rename.getNewPath());
634
635 assertEquals(o.getOldMode(), rename.getOldMode());
636 assertEquals(n.getNewMode(), rename.getNewMode());
637
638 assertEquals(o.getOldId(), rename.getOldId());
639 assertEquals(n.getNewId(), rename.getNewId());
640
641 assertEquals(score, rename.getScore());
642 }
643
644 private static void assertCopy(DiffEntry o, DiffEntry n, int score,
645 DiffEntry copy) {
646 assertEquals(ChangeType.COPY, copy.getChangeType());
647
648 assertEquals(o.getOldPath(), copy.getOldPath());
649 assertEquals(n.getNewPath(), copy.getNewPath());
650
651 assertEquals(o.getOldMode(), copy.getOldMode());
652 assertEquals(n.getNewMode(), copy.getNewMode());
653
654 assertEquals(o.getOldId(), copy.getOldId());
655 assertEquals(n.getNewId(), copy.getNewId());
656
657 assertEquals(score, copy.getScore());
658 }
659
660 private static void assertAdd(String newName, ObjectId newId,
661 FileMode newMode, DiffEntry add) {
662 assertEquals(DiffEntry.DEV_NULL, add.oldPath);
663 assertEquals(DiffEntry.A_ZERO, add.oldId);
664 assertEquals(FileMode.MISSING, add.oldMode);
665 assertEquals(ChangeType.ADD, add.changeType);
666 assertEquals(newName, add.newPath);
667 assertEquals(AbbreviatedObjectId.fromObjectId(newId), add.newId);
668 assertEquals(newMode, add.newMode);
669 }
670 }