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
48 import java.io.BufferedOutputStream;
49 import java.io.ByteArrayOutputStream;
50 import java.io.File;
51
52 import org.eclipse.jgit.api.Git;
53 import org.eclipse.jgit.diff.DiffEntry.ChangeType;
54 import org.eclipse.jgit.dircache.DirCacheIterator;
55 import org.eclipse.jgit.junit.RepositoryTestCase;
56 import org.eclipse.jgit.junit.TestRepository;
57 import org.eclipse.jgit.lib.AnyObjectId;
58 import org.eclipse.jgit.lib.ConfigConstants;
59 import org.eclipse.jgit.lib.FileMode;
60 import org.eclipse.jgit.lib.ObjectId;
61 import org.eclipse.jgit.lib.Repository;
62 import org.eclipse.jgit.patch.FileHeader;
63 import org.eclipse.jgit.patch.HunkHeader;
64 import org.eclipse.jgit.revwalk.RevCommit;
65 import org.eclipse.jgit.storage.file.FileBasedConfig;
66 import org.eclipse.jgit.treewalk.FileTreeIterator;
67 import org.eclipse.jgit.treewalk.filter.PathFilter;
68 import org.eclipse.jgit.util.FileUtils;
69 import org.eclipse.jgit.util.RawParseUtils;
70 import org.eclipse.jgit.util.io.DisabledOutputStream;
71 import org.junit.After;
72 import org.junit.Before;
73 import org.junit.Test;
74
75 public class DiffFormatterTest extends RepositoryTestCase {
76 private static final String DIFF = "diff --git ";
77
78 private static final String REGULAR_FILE = "100644";
79
80 private static final String GITLINK = "160000";
81
82 private static final String PATH_A = "src/a";
83
84 private static final String PATH_B = "src/b";
85
86 private DiffFormatter df;
87
88 private TestRepository<Repository> testDb;
89
90 @Override
91 @Before
92 public void setUp() throws Exception {
93 super.setUp();
94 testDb = new TestRepository<>(db);
95 df = new DiffFormatter(DisabledOutputStream.INSTANCE);
96 df.setRepository(db);
97 df.setAbbreviationLength(8);
98 }
99
100 @Override
101 @After
102 public void tearDown() throws Exception {
103 if (df != null) {
104 df.close();
105 }
106 super.tearDown();
107 }
108
109 @Test
110 public void testCreateFileHeader_Add() throws Exception {
111 ObjectId adId = blob("a\nd\n");
112 DiffEntry ent = DiffEntry.add("FOO", adId);
113 FileHeader fh = df.toFileHeader(ent);
114
115 String diffHeader = "diff --git a/FOO b/FOO\n"
116 + "new file mode " + REGULAR_FILE + "\n"
117 + "index "
118 + ObjectId.zeroId().abbreviate(8).name()
119 + ".."
120 + adId.abbreviate(8).name() + "\n"
121 + "--- /dev/null\n"
122 + "+++ b/FOO\n";
123 assertEquals(diffHeader, RawParseUtils.decode(fh.getBuffer()));
124
125 assertEquals(0, fh.getStartOffset());
126 assertEquals(fh.getBuffer().length, fh.getEndOffset());
127 assertEquals(FileHeader.PatchType.UNIFIED, fh.getPatchType());
128
129 assertEquals(1, fh.getHunks().size());
130
131 HunkHeader hh = fh.getHunks().get(0);
132 assertEquals(1, hh.toEditList().size());
133
134 EditList el = hh.toEditList();
135 assertEquals(1, el.size());
136
137 Edit e = el.get(0);
138 assertEquals(0, e.getBeginA());
139 assertEquals(0, e.getEndA());
140 assertEquals(0, e.getBeginB());
141 assertEquals(2, e.getEndB());
142 assertEquals(Edit.Type.INSERT, e.getType());
143 }
144
145 @Test
146 public void testCreateFileHeader_Delete() throws Exception {
147 ObjectId adId = blob("a\nd\n");
148 DiffEntry ent = DiffEntry.delete("FOO", adId);
149 FileHeader fh = df.toFileHeader(ent);
150
151 String diffHeader = "diff --git a/FOO b/FOO\n"
152 + "deleted file mode " + REGULAR_FILE + "\n"
153 + "index "
154 + adId.abbreviate(8).name()
155 + ".."
156 + ObjectId.zeroId().abbreviate(8).name() + "\n"
157 + "--- a/FOO\n"
158 + "+++ /dev/null\n";
159 assertEquals(diffHeader, RawParseUtils.decode(fh.getBuffer()));
160
161 assertEquals(0, fh.getStartOffset());
162 assertEquals(fh.getBuffer().length, fh.getEndOffset());
163 assertEquals(FileHeader.PatchType.UNIFIED, fh.getPatchType());
164
165 assertEquals(1, fh.getHunks().size());
166
167 HunkHeader hh = fh.getHunks().get(0);
168 assertEquals(1, hh.toEditList().size());
169
170 EditList el = hh.toEditList();
171 assertEquals(1, el.size());
172
173 Edit e = el.get(0);
174 assertEquals(0, e.getBeginA());
175 assertEquals(2, e.getEndA());
176 assertEquals(0, e.getBeginB());
177 assertEquals(0, e.getEndB());
178 assertEquals(Edit.Type.DELETE, e.getType());
179 }
180
181 @Test
182 public void testCreateFileHeader_Modify() throws Exception {
183 ObjectId adId = blob("a\nd\n");
184 ObjectId abcdId = blob("a\nb\nc\nd\n");
185
186 String diffHeader = makeDiffHeader(PATH_A, PATH_A, adId, abcdId);
187
188 DiffEntry ad = DiffEntry.delete(PATH_A, adId);
189 DiffEntry abcd = DiffEntry.add(PATH_A, abcdId);
190
191 DiffEntry mod = DiffEntry.pair(ChangeType.MODIFY, ad, abcd, 0);
192
193 FileHeader fh = df.toFileHeader(mod);
194
195 assertEquals(diffHeader, RawParseUtils.decode(fh.getBuffer()));
196 assertEquals(0, fh.getStartOffset());
197 assertEquals(fh.getBuffer().length, fh.getEndOffset());
198 assertEquals(FileHeader.PatchType.UNIFIED, fh.getPatchType());
199
200 assertEquals(1, fh.getHunks().size());
201
202 HunkHeader hh = fh.getHunks().get(0);
203 assertEquals(1, hh.toEditList().size());
204
205 EditList el = hh.toEditList();
206 assertEquals(1, el.size());
207
208 Edit e = el.get(0);
209 assertEquals(1, e.getBeginA());
210 assertEquals(1, e.getEndA());
211 assertEquals(1, e.getBeginB());
212 assertEquals(3, e.getEndB());
213 assertEquals(Edit.Type.INSERT, e.getType());
214 }
215
216 @Test
217 public void testCreateFileHeader_Binary() throws Exception {
218 ObjectId adId = blob("a\nd\n");
219 ObjectId binId = blob("a\nb\nc\n\0\0\0\0d\n");
220
221 String diffHeader = makeDiffHeader(PATH_A, PATH_B, adId, binId)
222 + "Binary files differ\n";
223
224 DiffEntry ad = DiffEntry.delete(PATH_A, adId);
225 DiffEntry abcd = DiffEntry.add(PATH_B, binId);
226
227 DiffEntry mod = DiffEntry.pair(ChangeType.MODIFY, ad, abcd, 0);
228
229 FileHeader fh = df.toFileHeader(mod);
230
231 assertEquals(diffHeader, RawParseUtils.decode(fh.getBuffer()));
232 assertEquals(FileHeader.PatchType.BINARY, fh.getPatchType());
233
234 assertEquals(1, fh.getHunks().size());
235
236 HunkHeader hh = fh.getHunks().get(0);
237 assertEquals(0, hh.toEditList().size());
238 }
239
240 @Test
241 public void testCreateFileHeader_GitLink() throws Exception {
242 ObjectId aId = blob("a\n");
243 ObjectId bId = blob("b\n");
244
245 String diffHeader = makeDiffHeaderModeChange(PATH_A, PATH_A, aId, bId,
246 GITLINK, REGULAR_FILE);
247
248 DiffEntry ad = DiffEntry.delete(PATH_A, aId);
249 ad.oldMode = FileMode.GITLINK;
250 DiffEntry abcd = DiffEntry.add(PATH_A, bId);
251
252 DiffEntry mod = DiffEntry.pair(ChangeType.MODIFY, ad, abcd, 0);
253
254 FileHeader fh = df.toFileHeader(mod);
255
256 assertEquals(diffHeader, RawParseUtils.decode(fh.getBuffer()));
257
258 assertEquals(1, fh.getHunks().size());
259
260 HunkHeader hh = fh.getHunks().get(0);
261 assertEquals(1, hh.toEditList().size());
262 }
263
264 @Test
265 public void testCreateFileHeader_AddGitLink() throws Exception {
266 ObjectId adId = blob("a\nd\n");
267 DiffEntry ent = DiffEntry.add("FOO", adId);
268 ent.newMode = FileMode.GITLINK;
269 FileHeader fh = df.toFileHeader(ent);
270
271 String diffHeader = "diff --git a/FOO b/FOO\n"
272 + "new file mode " + GITLINK + "\n"
273 + "index "
274 + ObjectId.zeroId().abbreviate(8).name()
275 + ".."
276 + adId.abbreviate(8).name() + "\n"
277 + "--- /dev/null\n"
278 + "+++ b/FOO\n";
279 assertEquals(diffHeader, RawParseUtils.decode(fh.getBuffer()));
280
281 assertEquals(1, fh.getHunks().size());
282 HunkHeader hh = fh.getHunks().get(0);
283
284 EditList el = hh.toEditList();
285 assertEquals(1, el.size());
286
287 Edit e = el.get(0);
288 assertEquals(0, e.getBeginA());
289 assertEquals(0, e.getEndA());
290 assertEquals(0, e.getBeginB());
291 assertEquals(1, e.getEndB());
292 assertEquals(Edit.Type.INSERT, e.getType());
293 }
294
295 @Test
296 public void testCreateFileHeader_DeleteGitLink() throws Exception {
297 ObjectId adId = blob("a\nd\n");
298 DiffEntry ent = DiffEntry.delete("FOO", adId);
299 ent.oldMode = FileMode.GITLINK;
300 FileHeader fh = df.toFileHeader(ent);
301
302 String diffHeader = "diff --git a/FOO b/FOO\n"
303 + "deleted file mode " + GITLINK + "\n"
304 + "index "
305 + adId.abbreviate(8).name()
306 + ".."
307 + ObjectId.zeroId().abbreviate(8).name() + "\n"
308 + "--- a/FOO\n"
309 + "+++ /dev/null\n";
310 assertEquals(diffHeader, RawParseUtils.decode(fh.getBuffer()));
311
312 assertEquals(1, fh.getHunks().size());
313 HunkHeader hh = fh.getHunks().get(0);
314
315 EditList el = hh.toEditList();
316 assertEquals(1, el.size());
317
318 Edit e = el.get(0);
319 assertEquals(0, e.getBeginA());
320 assertEquals(1, e.getEndA());
321 assertEquals(0, e.getBeginB());
322 assertEquals(0, e.getEndB());
323 assertEquals(Edit.Type.DELETE, e.getType());
324 }
325
326 @Test
327 public void testCreateFileHeaderWithoutIndexLine() throws Exception {
328 DiffEntry m = DiffEntry.modify(PATH_A);
329 m.oldMode = FileMode.REGULAR_FILE;
330 m.newMode = FileMode.EXECUTABLE_FILE;
331
332 FileHeader fh = df.toFileHeader(m);
333 String expected = DIFF + "a/src/a b/src/a\n" +
334 "old mode 100644\n" +
335 "new mode 100755\n";
336 assertEquals(expected, fh.getScriptText());
337 }
338
339 @Test
340 public void testCreateFileHeaderForRenameWithoutContentChange() throws Exception {
341 DiffEntry a = DiffEntry.delete(PATH_A, ObjectId.zeroId());
342 DiffEntry b = DiffEntry.add(PATH_B, ObjectId.zeroId());
343 DiffEntry m = DiffEntry.pair(ChangeType.RENAME, a, b, 100);
344 m.oldId = null;
345 m.newId = null;
346
347 FileHeader fh = df.toFileHeader(m);
348 String expected = DIFF + "a/src/a b/src/b\n" +
349 "similarity index 100%\n" +
350 "rename from src/a\n" +
351 "rename to src/b\n";
352 assertEquals(expected, fh.getScriptText());
353 }
354
355 @Test
356 public void testCreateFileHeaderForRenameModeChange()
357 throws Exception {
358 DiffEntry a = DiffEntry.delete(PATH_A, ObjectId.zeroId());
359 DiffEntry b = DiffEntry.add(PATH_B, ObjectId.zeroId());
360 b.oldMode = FileMode.REGULAR_FILE;
361 b.newMode = FileMode.EXECUTABLE_FILE;
362 DiffEntry m = DiffEntry.pair(ChangeType.RENAME, a, b, 100);
363 m.oldId = null;
364 m.newId = null;
365
366 FileHeader fh = df.toFileHeader(m);
367
368 String expected = DIFF + "a/src/a b/src/b\n" +
369 "old mode 100644\n" +
370 "new mode 100755\n" +
371 "similarity index 100%\n" +
372 "rename from src/a\n" +
373 "rename to src/b\n";
374
375 assertEquals(expected, fh.getScriptText());
376 }
377
378 @Test
379 public void testDiff() throws Exception {
380 write(new File(db.getDirectory().getParent(), "test.txt"), "test");
381 File folder = new File(db.getDirectory().getParent(), "folder");
382 FileUtils.mkdir(folder);
383 write(new File(folder, "folder.txt"), "folder");
384 try (Git git = new Git(db);
385 ByteArrayOutputStream os = new ByteArrayOutputStream();
386 DiffFormatter dfmt = new DiffFormatter(new BufferedOutputStream(os))) {
387 git.add().addFilepattern(".").call();
388 git.commit().setMessage("Initial commit").call();
389 write(new File(folder, "folder.txt"), "folder change");
390 dfmt.setRepository(db);
391 dfmt.setPathFilter(PathFilter.create("folder"));
392 DirCacheIterator oldTree = new DirCacheIterator(db.readDirCache());
393 FileTreeIterator newTree = new FileTreeIterator(db);
394
395 dfmt.format(oldTree, newTree);
396 dfmt.flush();
397
398 String actual = os.toString("UTF-8");
399 String expected =
400 "diff --git a/folder/folder.txt b/folder/folder.txt\n"
401 + "index 0119635..95c4c65 100644\n"
402 + "--- a/folder/folder.txt\n" + "+++ b/folder/folder.txt\n"
403 + "@@ -1 +1 @@\n" + "-folder\n"
404 + "\\ No newline at end of file\n" + "+folder change\n"
405 + "\\ No newline at end of file\n";
406
407 assertEquals(expected, actual);
408 }
409 }
410
411 @Test
412 public void testDiffRootNullToTree() throws Exception {
413 write(new File(db.getDirectory().getParent(), "test.txt"), "test");
414 File folder = new File(db.getDirectory().getParent(), "folder");
415 FileUtils.mkdir(folder);
416 write(new File(folder, "folder.txt"), "folder");
417 try (Git git = new Git(db);
418 ByteArrayOutputStream os = new ByteArrayOutputStream();
419 DiffFormatter dfmt = new DiffFormatter(new BufferedOutputStream(os))) {
420 git.add().addFilepattern(".").call();
421 RevCommit commit = git.commit().setMessage("Initial commit").call();
422 write(new File(folder, "folder.txt"), "folder change");
423
424 dfmt.setRepository(db);
425 dfmt.setPathFilter(PathFilter.create("folder"));
426 dfmt.format(null, commit.getTree().getId());
427 dfmt.flush();
428
429 String actual = os.toString("UTF-8");
430 String expected = "diff --git a/folder/folder.txt b/folder/folder.txt\n"
431 + "new file mode 100644\n"
432 + "index 0000000..0119635\n"
433 + "--- /dev/null\n"
434 + "+++ b/folder/folder.txt\n"
435 + "@@ -0,0 +1 @@\n"
436 + "+folder\n"
437 + "\\ No newline at end of file\n";
438
439 assertEquals(expected, actual);
440 }
441 }
442
443 @Test
444 public void testDiffRootTreeToNull() throws Exception {
445 write(new File(db.getDirectory().getParent(), "test.txt"), "test");
446 File folder = new File(db.getDirectory().getParent(), "folder");
447 FileUtils.mkdir(folder);
448 write(new File(folder, "folder.txt"), "folder");
449 try (Git git = new Git(db);
450 ByteArrayOutputStream os = new ByteArrayOutputStream();
451 DiffFormatter dfmt = new DiffFormatter(new BufferedOutputStream(os));) {
452 git.add().addFilepattern(".").call();
453 RevCommit commit = git.commit().setMessage("Initial commit").call();
454 write(new File(folder, "folder.txt"), "folder change");
455
456 dfmt.setRepository(db);
457 dfmt.setPathFilter(PathFilter.create("folder"));
458 dfmt.format(commit.getTree().getId(), null);
459 dfmt.flush();
460
461 String actual = os.toString("UTF-8");
462 String expected = "diff --git a/folder/folder.txt b/folder/folder.txt\n"
463 + "deleted file mode 100644\n"
464 + "index 0119635..0000000\n"
465 + "--- a/folder/folder.txt\n"
466 + "+++ /dev/null\n"
467 + "@@ -1 +0,0 @@\n"
468 + "-folder\n"
469 + "\\ No newline at end of file\n";
470
471 assertEquals(expected, actual);
472 }
473 }
474
475 @Test
476 public void testDiffNullToNull() throws Exception {
477 try (ByteArrayOutputStream os = new ByteArrayOutputStream();
478 DiffFormatter dfmt = new DiffFormatter(new BufferedOutputStream(os))) {
479 dfmt.setRepository(db);
480 dfmt.format((AnyObjectId) null, null);
481 dfmt.flush();
482
483 String actual = os.toString("UTF-8");
484 String expected = "";
485
486 assertEquals(expected, actual);
487 }
488 }
489
490 @Test
491 public void testDiffAutoCrlfSmallFile() throws Exception {
492 String content = "01234\r\n01234\r\n01234\r\n";
493 String expectedDiff = "diff --git a/test.txt b/test.txt\n"
494 + "index fe25983..a44a032 100644\n"
495 + "--- a/test.txt\n"
496 + "+++ b/test.txt\n"
497 + "@@ -1,3 +1,4 @@\n"
498 + " 01234\n"
499 + "+ABCD\n"
500 + " 01234\n"
501 + " 01234\n";
502 doAutoCrLfTest(content, expectedDiff);
503 }
504
505 @Test
506 public void testDiffAutoCrlfMediumFile() throws Exception {
507 String content = mediumCrLfString();
508 String expectedDiff = "diff --git a/test.txt b/test.txt\n"
509 + "index 215c502..c10f08c 100644\n"
510 + "--- a/test.txt\n"
511 + "+++ b/test.txt\n"
512 + "@@ -1,4 +1,5 @@\n"
513 + " 01234567\n"
514 + "+ABCD\n"
515 + " 01234567\n"
516 + " 01234567\n"
517 + " 01234567\n";
518 doAutoCrLfTest(content, expectedDiff);
519 }
520
521 @Test
522 public void testDiffAutoCrlfLargeFile() throws Exception {
523 String content = largeCrLfString();
524 String expectedDiff = "diff --git a/test.txt b/test.txt\n"
525 + "index 7014942..c0487a7 100644\n"
526 + "--- a/test.txt\n"
527 + "+++ b/test.txt\n"
528 + "@@ -1,4 +1,5 @@\n"
529 + " 012345678901234567890123456789012345678901234567\n"
530 + "+ABCD\n"
531 + " 012345678901234567890123456789012345678901234567\n"
532 + " 012345678901234567890123456789012345678901234567\n"
533 + " 012345678901234567890123456789012345678901234567\n";
534 doAutoCrLfTest(content, expectedDiff);
535 }
536
537 private void doAutoCrLfTest(String content, String expectedDiff)
538 throws Exception {
539 FileBasedConfig config = db.getConfig();
540 config.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
541 ConfigConstants.CONFIG_KEY_AUTOCRLF, "true");
542 config.save();
543 commitFile("test.txt", content, "master");
544
545 int i = content.indexOf('\n');
546 content = content.substring(0, i + 1) + "ABCD\r\n"
547 + content.substring(i + 1);
548 writeTrashFile("test.txt", content);
549
550 try (ByteArrayOutputStream os = new ByteArrayOutputStream();
551 DiffFormatter dfmt = new DiffFormatter(
552 new BufferedOutputStream(os))) {
553 dfmt.setRepository(db);
554 dfmt.format(new DirCacheIterator(db.readDirCache()),
555 new FileTreeIterator(db));
556 dfmt.flush();
557
558 String actual = os.toString("UTF-8");
559
560 assertEquals(expectedDiff, actual);
561 }
562 }
563
564 private static String largeCrLfString() {
565 String line = "012345678901234567890123456789012345678901234567\r\n";
566 StringBuilder builder = new StringBuilder(
567 2 * RawText.FIRST_FEW_BYTES);
568 while (builder.length() < 2 * RawText.FIRST_FEW_BYTES) {
569 builder.append(line);
570 }
571 return builder.toString();
572 }
573
574 private static String mediumCrLfString() {
575
576
577 String line = "01234567\r\n";
578 StringBuilder builder = new StringBuilder(
579 RawText.FIRST_FEW_BYTES + line.length());
580 while (builder.length() <= RawText.FIRST_FEW_BYTES) {
581 builder.append(line);
582 }
583 return builder.toString();
584 }
585
586 private static String makeDiffHeader(String pathA, String pathB,
587 ObjectId aId,
588 ObjectId bId) {
589 String a = aId.abbreviate(8).name();
590 String b = bId.abbreviate(8).name();
591 return DIFF + "a/" + pathA + " " + "b/" + pathB + "\n" +
592 "index " + a + ".." + b + " " + REGULAR_FILE + "\n" +
593 "--- a/" + pathA + "\n" +
594 "+++ b/" + pathB + "\n";
595 }
596
597 private static String makeDiffHeaderModeChange(String pathA, String pathB,
598 ObjectId aId, ObjectId bId, String modeA, String modeB) {
599 String a = aId.abbreviate(8).name();
600 String b = bId.abbreviate(8).name();
601 return DIFF + "a/" + pathA + " " + "b/" + pathB + "\n" +
602 "old mode " + modeA + "\n" +
603 "new mode " + modeB + "\n" +
604 "index " + a + ".." + b + "\n" +
605 "--- a/" + pathA + "\n" +
606 "+++ b/" + pathB + "\n";
607 }
608
609 private ObjectId blob(String content) throws Exception {
610 return testDb.blob(content).copy();
611 }
612 }