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