View Javadoc
1   /*
2    * Copyright (C) 2010, 2021 Google Inc. 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  
11  package org.eclipse.jgit.diff;
12  
13  import static org.junit.Assert.assertEquals;
14  import static org.junit.Assert.assertNotNull;
15  import static org.junit.Assert.assertNull;
16  import static org.junit.Assert.assertTrue;
17  
18  import java.io.BufferedOutputStream;
19  import java.io.ByteArrayOutputStream;
20  import java.io.File;
21  import java.util.ArrayList;
22  import java.util.List;
23  
24  import org.eclipse.jgit.api.Git;
25  import org.eclipse.jgit.api.Status;
26  import org.eclipse.jgit.diff.DiffEntry.ChangeType;
27  import org.eclipse.jgit.dircache.DirCacheIterator;
28  import org.eclipse.jgit.junit.RepositoryTestCase;
29  import org.eclipse.jgit.junit.TestRepository;
30  import org.eclipse.jgit.lib.AnyObjectId;
31  import org.eclipse.jgit.lib.ConfigConstants;
32  import org.eclipse.jgit.lib.FileMode;
33  import org.eclipse.jgit.lib.ObjectId;
34  import org.eclipse.jgit.lib.Repository;
35  import org.eclipse.jgit.patch.FileHeader;
36  import org.eclipse.jgit.patch.HunkHeader;
37  import org.eclipse.jgit.revwalk.RevCommit;
38  import org.eclipse.jgit.storage.file.FileBasedConfig;
39  import org.eclipse.jgit.treewalk.CanonicalTreeParser;
40  import org.eclipse.jgit.treewalk.FileTreeIterator;
41  import org.eclipse.jgit.treewalk.filter.OrTreeFilter;
42  import org.eclipse.jgit.treewalk.filter.PathFilter;
43  import org.eclipse.jgit.treewalk.filter.PathSuffixFilter;
44  import org.eclipse.jgit.treewalk.filter.TreeFilter;
45  import org.eclipse.jgit.util.FileUtils;
46  import org.eclipse.jgit.util.RawParseUtils;
47  import org.eclipse.jgit.util.io.DisabledOutputStream;
48  import org.junit.After;
49  import org.junit.Before;
50  import org.junit.Test;
51  
52  public class DiffFormatterTest extends RepositoryTestCase {
53  	private static final String DIFF = "diff --git ";
54  
55  	private static final String REGULAR_FILE = "100644";
56  
57  	private static final String GITLINK = "160000";
58  
59  	private static final String PATH_A = "src/a";
60  
61  	private static final String PATH_B = "src/b";
62  
63  	private DiffFormatter df;
64  
65  	private TestRepository<Repository> testDb;
66  
67  	@Override
68  	@Before
69  	public void setUp() throws Exception {
70  		super.setUp();
71  		testDb = new TestRepository<>(db);
72  		df = new DiffFormatter(DisabledOutputStream.INSTANCE);
73  		df.setRepository(db);
74  		df.setAbbreviationLength(8);
75  	}
76  
77  	@Override
78  	@After
79  	public void tearDown() throws Exception {
80  		if (df != null) {
81  			df.close();
82  		}
83  		super.tearDown();
84  	}
85  
86  	@Test
87  	public void testDefaultRenameDetectorSettings() throws Exception {
88  		RenameDetector rd = df.getRenameDetector();
89  		assertNull(rd);
90  		df.setDetectRenames(true);
91  		rd = df.getRenameDetector();
92  		assertNotNull(rd);
93  		assertEquals(400, rd.getRenameLimit());
94  		assertEquals(60, rd.getRenameScore());
95  	}
96  
97  	@Test
98  	public void testCreateFileHeader_Add() throws Exception {
99  		ObjectId adId = blob("a\nd\n");
100 		DiffEntry ent = DiffEntry.add("FOO", adId);
101 		FileHeader fh = df.toFileHeader(ent);
102 
103 		String diffHeader = "diff --git a/FOO b/FOO\n" //
104 				+ "new file mode " + REGULAR_FILE + "\n"
105 				+ "index "
106 				+ ObjectId.zeroId().abbreviate(8).name()
107 				+ ".."
108 				+ adId.abbreviate(8).name() + "\n" //
109 				+ "--- /dev/null\n"//
110 				+ "+++ b/FOO\n";
111 		assertEquals(diffHeader, RawParseUtils.decode(fh.getBuffer()));
112 
113 		assertEquals(0, fh.getStartOffset());
114 		assertEquals(fh.getBuffer().length, fh.getEndOffset());
115 		assertEquals(FileHeader.PatchType.UNIFIED, fh.getPatchType());
116 
117 		assertEquals(1, fh.getHunks().size());
118 
119 		HunkHeader hh = fh.getHunks().get(0);
120 		assertEquals(1, hh.toEditList().size());
121 
122 		EditList el = hh.toEditList();
123 		assertEquals(1, el.size());
124 
125 		Edit e = el.get(0);
126 		assertEquals(0, e.getBeginA());
127 		assertEquals(0, e.getEndA());
128 		assertEquals(0, e.getBeginB());
129 		assertEquals(2, e.getEndB());
130 		assertEquals(Edit.Type.INSERT, e.getType());
131 	}
132 
133 	@Test
134 	public void testCreateFileHeader_Delete() throws Exception {
135 		ObjectId adId = blob("a\nd\n");
136 		DiffEntry ent = DiffEntry.delete("FOO", adId);
137 		FileHeader fh = df.toFileHeader(ent);
138 
139 		String diffHeader = "diff --git a/FOO b/FOO\n" //
140 				+ "deleted file mode " + REGULAR_FILE + "\n"
141 				+ "index "
142 				+ adId.abbreviate(8).name()
143 				+ ".."
144 				+ ObjectId.zeroId().abbreviate(8).name() + "\n" //
145 				+ "--- a/FOO\n"//
146 				+ "+++ /dev/null\n";
147 		assertEquals(diffHeader, RawParseUtils.decode(fh.getBuffer()));
148 
149 		assertEquals(0, fh.getStartOffset());
150 		assertEquals(fh.getBuffer().length, fh.getEndOffset());
151 		assertEquals(FileHeader.PatchType.UNIFIED, fh.getPatchType());
152 
153 		assertEquals(1, fh.getHunks().size());
154 
155 		HunkHeader hh = fh.getHunks().get(0);
156 		assertEquals(1, hh.toEditList().size());
157 
158 		EditList el = hh.toEditList();
159 		assertEquals(1, el.size());
160 
161 		Edit e = el.get(0);
162 		assertEquals(0, e.getBeginA());
163 		assertEquals(2, e.getEndA());
164 		assertEquals(0, e.getBeginB());
165 		assertEquals(0, e.getEndB());
166 		assertEquals(Edit.Type.DELETE, e.getType());
167 	}
168 
169 	@Test
170 	public void testCreateFileHeader_Modify() throws Exception {
171 		ObjectId adId = blob("a\nd\n");
172 		ObjectId abcdId = blob("a\nb\nc\nd\n");
173 
174 		String diffHeader = makeDiffHeader(PATH_A, PATH_A, adId, abcdId);
175 
176 		DiffEntry ad = DiffEntry.delete(PATH_A, adId);
177 		DiffEntry abcd = DiffEntry.add(PATH_A, abcdId);
178 
179 		DiffEntry mod = DiffEntry.pair(ChangeType.MODIFY, ad, abcd, 0);
180 
181 		FileHeader fh = df.toFileHeader(mod);
182 
183 		assertEquals(diffHeader, RawParseUtils.decode(fh.getBuffer()));
184 		assertEquals(0, fh.getStartOffset());
185 		assertEquals(fh.getBuffer().length, fh.getEndOffset());
186 		assertEquals(FileHeader.PatchType.UNIFIED, fh.getPatchType());
187 
188 		assertEquals(1, fh.getHunks().size());
189 
190 		HunkHeader hh = fh.getHunks().get(0);
191 		assertEquals(1, hh.toEditList().size());
192 
193 		EditList el = hh.toEditList();
194 		assertEquals(1, el.size());
195 
196 		Edit e = el.get(0);
197 		assertEquals(1, e.getBeginA());
198 		assertEquals(1, e.getEndA());
199 		assertEquals(1, e.getBeginB());
200 		assertEquals(3, e.getEndB());
201 		assertEquals(Edit.Type.INSERT, e.getType());
202 	}
203 
204 	@Test
205 	public void testCreateFileHeader_Binary() throws Exception {
206 		ObjectId adId = blob("a\nd\n");
207 		ObjectId binId = blob("a\nb\nc\n\0\0\0\0d\n");
208 
209 		String diffHeader = makeDiffHeader(PATH_A, PATH_B, adId, binId)
210 				+ "Binary files differ\n";
211 
212 		DiffEntry ad = DiffEntry.delete(PATH_A, adId);
213 		DiffEntry abcd = DiffEntry.add(PATH_B, binId);
214 
215 		DiffEntry mod = DiffEntry.pair(ChangeType.MODIFY, ad, abcd, 0);
216 
217 		FileHeader fh = df.toFileHeader(mod);
218 
219 		assertEquals(diffHeader, RawParseUtils.decode(fh.getBuffer()));
220 		assertEquals(FileHeader.PatchType.BINARY, fh.getPatchType());
221 
222 		assertEquals(1, fh.getHunks().size());
223 
224 		HunkHeader hh = fh.getHunks().get(0);
225 		assertEquals(0, hh.toEditList().size());
226 	}
227 
228 	@Test
229 	public void testCreateFileHeader_GitLink() throws Exception {
230 		ObjectId aId = blob("a\n");
231 		ObjectId bId = blob("b\n");
232 
233 		String diffHeader = makeDiffHeaderModeChange(PATH_A, PATH_A, aId, bId,
234 				GITLINK, REGULAR_FILE);
235 
236 		DiffEntry ad = DiffEntry.delete(PATH_A, aId);
237 		ad.oldMode = FileMode.GITLINK;
238 		DiffEntry abcd = DiffEntry.add(PATH_A, bId);
239 
240 		DiffEntry mod = DiffEntry.pair(ChangeType.MODIFY, ad, abcd, 0);
241 
242 		FileHeader fh = df.toFileHeader(mod);
243 
244 		assertEquals(diffHeader, RawParseUtils.decode(fh.getBuffer()));
245 
246 		assertEquals(1, fh.getHunks().size());
247 
248 		HunkHeader hh = fh.getHunks().get(0);
249 		assertEquals(1, hh.toEditList().size());
250 	}
251 
252 	@Test
253 	public void testCreateFileHeader_AddGitLink() throws Exception {
254 		ObjectId adId = blob("a\nd\n");
255 		DiffEntry ent = DiffEntry.add("FOO", adId);
256 		ent.newMode = FileMode.GITLINK;
257 		FileHeader fh = df.toFileHeader(ent);
258 
259 		String diffHeader = "diff --git a/FOO b/FOO\n" //
260 				+ "new file mode " + GITLINK + "\n"
261 				+ "index "
262 				+ ObjectId.zeroId().abbreviate(8).name()
263 				+ ".."
264 				+ adId.abbreviate(8).name() + "\n" //
265 				+ "--- /dev/null\n"//
266 				+ "+++ b/FOO\n";
267 		assertEquals(diffHeader, RawParseUtils.decode(fh.getBuffer()));
268 
269 		assertEquals(1, fh.getHunks().size());
270 		HunkHeader hh = fh.getHunks().get(0);
271 
272 		EditList el = hh.toEditList();
273 		assertEquals(1, el.size());
274 
275 		Edit e = el.get(0);
276 		assertEquals(0, e.getBeginA());
277 		assertEquals(0, e.getEndA());
278 		assertEquals(0, e.getBeginB());
279 		assertEquals(1, e.getEndB());
280 		assertEquals(Edit.Type.INSERT, e.getType());
281 	}
282 
283 	@Test
284 	public void testCreateFileHeader_DeleteGitLink() throws Exception {
285 		ObjectId adId = blob("a\nd\n");
286 		DiffEntry ent = DiffEntry.delete("FOO", adId);
287 		ent.oldMode = FileMode.GITLINK;
288 		FileHeader fh = df.toFileHeader(ent);
289 
290 		String diffHeader = "diff --git a/FOO b/FOO\n" //
291 				+ "deleted file mode " + GITLINK + "\n"
292 				+ "index "
293 				+ adId.abbreviate(8).name()
294 				+ ".."
295 				+ ObjectId.zeroId().abbreviate(8).name() + "\n" //
296 				+ "--- a/FOO\n"//
297 				+ "+++ /dev/null\n";
298 		assertEquals(diffHeader, RawParseUtils.decode(fh.getBuffer()));
299 
300 		assertEquals(1, fh.getHunks().size());
301 		HunkHeader hh = fh.getHunks().get(0);
302 
303 		EditList el = hh.toEditList();
304 		assertEquals(1, el.size());
305 
306 		Edit e = el.get(0);
307 		assertEquals(0, e.getBeginA());
308 		assertEquals(1, e.getEndA());
309 		assertEquals(0, e.getBeginB());
310 		assertEquals(0, e.getEndB());
311 		assertEquals(Edit.Type.DELETE, e.getType());
312 	}
313 
314 	@Test
315 	public void testCreateFileHeaderWithoutIndexLine() throws Exception {
316 		DiffEntry m = DiffEntry.modify(PATH_A);
317 		m.oldMode = FileMode.REGULAR_FILE;
318 		m.newMode = FileMode.EXECUTABLE_FILE;
319 
320 		FileHeader fh = df.toFileHeader(m);
321 		String expected = DIFF + "a/src/a b/src/a\n" + //
322 				"old mode 100644\n" + //
323 				"new mode 100755\n";
324 		assertEquals(expected, fh.getScriptText());
325 	}
326 
327 	@Test
328 	public void testCreateFileHeaderForRenameWithoutContentChange() throws Exception {
329 		DiffEntry a = DiffEntry.delete(PATH_A, ObjectId.zeroId());
330 		DiffEntry b = DiffEntry.add(PATH_B, ObjectId.zeroId());
331 		DiffEntry m = DiffEntry.pair(ChangeType.RENAME, a, b, 100);
332 		m.oldId = null;
333 		m.newId = null;
334 
335 		FileHeader fh = df.toFileHeader(m);
336 		String expected = DIFF + "a/src/a b/src/b\n" + //
337 				"similarity index 100%\n" + //
338 				"rename from src/a\n" + //
339 				"rename to src/b\n";
340 		assertEquals(expected, fh.getScriptText());
341 	}
342 
343 	@Test
344 	public void testCreateFileHeaderForRenameModeChange()
345 			throws Exception {
346 		DiffEntry a = DiffEntry.delete(PATH_A, ObjectId.zeroId());
347 		DiffEntry b = DiffEntry.add(PATH_B, ObjectId.zeroId());
348 		b.oldMode = FileMode.REGULAR_FILE;
349 		b.newMode = FileMode.EXECUTABLE_FILE;
350 		DiffEntry m = DiffEntry.pair(ChangeType.RENAME, a, b, 100);
351 		m.oldId = null;
352 		m.newId = null;
353 
354 		FileHeader fh = df.toFileHeader(m);
355 		//@formatter:off
356 		String expected = DIFF + "a/src/a b/src/b\n" +
357 				"old mode 100644\n" +
358 				"new mode 100755\n" +
359 				"similarity index 100%\n" +
360 				"rename from src/a\n" +
361 				"rename to src/b\n";
362 		//@formatter:on
363 		assertEquals(expected, fh.getScriptText());
364 	}
365 
366 	@Test
367 	public void testDiff() throws Exception {
368 		write(new File(db.getDirectory().getParent(), "test.txt"), "test");
369 		File folder = new File(db.getDirectory().getParent(), "folder");
370 		FileUtils.mkdir(folder);
371 		write(new File(folder, "folder.txt"), "folder");
372 		try (Git git = new Git(db);
373 				ByteArrayOutputStream os = new ByteArrayOutputStream();
374 				DiffFormatter dfmt = new DiffFormatter(new BufferedOutputStream(os))) {
375 			git.add().addFilepattern(".").call();
376 			git.commit().setMessage("Initial commit").call();
377 			write(new File(folder, "folder.txt"), "folder change");
378 			dfmt.setRepository(db);
379 			dfmt.setPathFilter(PathFilter.create("folder"));
380 			DirCacheIterator oldTree = new DirCacheIterator(db.readDirCache());
381 			FileTreeIterator newTree = new FileTreeIterator(db);
382 
383 			dfmt.format(oldTree, newTree);
384 			dfmt.flush();
385 
386 			String actual = os.toString("UTF-8");
387 			String expected =
388 					"diff --git a/folder/folder.txt b/folder/folder.txt\n"
389 					+ "index 0119635..95c4c65 100644\n"
390 					+ "--- a/folder/folder.txt\n" + "+++ b/folder/folder.txt\n"
391 					+ "@@ -1 +1 @@\n" + "-folder\n"
392 					+ "\\ No newline at end of file\n" + "+folder change\n"
393 					+ "\\ No newline at end of file\n";
394 
395 			assertEquals(expected, actual);
396 		}
397 	}
398 
399 	@Test
400 	public void testDiffRootNullToTree() throws Exception {
401 		write(new File(db.getDirectory().getParent(), "test.txt"), "test");
402 		File folder = new File(db.getDirectory().getParent(), "folder");
403 		FileUtils.mkdir(folder);
404 		write(new File(folder, "folder.txt"), "folder");
405 		try (Git git = new Git(db);
406 				ByteArrayOutputStream os = new ByteArrayOutputStream();
407 				DiffFormatter dfmt = new DiffFormatter(new BufferedOutputStream(os))) {
408 			git.add().addFilepattern(".").call();
409 			RevCommit commit = git.commit().setMessage("Initial commit").call();
410 			write(new File(folder, "folder.txt"), "folder change");
411 
412 			dfmt.setRepository(db);
413 			dfmt.setPathFilter(PathFilter.create("folder"));
414 			dfmt.format(null, commit.getTree().getId());
415 			dfmt.flush();
416 
417 			String actual = os.toString("UTF-8");
418 			String expected = "diff --git a/folder/folder.txt b/folder/folder.txt\n"
419 					+ "new file mode 100644\n"
420 					+ "index 0000000..0119635\n"
421 					+ "--- /dev/null\n"
422 					+ "+++ b/folder/folder.txt\n"
423 					+ "@@ -0,0 +1 @@\n"
424 					+ "+folder\n"
425 					+ "\\ No newline at end of file\n";
426 
427 			assertEquals(expected, actual);
428 		}
429 	}
430 
431 	@Test
432 	public void testDiffRootTreeToNull() throws Exception {
433 		write(new File(db.getDirectory().getParent(), "test.txt"), "test");
434 		File folder = new File(db.getDirectory().getParent(), "folder");
435 		FileUtils.mkdir(folder);
436 		write(new File(folder, "folder.txt"), "folder");
437 		try (Git git = new Git(db);
438 				ByteArrayOutputStream os = new ByteArrayOutputStream();
439 				DiffFormatter dfmt = new DiffFormatter(new BufferedOutputStream(os));) {
440 			git.add().addFilepattern(".").call();
441 			RevCommit commit = git.commit().setMessage("Initial commit").call();
442 			write(new File(folder, "folder.txt"), "folder change");
443 
444 			dfmt.setRepository(db);
445 			dfmt.setPathFilter(PathFilter.create("folder"));
446 			dfmt.format(commit.getTree().getId(), null);
447 			dfmt.flush();
448 
449 			String actual = os.toString("UTF-8");
450 			String expected = "diff --git a/folder/folder.txt b/folder/folder.txt\n"
451 					+ "deleted file mode 100644\n"
452 					+ "index 0119635..0000000\n"
453 					+ "--- a/folder/folder.txt\n"
454 					+ "+++ /dev/null\n"
455 					+ "@@ -1 +0,0 @@\n"
456 					+ "-folder\n"
457 					+ "\\ No newline at end of file\n";
458 
459 			assertEquals(expected, actual);
460 		}
461 	}
462 
463 	@Test
464 	public void testDiffNullToNull() throws Exception {
465 		try (ByteArrayOutputStream os = new ByteArrayOutputStream();
466 				DiffFormatter dfmt = new DiffFormatter(new BufferedOutputStream(os))) {
467 			dfmt.setRepository(db);
468 			dfmt.format((AnyObjectId) null, null);
469 			dfmt.flush();
470 
471 			String actual = os.toString("UTF-8");
472 			String expected = "";
473 
474 			assertEquals(expected, actual);
475 		}
476 	}
477 
478 	@Test
479 	public void testTrackedFileInIgnoredFolderUnchanged()
480 			throws Exception {
481 		commitFile("empty/empty/foo", "", "master");
482 		commitFile(".gitignore", "empty/*", "master");
483 		try (Git git = new Git(db)) {
484 			Status status = git.status().call();
485 			assertTrue(status.isClean());
486 		}
487 		try (ByteArrayOutputStream os = new ByteArrayOutputStream();
488 				DiffFormatter dfmt = new DiffFormatter(os)) {
489 			dfmt.setRepository(db);
490 			dfmt.format(new DirCacheIterator(db.readDirCache()),
491 					new FileTreeIterator(db));
492 			dfmt.flush();
493 
494 			String actual = os.toString("UTF-8");
495 
496 			assertEquals("", actual);
497 		}
498 	}
499 
500 	@Test
501 	public void testFilter() throws Exception {
502 		RevCommit parent;
503 		RevCommit head;
504 		try (Git git = new Git(db)) {
505 			writeTrashFile("foo.txt", "foo\n");
506 			writeTrashFile("src/some.txt", "some\n");
507 			writeTrashFile("src/image.png", "image\n");
508 			writeTrashFile("src/test.pdf", "test\n");
509 			writeTrashFile("src/xyz.txt", "xyz\n");
510 			git.add().addFilepattern(".").call();
511 			parent = git.commit().setMessage("initial").call();
512 			writeTrashFile("foo.txt", "FOO\n");
513 			writeTrashFile("src/some.txt", "SOME\n");
514 			writeTrashFile("src/image.png", "IMAGE\n");
515 			writeTrashFile("src/test.pdf", "TEST\n");
516 			writeTrashFile("src/xyz.txt", "XYZ\n");
517 			git.add().addFilepattern(".").call();
518 			head = git.commit().setMessage("second").call();
519 		}
520 		try (ByteArrayOutputStream os = new ByteArrayOutputStream();
521 				DiffFormatter dfmt = new DiffFormatter(os)) {
522 			dfmt.setRepository(db);
523 			List<TreeFilter> skip = new ArrayList<>();
524 			skip.add(PathSuffixFilter.create(".png"));
525 			skip.add(PathSuffixFilter.create(".pdf"));
526 			dfmt.setPathFilter(OrTreeFilter.create(skip).negate());
527 			dfmt.format(
528 					new CanonicalTreeParser(null, db.newObjectReader(),
529 							parent.getTree()),
530 					new CanonicalTreeParser(null, db.newObjectReader(),
531 							head.getTree()));
532 			dfmt.flush();
533 
534 			String actual = os.toString("UTF-8");
535 
536 			String expected = "diff --git a/foo.txt b/foo.txt\n"
537 					+ "index 257cc56..b7d6715 100644\n"
538 					+ "--- a/foo.txt\n"
539 					+ "+++ b/foo.txt\n"
540 					+ "@@ -1 +1 @@\n"
541 					+ "-foo\n"
542 					+ "+FOO\n"
543 					+ "diff --git a/src/some.txt b/src/some.txt\n"
544 					+ "index 363ef61..76cea5f 100644\n"
545 					+ "--- a/src/some.txt\n"
546 					+ "+++ b/src/some.txt\n"
547 					+ "@@ -1 +1 @@\n"
548 					+ "-some\n"
549 					+ "+SOME\n"
550 					+ "diff --git a/src/xyz.txt b/src/xyz.txt\n"
551 					+ "index cd470e6..d4e3ab0 100644\n"
552 					+ "--- a/src/xyz.txt\n"
553 					+ "+++ b/src/xyz.txt\n"
554 					+ "@@ -1 +1 @@\n"
555 					+ "-xyz\n"
556 					+ "+XYZ\n";
557 
558 			assertEquals(expected, actual);
559 		}
560 	}
561 
562 	@Test
563 	public void testTrackedFileInIgnoredFolderChanged()
564 			throws Exception {
565 		String expectedDiff = "diff --git a/empty/empty/foo b/empty/empty/foo\n"
566 				+ "index e69de29..5ea2ed4 100644\n" //
567 				+ "--- a/empty/empty/foo\n" //
568 				+ "+++ b/empty/empty/foo\n" //
569 				+ "@@ -0,0 +1 @@\n" //
570 				+ "+changed\n";
571 
572 		commitFile("empty/empty/foo", "", "master");
573 		commitFile(".gitignore", "empty/*", "master");
574 		try (Git git = new Git(db)) {
575 			Status status = git.status().call();
576 			assertTrue(status.isClean());
577 		}
578 		try (ByteArrayOutputStream os = new ByteArrayOutputStream();
579 				DiffFormatter dfmt = new DiffFormatter(os)) {
580 			writeTrashFile("empty/empty/foo", "changed\n");
581 			dfmt.setRepository(db);
582 			dfmt.format(new DirCacheIterator(db.readDirCache()),
583 					new FileTreeIterator(db));
584 			dfmt.flush();
585 
586 			String actual = os.toString("UTF-8");
587 
588 			assertEquals(expectedDiff, actual);
589 		}
590 	}
591 
592 	@Test
593 	public void testDiffAutoCrlfSmallFile() throws Exception {
594 		String content = "01234\r\n01234\r\n01234\r\n";
595 		String expectedDiff = "diff --git a/test.txt b/test.txt\n"
596 				+ "index fe25983..a44a032 100644\n" //
597 				+ "--- a/test.txt\n" //
598 				+ "+++ b/test.txt\n" //
599 				+ "@@ -1,3 +1,4 @@\n" //
600 				+ " 01234\n" //
601 				+ "+ABCD\n" //
602 				+ " 01234\n" //
603 				+ " 01234\n";
604 		doAutoCrLfTest(content, expectedDiff);
605 	}
606 
607 	@Test
608 	public void testDiffAutoCrlfMediumFile() throws Exception {
609 		String content = mediumCrLfString();
610 		String expectedDiff = "diff --git a/test.txt b/test.txt\n"
611 				+ "index 6d9ffed..50d7b5a 100644\n" //
612 				+ "--- a/test.txt\n" //
613 				+ "+++ b/test.txt\n" //
614 				+ "@@ -1,4 +1,5 @@\n" //
615 				+ " 01234567\n" //
616 				+ "+ABCD\n" //
617 				+ " 01234567\n" //
618 				+ " 01234567\n" //
619 				+ " 01234567\n";
620 		doAutoCrLfTest(content, expectedDiff);
621 	}
622 
623 	@Test
624 	public void testDiffAutoCrlfLargeFile() throws Exception {
625 		String content = largeCrLfString();
626 		String expectedDiff = "diff --git a/test.txt b/test.txt\n"
627 				+ "index d6399a1..de26ce5 100644\n" //
628 				+ "--- a/test.txt\n" //
629 				+ "+++ b/test.txt\n" //
630 				+ "@@ -1,4 +1,5 @@\n"
631 				+ " 012345678901234567890123456789012345678901234567\n"
632 				+ "+ABCD\n"
633 				+ " 012345678901234567890123456789012345678901234567\n"
634 				+ " 012345678901234567890123456789012345678901234567\n"
635 				+ " 012345678901234567890123456789012345678901234567\n";
636 		doAutoCrLfTest(content, expectedDiff);
637 	}
638 
639 	private void doAutoCrLfTest(String content, String expectedDiff)
640 			throws Exception {
641 		FileBasedConfig config = db.getConfig();
642 		config.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
643 				ConfigConstants.CONFIG_KEY_AUTOCRLF, "true");
644 		config.save();
645 		commitFile("test.txt", content, "master");
646 		// Insert a line into content
647 		int i = content.indexOf('\n');
648 		content = content.substring(0, i + 1) + "ABCD\r\n"
649 				+ content.substring(i + 1);
650 		writeTrashFile("test.txt", content);
651 		// Create the patch
652 		try (ByteArrayOutputStream os = new ByteArrayOutputStream();
653 				DiffFormatter dfmt = new DiffFormatter(
654 						new BufferedOutputStream(os))) {
655 			dfmt.setRepository(db);
656 			dfmt.format(new DirCacheIterator(db.readDirCache()),
657 					new FileTreeIterator(db));
658 			dfmt.flush();
659 
660 			String actual = os.toString("UTF-8");
661 
662 			assertEquals(expectedDiff, actual);
663 		}
664 	}
665 
666 	private static String largeCrLfString() {
667 		String line = "012345678901234567890123456789012345678901234567\r\n";
668 		int bufferSize = RawText.getBufferSize();
669 		StringBuilder builder = new StringBuilder(2 * bufferSize);
670 		while (builder.length() < 2 * bufferSize) {
671 			builder.append(line);
672 		}
673 		return builder.toString();
674 	}
675 
676 	private static String mediumCrLfString() {
677 		// Create a CR-LF string longer than RawText.FIRST_FEW_BYTES whose
678 		// canonical representation is shorter than RawText.FIRST_FEW_BYTES.
679 		String line = "01234567\r\n"; // 10 characters
680 		int bufferSize = RawText.getBufferSize();
681 		StringBuilder builder = new StringBuilder(bufferSize + line.length());
682 		while (builder.length() <= bufferSize) {
683 			builder.append(line);
684 		}
685 		return builder.toString();
686 	}
687 
688 	private static String makeDiffHeader(String pathA, String pathB,
689 			ObjectId aId,
690 			ObjectId bId) {
691 		String a = aId.abbreviate(8).name();
692 		String b = bId.abbreviate(8).name();
693 		return DIFF + "a/" + pathA + " " + "b/" + pathB + "\n" + //
694 				"index " + a + ".." + b + " " + REGULAR_FILE + "\n" + //
695 				"--- a/" + pathA + "\n" + //
696 				"+++ b/" + pathB + "\n";
697 	}
698 
699 	private static String makeDiffHeaderModeChange(String pathA, String pathB,
700 			ObjectId aId, ObjectId bId, String modeA, String modeB) {
701 		String a = aId.abbreviate(8).name();
702 		String b = bId.abbreviate(8).name();
703 		return DIFF + "a/" + pathA + " " + "b/" + pathB + "\n" + //
704 				"old mode " + modeA + "\n" + //
705 				"new mode " + modeB + "\n" + //
706 				"index " + a + ".." + b + "\n" + //
707 				"--- a/" + pathA + "\n" + //
708 				"+++ b/" + pathB + "\n";
709 	}
710 
711 	private ObjectId blob(String content) throws Exception {
712 		return testDb.blob(content).copy();
713 	}
714 }