View Javadoc
1   /*
2    * Copyright (C) 2010, 2013 Google Inc.
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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 		//@formatter:off
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 		//@formatter:on
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 		// Insert a line into content
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 		// Create the patch
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 		// Create a CR-LF string longer than RawText.FIRST_FEW_BYTES whose
576 		// canonical representation is shorter than RawText.FIRST_FEW_BYTES.
577 		String line = "01234567\r\n"; // 10 characters
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 }