1
2
3
4
5
6
7
8
9
10
11 package org.eclipse.jgit.patch;
12
13 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
14 import static org.junit.Assert.assertArrayEquals;
15 import static org.junit.Assert.assertEquals;
16 import static org.junit.Assert.assertFalse;
17 import static org.junit.Assert.assertNotNull;
18 import static org.junit.Assert.assertNull;
19 import static org.junit.Assert.assertTrue;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.OutputStream;
25 import java.nio.charset.StandardCharsets;
26 import java.nio.file.Files;
27 import org.eclipse.jgit.api.Git;
28 import org.eclipse.jgit.api.errors.PatchApplyException;
29 import org.eclipse.jgit.api.errors.PatchFormatException;
30 import org.eclipse.jgit.attributes.FilterCommand;
31 import org.eclipse.jgit.attributes.FilterCommandFactory;
32 import org.eclipse.jgit.attributes.FilterCommandRegistry;
33 import org.eclipse.jgit.junit.RepositoryTestCase;
34 import org.eclipse.jgit.junit.TestRepository;
35 import org.eclipse.jgit.lib.Config;
36 import org.eclipse.jgit.lib.ConfigConstants;
37 import org.eclipse.jgit.lib.ObjectId;
38 import org.eclipse.jgit.lib.ObjectInserter;
39 import org.eclipse.jgit.patch.PatchApplier.Result;
40 import org.eclipse.jgit.revwalk.RevCommit;
41 import org.eclipse.jgit.revwalk.RevTree;
42 import org.eclipse.jgit.revwalk.RevWalk;
43 import org.eclipse.jgit.treewalk.TreeWalk;
44 import org.eclipse.jgit.util.FS;
45 import org.eclipse.jgit.util.IO;
46 import org.junit.Test;
47 import org.junit.runner.RunWith;
48 import org.junit.runners.Suite;
49
50 @RunWith(Suite.class)
51 @Suite.SuiteClasses({
52 PatchApplierTest.WithWorktree. class,
53 PatchApplierTest.InCore.class,
54 })
55 public class PatchApplierTest {
56
57 public abstract static class Base extends RepositoryTestCase {
58
59 protected String name;
60
61
62 protected byte[] preImage;
63
64 protected byte[] postImage;
65
66 protected String expectedText;
67 protected RevTree baseTip;
68 public boolean inCore;
69
70 Base(boolean inCore) {
71 this.inCore = inCore;
72 }
73
74 protected void init(String aName, boolean preExists, boolean postExists)
75 throws Exception {
76
77 this.name = aName;
78 if (postExists) {
79 postImage = IO
80 .readWholeStream(getTestResource(name + "_PostImage"), 0)
81 .array();
82 expectedText = new String(postImage, StandardCharsets.UTF_8);
83 }
84
85 File f = new File(db.getWorkTree(), name);
86 if (preExists) {
87 preImage = IO
88 .readWholeStream(getTestResource(name + "_PreImage"), 0)
89 .array();
90 try (Git git = new Git(db)) {
91 Files.write(f.toPath(), preImage);
92 git.add().addFilepattern(name).call();
93 }
94 }
95 try (Git git = new Git(db)) {
96 RevCommit base = git.commit().setMessage("PreImage").call();
97 baseTip = base.getTree();
98 }
99 }
100
101 void init(final String aName) throws Exception {
102 init(aName, true, true);
103 }
104
105 protected Result applyPatch()
106 throws PatchApplyException, PatchFormatException, IOException {
107 InputStream patchStream = getTestResource(name + ".patch");
108 if (inCore) {
109 try (ObjectInserter oi = db.newObjectInserter()) {
110 return new PatchApplier(db, baseTip, oi).applyPatch(patchStream);
111 }
112 }
113 return new PatchApplier(db).applyPatch(patchStream);
114 }
115
116 protected static InputStream getTestResource(String patchFile) {
117 return PatchApplierTest.class.getClassLoader()
118 .getResourceAsStream("org/eclipse/jgit/diff/" + patchFile);
119 }
120 void verifyChange(Result result, String aName) throws Exception {
121 verifyChange(result, aName, true);
122 }
123
124 protected void verifyContent(Result result, String path, boolean exists) throws Exception {
125 if (inCore) {
126 byte[] output = readBlob(result.getTreeId(), path);
127 if (!exists)
128 assertNull(output);
129 else {
130 assertNotNull(output);
131 assertEquals(new String(output, StandardCharsets.UTF_8), expectedText);
132 }
133 } else {
134 File f = new File(db.getWorkTree(), path);
135 if (!exists)
136 assertFalse(f.exists());
137 else
138 checkFile(f, expectedText);
139 }
140 }
141
142 void verifyChange(Result result, String aName, boolean exists) throws Exception {
143 assertEquals(1, result.getPaths().size());
144 verifyContent(result, aName, exists);
145 }
146
147 protected byte[] readBlob(ObjectId treeish, String path) throws Exception {
148 try (TestRepository<?> tr = new TestRepository<>(db);
149 RevWalk rw = tr.getRevWalk()) {
150 db.incrementOpen();
151 RevTree tree = rw.parseTree(treeish);
152 try (TreeWalk tw = TreeWalk.forPath(db,path,tree)){
153 if (tw == null) {
154 return null;
155 }
156 return tw.getObjectReader().open(tw.getObjectId(0), OBJ_BLOB).getBytes();
157 }
158 }
159 }
160
161 protected void checkBinary(Result result, int numberOfFiles) throws Exception {
162 assertEquals(numberOfFiles, result.getPaths().size());
163 if (inCore) {
164 assertArrayEquals(postImage, readBlob(result.getTreeId(), result.getPaths().get(0)));
165 } else {
166 File f = new File(db.getWorkTree(), name);
167 assertArrayEquals(postImage, Files.readAllBytes(f.toPath()));
168 }
169 }
170
171
172
173 @Test
174 public void testBinaryDelta() throws Exception {
175 init("delta");
176 checkBinary(applyPatch(), 1);
177 }
178
179 @Test
180 public void testBinaryLiteral() throws Exception {
181 init("literal");
182 checkBinary(applyPatch(), 1);
183 }
184
185 @Test
186 public void testBinaryLiteralAdd() throws Exception {
187 init("literal_add", false, true);
188 checkBinary(applyPatch(), 1);
189 }
190
191 @Test
192 public void testModifyM2() throws Exception {
193 init("M2", true, true);
194
195 Result result = applyPatch();
196
197 if (!inCore && FS.DETECTED.supportsExecute()) {
198 assertEquals(1, result.getPaths().size());
199 File f = new File(db.getWorkTree(), result.getPaths().get(0));
200 assertTrue(FS.DETECTED.canExecute(f));
201 }
202
203 verifyChange(result, "M2");
204 }
205
206 @Test
207 public void testModifyM3() throws Exception {
208 init("M3", true, true);
209
210 Result result = applyPatch();
211
212 verifyChange(result, "M3");
213 if (!inCore && FS.DETECTED.supportsExecute()) {
214 File f = new File(db.getWorkTree(), result.getPaths().get(0));
215 assertFalse(FS.DETECTED.canExecute(f));
216 }
217 }
218
219 @Test
220 public void testModifyX() throws Exception {
221 init("X");
222
223 Result result = applyPatch();
224 verifyChange(result, "X");
225 }
226
227 @Test
228 public void testModifyY() throws Exception {
229 init("Y");
230
231 Result result = applyPatch();
232
233 verifyChange(result, "Y");
234 }
235
236 @Test
237 public void testModifyZ() throws Exception {
238 init("Z");
239
240 Result result = applyPatch();
241 verifyChange(result, "Z");
242 }
243
244 @Test
245 public void testNonASCII() throws Exception {
246 init("NonASCII");
247
248 Result result = applyPatch();
249 verifyChange(result, "NonASCII");
250 }
251
252 @Test
253 public void testNonASCII2() throws Exception {
254 init("NonASCII2");
255
256 Result result = applyPatch();
257 verifyChange(result, "NonASCII2");
258 }
259
260 @Test
261 public void testNonASCIIAdd() throws Exception {
262 init("NonASCIIAdd");
263
264 Result result = applyPatch();
265 verifyChange(result, "NonASCIIAdd");
266 }
267
268 @Test
269 public void testNonASCIIAdd2() throws Exception {
270 init("NonASCIIAdd2", false, true);
271
272 Result result = applyPatch();
273 verifyChange(result, "NonASCIIAdd2");
274 }
275
276 @Test
277 public void testNonASCIIDel() throws Exception {
278 init("NonASCIIDel", true, false);
279
280 Result result = applyPatch();
281 verifyChange(result, "NonASCIIDel", false);
282 assertEquals("NonASCIIDel", result.getPaths().get(0));
283 }
284
285 @Test
286 public void testRenameNoHunks() throws Exception {
287 init("RenameNoHunks", true, true);
288
289 Result result = applyPatch();
290
291 assertEquals(2, result.getPaths().size());
292 assertTrue(result.getPaths().contains("RenameNoHunks"));
293 assertTrue(result.getPaths().contains("nested/subdir/Renamed"));
294
295 verifyContent(result,"nested/subdir/Renamed", true);
296 }
297
298 @Test
299 public void testRenameWithHunks() throws Exception {
300 init("RenameWithHunks", true, true);
301
302 Result result = applyPatch();
303 assertEquals(2, result.getPaths().size());
304 assertTrue(result.getPaths().contains("RenameWithHunks"));
305 assertTrue(result.getPaths().contains("nested/subdir/Renamed"));
306
307 verifyContent(result,"nested/subdir/Renamed", true);
308 }
309
310 @Test
311 public void testCopyWithHunks() throws Exception {
312 init("CopyWithHunks", true, true);
313
314 Result result = applyPatch();
315 verifyChange(result, "CopyResult", true);
316 }
317
318 @Test
319 public void testShiftUp() throws Exception {
320 init("ShiftUp");
321
322 Result result = applyPatch();
323 verifyChange(result, "ShiftUp");
324 }
325
326 @Test
327 public void testShiftUp2() throws Exception {
328 init("ShiftUp2");
329
330 Result result = applyPatch();
331 verifyChange(result, "ShiftUp2");
332 }
333
334 @Test
335 public void testShiftDown() throws Exception {
336 init("ShiftDown");
337
338 Result result = applyPatch();
339 verifyChange(result, "ShiftDown");
340 }
341
342 @Test
343 public void testShiftDown2() throws Exception {
344 init("ShiftDown2");
345
346 Result result = applyPatch();
347 verifyChange(result, "ShiftDown2");
348 }
349 }
350
351 public static class InCore extends Base {
352
353 public InCore() {
354 super(true);
355 }
356 }
357
358 public static class WithWorktree extends Base {
359 public WithWorktree() { super(false); }
360
361 @Test
362 public void testModifyNL1() throws Exception {
363 init("NL1");
364
365 Result result = applyPatch();
366 verifyChange(result, "NL1");
367 }
368
369 @Test
370 public void testCrLf() throws Exception {
371 try {
372 db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
373 ConfigConstants.CONFIG_KEY_AUTOCRLF, true);
374 init("crlf", true, true);
375
376 Result result = applyPatch();
377
378 verifyChange(result, "crlf");
379 } finally {
380 db.getConfig().unset(ConfigConstants.CONFIG_CORE_SECTION, null,
381 ConfigConstants.CONFIG_KEY_AUTOCRLF);
382 }
383 }
384
385 @Test
386 public void testCrLfOff() throws Exception {
387 try {
388 db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
389 ConfigConstants.CONFIG_KEY_AUTOCRLF, false);
390 init("crlf", true, true);
391
392 Result result = applyPatch();
393
394 verifyChange(result, "crlf");
395 } finally {
396 db.getConfig().unset(ConfigConstants.CONFIG_CORE_SECTION, null,
397 ConfigConstants.CONFIG_KEY_AUTOCRLF);
398 }
399 }
400
401 @Test
402 public void testCrLfEmptyCommitted() throws Exception {
403 try {
404 db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
405 ConfigConstants.CONFIG_KEY_AUTOCRLF, true);
406 init("crlf3", true, true);
407
408 Result result = applyPatch();
409
410 verifyChange(result, "crlf3");
411 } finally {
412 db.getConfig().unset(ConfigConstants.CONFIG_CORE_SECTION, null,
413 ConfigConstants.CONFIG_KEY_AUTOCRLF);
414 }
415 }
416
417 @Test
418 public void testCrLfNewFile() throws Exception {
419 try {
420 db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
421 ConfigConstants.CONFIG_KEY_AUTOCRLF, true);
422 init("crlf4", false, true);
423
424 Result result = applyPatch();
425
426 verifyChange(result, "crlf4");
427 } finally {
428 db.getConfig().unset(ConfigConstants.CONFIG_CORE_SECTION, null,
429 ConfigConstants.CONFIG_KEY_AUTOCRLF);
430 }
431 }
432
433 @Test
434 public void testPatchWithCrLf() throws Exception {
435 try {
436 db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
437 ConfigConstants.CONFIG_KEY_AUTOCRLF, false);
438 init("crlf2", true, true);
439
440 Result result = applyPatch();
441
442 verifyChange(result, "crlf2");
443 } finally {
444 db.getConfig().unset(ConfigConstants.CONFIG_CORE_SECTION, null,
445 ConfigConstants.CONFIG_KEY_AUTOCRLF);
446 }
447 }
448
449 @Test
450 public void testPatchWithCrLf2() throws Exception {
451 String aName = "crlf2";
452 try (Git git = new Git(db)) {
453 db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
454 ConfigConstants.CONFIG_KEY_AUTOCRLF, false);
455 init(aName, true, true);
456 db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
457 ConfigConstants.CONFIG_KEY_AUTOCRLF, true);
458
459 Result result = applyPatch();
460
461 verifyChange(result, aName);
462 } finally {
463 db.getConfig().unset(ConfigConstants.CONFIG_CORE_SECTION, null,
464 ConfigConstants.CONFIG_KEY_AUTOCRLF);
465 }
466 }
467
468
469
470
471
472 private static class ReplaceFilter extends FilterCommand {
473
474 private final char toReplace;
475
476 private final char replacement;
477
478 ReplaceFilter(InputStream in, OutputStream out, char toReplace,
479 char replacement) {
480 super(in, out);
481 this.toReplace = toReplace;
482 this.replacement = replacement;
483 }
484
485 @Override
486 public int run() throws IOException {
487 int b = in.read();
488 if (b < 0) {
489 in.close();
490 out.close();
491 return -1;
492 }
493 if ((b & 0xFF) == toReplace) {
494 b = replacement;
495 }
496 out.write(b);
497 return 1;
498 }
499 }
500
501 @Test
502 public void testFiltering() throws Exception {
503
504 FilterCommandFactory clean = (repo, in, out) -> new ReplaceFilter(in, out, 'A', 'E');
505 FilterCommandFactory smudge = (repo, in, out) -> new ReplaceFilter(in, out, 'E', 'A');
506 FilterCommandRegistry.register("jgit://builtin/a2e/clean", clean);
507 FilterCommandRegistry.register("jgit://builtin/a2e/smudge", smudge);
508 Config config = db.getConfig();
509 try (Git git = new Git(db)) {
510 config.setString(ConfigConstants.CONFIG_FILTER_SECTION, "a2e",
511 "clean", "jgit://builtin/a2e/clean");
512 config.setString(ConfigConstants.CONFIG_FILTER_SECTION, "a2e",
513 "smudge", "jgit://builtin/a2e/smudge");
514 write(new File(db.getWorkTree(), ".gitattributes"),
515 "smudgetest filter=a2e");
516 git.add().addFilepattern(".gitattributes").call();
517 git.commit().setMessage("Attributes").call();
518 init("smudgetest", true, true);
519
520 Result result = applyPatch();
521
522 verifyChange(result, name);
523 } finally {
524 config.unset(ConfigConstants.CONFIG_FILTER_SECTION, "a2e",
525 "clean");
526 config.unset(ConfigConstants.CONFIG_FILTER_SECTION, "a2e",
527 "smudge");
528
529 FilterCommandRegistry.unregister("jgit://builtin/a2e/clean");
530 FilterCommandRegistry.unregister("jgit://builtin/a2e/smudge");
531 }
532 }
533 }
534 }