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