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