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.api;
45
46 import static org.eclipse.jgit.util.FileUtils.RECURSIVE;
47 import static org.junit.Assert.assertEquals;
48 import static org.junit.Assert.assertTrue;
49 import static org.junit.Assert.fail;
50
51 import java.io.File;
52 import java.io.FileInputStream;
53 import java.io.IOException;
54 import java.io.PrintWriter;
55 import java.util.Set;
56
57 import org.eclipse.jgit.api.errors.FilterFailedException;
58 import org.eclipse.jgit.api.errors.GitAPIException;
59 import org.eclipse.jgit.api.errors.NoFilepatternException;
60 import org.eclipse.jgit.attributes.FilterCommandRegistry;
61 import org.eclipse.jgit.dircache.DirCache;
62 import org.eclipse.jgit.dircache.DirCacheBuilder;
63 import org.eclipse.jgit.dircache.DirCacheEntry;
64 import org.eclipse.jgit.junit.JGitTestUtil;
65 import org.eclipse.jgit.junit.RepositoryTestCase;
66 import org.eclipse.jgit.lfs.CleanFilter;
67 import org.eclipse.jgit.lfs.SmudgeFilter;
68 import org.eclipse.jgit.lib.ConfigConstants;
69 import org.eclipse.jgit.lib.Constants;
70 import org.eclipse.jgit.lib.FileMode;
71 import org.eclipse.jgit.lib.ObjectId;
72 import org.eclipse.jgit.lib.ObjectInserter;
73 import org.eclipse.jgit.lib.Repository;
74 import org.eclipse.jgit.lib.StoredConfig;
75 import org.eclipse.jgit.revwalk.RevCommit;
76 import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
77 import org.eclipse.jgit.treewalk.TreeWalk;
78 import org.eclipse.jgit.treewalk.WorkingTreeOptions;
79 import org.eclipse.jgit.util.FS;
80 import org.eclipse.jgit.util.FileUtils;
81 import org.junit.Test;
82 import org.junit.experimental.theories.DataPoints;
83 import org.junit.experimental.theories.Theories;
84 import org.junit.experimental.theories.Theory;
85 import org.junit.runner.RunWith;
86
87 @RunWith(Theories.class)
88 public class AddCommandTest extends RepositoryTestCase {
89 @DataPoints
90 public static boolean[] sleepBeforeAddOptions = { true, false };
91
92
93 @Override
94 public void setUp() throws Exception {
95 CleanFilter.register();
96 SmudgeFilter.register();
97 super.setUp();
98 }
99
100 @Test
101 public void testAddNothing() throws GitAPIException {
102 try (Git git = new Git(db)) {
103 git.add().call();
104 fail("Expected IllegalArgumentException");
105 } catch (NoFilepatternException e) {
106
107 }
108
109 }
110
111 @Test
112 public void testAddNonExistingSingleFile() throws GitAPIException {
113 try (Git git = new Git(db)) {
114 DirCache dc = git.add().addFilepattern("a.txt").call();
115 assertEquals(0, dc.getEntryCount());
116 }
117 }
118
119 @Test
120 public void testAddExistingSingleFile() throws IOException, GitAPIException {
121 File file = new File(db.getWorkTree(), "a.txt");
122 FileUtils.createNewFile(file);
123 PrintWriter writer = new PrintWriter(file);
124 writer.print("content");
125 writer.close();
126
127 try (Git git = new Git(db)) {
128 git.add().addFilepattern("a.txt").call();
129
130 assertEquals(
131 "[a.txt, mode:100644, content:content]",
132 indexState(CONTENT));
133 }
134 }
135
136 @Test
137 public void testCleanFilter() throws IOException, GitAPIException {
138 writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
139 writeTrashFile("src/a.tmp", "foo");
140
141
142 writeTrashFile("src/a.txt", "foo\n");
143 File script = writeTempFile("sed s/o/e/g");
144
145 try (Git git = new Git(db)) {
146 StoredConfig config = git.getRepository().getConfig();
147 config.setString("filter", "tstFilter", "clean",
148 "sh " + slashify(script.getPath()));
149 config.save();
150
151 git.add().addFilepattern("src/a.txt").addFilepattern("src/a.tmp")
152 .call();
153
154 assertEquals(
155 "[src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:fee\n]",
156 indexState(CONTENT));
157 }
158 }
159
160 @Theory
161 public void testBuiltinFilters(boolean sleepBeforeAdd)
162 throws IOException,
163 GitAPIException, InterruptedException {
164 writeTrashFile(".gitattributes", "*.txt filter=lfs");
165 writeTrashFile("src/a.tmp", "foo");
166
167
168 File script = writeTempFile("sed s/o/e/g");
169 File f = writeTrashFile("src/a.txt", "foo\n");
170
171 try (Git git = new Git(db)) {
172 if (!sleepBeforeAdd) {
173 fsTick(f);
174 }
175 git.add().addFilepattern(".gitattributes").call();
176 StoredConfig config = git.getRepository().getConfig();
177 config.setString("filter", "lfs", "clean",
178 "sh " + slashify(script.getPath()));
179 config.setString("filter", "lfs", "smudge",
180 "sh " + slashify(script.getPath()));
181 config.setBoolean("filter", "lfs", "useJGitBuiltin", true);
182 config.save();
183
184 if (!sleepBeforeAdd) {
185 fsTick(f);
186 }
187 git.add().addFilepattern("src/a.txt").addFilepattern("src/a.tmp")
188 .addFilepattern(".gitattributes").call();
189
190 assertEquals(
191 "[.gitattributes, mode:100644, content:*.txt filter=lfs][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:version https://git-lfs.github.com/spec/v1\noid sha256:b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c\nsize 4\n]",
192 indexState(CONTENT));
193
194 RevCommit c1 = git.commit().setMessage("c1").call();
195 assertTrue(git.status().call().isClean());
196 f = writeTrashFile("src/a.txt", "foobar\n");
197 if (!sleepBeforeAdd) {
198 fsTick(f);
199 }
200 git.add().addFilepattern("src/a.txt").call();
201 git.commit().setMessage("c2").call();
202 assertTrue(git.status().call().isClean());
203 assertEquals(
204 "[.gitattributes, mode:100644, content:*.txt filter=lfs][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:version https://git-lfs.github.com/spec/v1\noid sha256:aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f\nsize 7\n]",
205 indexState(CONTENT));
206 assertEquals("foobar\n", read("src/a.txt"));
207 git.checkout().setName(c1.getName()).call();
208 assertEquals(
209 "[.gitattributes, mode:100644, content:*.txt filter=lfs][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:version https://git-lfs.github.com/spec/v1\noid sha256:b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c\nsize 4\n]",
210 indexState(CONTENT));
211 assertEquals(
212 "foo\n", read("src/a.txt"));
213 }
214 }
215
216 @Theory
217 public void testBuiltinCleanFilter(boolean sleepBeforeAdd)
218 throws IOException, GitAPIException, InterruptedException {
219 writeTrashFile(".gitattributes", "*.txt filter=lfs");
220 writeTrashFile("src/a.tmp", "foo");
221
222
223 File script = writeTempFile("sed s/o/e/g");
224 File f = writeTrashFile("src/a.txt", "foo\n");
225
226
227 FilterCommandRegistry.unregister(
228 org.eclipse.jgit.lib.Constants.BUILTIN_FILTER_PREFIX
229 + "lfs/smudge");
230
231 try (Git git = new Git(db)) {
232 if (!sleepBeforeAdd) {
233 fsTick(f);
234 }
235 git.add().addFilepattern(".gitattributes").call();
236 StoredConfig config = git.getRepository().getConfig();
237 config.setString("filter", "lfs", "clean",
238 "sh " + slashify(script.getPath()));
239 config.setString("filter", "lfs", "smudge",
240 "sh " + slashify(script.getPath()));
241 config.setBoolean("filter", "lfs", "useJGitBuiltin", true);
242 config.save();
243
244 if (!sleepBeforeAdd) {
245 fsTick(f);
246 }
247 git.add().addFilepattern("src/a.txt").addFilepattern("src/a.tmp")
248 .addFilepattern(".gitattributes").call();
249
250 assertEquals(
251 "[.gitattributes, mode:100644, content:*.txt filter=lfs][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:version https://git-lfs.github.com/spec/v1\noid sha256:b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c\nsize 4\n]",
252 indexState(CONTENT));
253
254 RevCommit c1 = git.commit().setMessage("c1").call();
255 assertTrue(git.status().call().isClean());
256 f = writeTrashFile("src/a.txt", "foobar\n");
257 if (!sleepBeforeAdd) {
258 fsTick(f);
259 }
260 git.add().addFilepattern("src/a.txt").call();
261 git.commit().setMessage("c2").call();
262 assertTrue(git.status().call().isClean());
263 assertEquals(
264 "[.gitattributes, mode:100644, content:*.txt filter=lfs][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:version https://git-lfs.github.com/spec/v1\noid sha256:aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f\nsize 7\n]",
265 indexState(CONTENT));
266 assertEquals("foobar\n", read("src/a.txt"));
267 git.checkout().setName(c1.getName()).call();
268 assertEquals(
269 "[.gitattributes, mode:100644, content:*.txt filter=lfs][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:version https://git-lfs.github.com/spec/v1\noid sha256:b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c\nsize 4\n]",
270 indexState(CONTENT));
271
272
273
274
275
276 assertEquals(
277 "versien https://git-lfs.github.cem/spec/v1\neid sha256:b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c\nsize 4\n",
278 read("src/a.txt"));
279 }
280 }
281
282 @Test
283 public void testAttributesWithTreeWalkFilter()
284 throws IOException, GitAPIException {
285 writeTrashFile(".gitattributes", "*.txt filter=lfs");
286 writeTrashFile("src/a.tmp", "foo");
287 writeTrashFile("src/a.txt", "foo\n");
288 File script = writeTempFile("sed s/o/e/g");
289
290 try (Git git = new Git(db)) {
291 StoredConfig config = git.getRepository().getConfig();
292 config.setString("filter", "lfs", "clean",
293 "sh " + slashify(script.getPath()));
294 config.save();
295
296 git.add().addFilepattern(".gitattributes").call();
297 git.commit().setMessage("attr").call();
298 git.add().addFilepattern("src/a.txt").addFilepattern("src/a.tmp")
299 .addFilepattern(".gitattributes").call();
300 git.commit().setMessage("c1").call();
301 assertTrue(git.status().call().isClean());
302 }
303 }
304
305 @Test
306 public void testCleanFilterEnvironment()
307 throws IOException, GitAPIException {
308 writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
309 writeTrashFile("src/a.txt", "foo");
310 File script = writeTempFile("echo $GIT_DIR; echo 1 >xyz");
311
312 try (Git git = new Git(db)) {
313 StoredConfig config = git.getRepository().getConfig();
314 config.setString("filter", "tstFilter", "clean",
315 "sh " + slashify(script.getPath()));
316 config.save();
317 git.add().addFilepattern("src/a.txt").call();
318
319 String gitDir = db.getDirectory().getAbsolutePath();
320 assertEquals("[src/a.txt, mode:100644, content:" + gitDir
321 + "\n]", indexState(CONTENT));
322 assertTrue(new File(db.getWorkTree(), "xyz").exists());
323 }
324 }
325
326 @Test
327 public void testMultipleCleanFilter() throws IOException, GitAPIException {
328 writeTrashFile(".gitattributes",
329 "*.txt filter=tstFilter\n*.tmp filter=tstFilter2");
330
331
332 writeTrashFile("src/a.tmp", "foo\n");
333 writeTrashFile("src/a.txt", "foo\n");
334 File script = writeTempFile("sed s/o/e/g");
335 File script2 = writeTempFile("sed s/f/x/g");
336
337 try (Git git = new Git(db)) {
338 StoredConfig config = git.getRepository().getConfig();
339 config.setString("filter", "tstFilter", "clean",
340 "sh " + slashify(script.getPath()));
341 config.setString("filter", "tstFilter2", "clean",
342 "sh " + slashify(script2.getPath()));
343 config.save();
344
345 git.add().addFilepattern("src/a.txt").addFilepattern("src/a.tmp")
346 .call();
347
348 assertEquals(
349 "[src/a.tmp, mode:100644, content:xoo\n][src/a.txt, mode:100644, content:fee\n]",
350 indexState(CONTENT));
351
352
353 }
354 }
355
356
357
358
359
360
361
362
363
364 @Test
365 public void testCommandInjection() throws IOException, GitAPIException {
366
367
368 writeTrashFile("; echo virus", "foo\n");
369 File script = writeTempFile("sed s/o/e/g");
370
371 try (Git git = new Git(db)) {
372 StoredConfig config = git.getRepository().getConfig();
373 config.setString("filter", "tstFilter", "clean",
374 "sh " + slashify(script.getPath()) + " %f");
375 writeTrashFile(".gitattributes", "* filter=tstFilter");
376
377 git.add().addFilepattern("; echo virus").call();
378
379
380 assertEquals("[; echo virus, mode:100644, content:fee\n]",
381 indexState(CONTENT));
382 }
383 }
384
385 @Test
386 public void testBadCleanFilter() throws IOException, GitAPIException {
387 writeTrashFile("a.txt", "foo");
388 File script = writeTempFile("sedfoo s/o/e/g");
389
390 try (Git git = new Git(db)) {
391 StoredConfig config = git.getRepository().getConfig();
392 config.setString("filter", "tstFilter", "clean",
393 "sh " + script.getPath());
394 config.save();
395 writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
396
397 try {
398 git.add().addFilepattern("a.txt").call();
399 fail("Didn't received the expected exception");
400 } catch (FilterFailedException e) {
401 assertEquals(127, e.getReturnCode());
402 }
403 }
404 }
405
406 @Test
407 public void testBadCleanFilter2() throws IOException, GitAPIException {
408 writeTrashFile("a.txt", "foo");
409 File script = writeTempFile("sed s/o/e/g");
410
411 try (Git git = new Git(db)) {
412 StoredConfig config = git.getRepository().getConfig();
413 config.setString("filter", "tstFilter", "clean",
414 "shfoo " + script.getPath());
415 config.save();
416 writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
417
418 try {
419 git.add().addFilepattern("a.txt").call();
420 fail("Didn't received the expected exception");
421 } catch (FilterFailedException e) {
422 assertEquals(127, e.getReturnCode());
423 }
424 }
425 }
426
427 @Test
428 public void testCleanFilterReturning12() throws IOException,
429 GitAPIException {
430 writeTrashFile("a.txt", "foo");
431 File script = writeTempFile("exit 12");
432
433 try (Git git = new Git(db)) {
434 StoredConfig config = git.getRepository().getConfig();
435 config.setString("filter", "tstFilter", "clean",
436 "sh " + slashify(script.getPath()));
437 config.save();
438 writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
439
440 try {
441 git.add().addFilepattern("a.txt").call();
442 fail("Didn't received the expected exception");
443 } catch (FilterFailedException e) {
444 assertEquals(12, e.getReturnCode());
445 }
446 }
447 }
448
449 @Test
450 public void testNotApplicableFilter() throws IOException, GitAPIException {
451 writeTrashFile("a.txt", "foo");
452 File script = writeTempFile("sed s/o/e/g");
453
454 try (Git git = new Git(db)) {
455 StoredConfig config = git.getRepository().getConfig();
456 config.setString("filter", "tstFilter", "something",
457 "sh " + script.getPath());
458 config.save();
459 writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
460
461 git.add().addFilepattern("a.txt").call();
462
463 assertEquals("[a.txt, mode:100644, content:foo]",
464 indexState(CONTENT));
465 }
466 }
467
468 private File writeTempFile(String body) throws IOException {
469 File f = File.createTempFile("AddCommandTest_", "");
470 JGitTestUtil.write(f, body);
471 return f;
472 }
473
474 @Test
475 public void testAddExistingSingleSmallFileWithNewLine() throws IOException,
476 GitAPIException {
477 File file = new File(db.getWorkTree(), "a.txt");
478 FileUtils.createNewFile(file);
479 PrintWriter writer = new PrintWriter(file);
480 writer.print("row1\r\nrow2");
481 writer.close();
482
483 try (Git git = new Git(db)) {
484 db.getConfig().setString("core", null, "autocrlf", "false");
485 git.add().addFilepattern("a.txt").call();
486 assertEquals("[a.txt, mode:100644, content:row1\r\nrow2]",
487 indexState(CONTENT));
488 db.getConfig().setString("core", null, "autocrlf", "true");
489 git.add().addFilepattern("a.txt").call();
490 assertEquals("[a.txt, mode:100644, content:row1\nrow2]",
491 indexState(CONTENT));
492 db.getConfig().setString("core", null, "autocrlf", "input");
493 git.add().addFilepattern("a.txt").call();
494 assertEquals("[a.txt, mode:100644, content:row1\nrow2]",
495 indexState(CONTENT));
496 }
497 }
498
499 @Test
500 public void testAddExistingSingleMediumSizeFileWithNewLine()
501 throws IOException, GitAPIException {
502 File file = new File(db.getWorkTree(), "a.txt");
503 FileUtils.createNewFile(file);
504 StringBuilder data = new StringBuilder();
505 for (int i = 0; i < 1000; ++i) {
506 data.append("row1\r\nrow2");
507 }
508 String crData = data.toString();
509 PrintWriter writer = new PrintWriter(file);
510 writer.print(crData);
511 writer.close();
512 String lfData = data.toString().replaceAll("\r", "");
513 try (Git git = new Git(db)) {
514 db.getConfig().setString("core", null, "autocrlf", "false");
515 git.add().addFilepattern("a.txt").call();
516 assertEquals("[a.txt, mode:100644, content:" + data + "]",
517 indexState(CONTENT));
518 db.getConfig().setString("core", null, "autocrlf", "true");
519 git.add().addFilepattern("a.txt").call();
520 assertEquals("[a.txt, mode:100644, content:" + lfData + "]",
521 indexState(CONTENT));
522 db.getConfig().setString("core", null, "autocrlf", "input");
523 git.add().addFilepattern("a.txt").call();
524 assertEquals("[a.txt, mode:100644, content:" + lfData + "]",
525 indexState(CONTENT));
526 }
527 }
528
529 @Test
530 public void testAddExistingSingleBinaryFile() throws IOException,
531 GitAPIException {
532 File file = new File(db.getWorkTree(), "a.txt");
533 FileUtils.createNewFile(file);
534 PrintWriter writer = new PrintWriter(file);
535 writer.print("row1\r\nrow2\u0000");
536 writer.close();
537
538 try (Git git = new Git(db)) {
539 db.getConfig().setString("core", null, "autocrlf", "false");
540 git.add().addFilepattern("a.txt").call();
541 assertEquals("[a.txt, mode:100644, content:row1\r\nrow2\u0000]",
542 indexState(CONTENT));
543 db.getConfig().setString("core", null, "autocrlf", "true");
544 git.add().addFilepattern("a.txt").call();
545 assertEquals("[a.txt, mode:100644, content:row1\r\nrow2\u0000]",
546 indexState(CONTENT));
547 db.getConfig().setString("core", null, "autocrlf", "input");
548 git.add().addFilepattern("a.txt").call();
549 assertEquals("[a.txt, mode:100644, content:row1\r\nrow2\u0000]",
550 indexState(CONTENT));
551 }
552 }
553
554 @Test
555 public void testAddExistingSingleFileInSubDir() throws IOException,
556 GitAPIException {
557 FileUtils.mkdir(new File(db.getWorkTree(), "sub"));
558 File file = new File(db.getWorkTree(), "sub/a.txt");
559 FileUtils.createNewFile(file);
560 PrintWriter writer = new PrintWriter(file);
561 writer.print("content");
562 writer.close();
563
564 try (Git git = new Git(db)) {
565 git.add().addFilepattern("sub/a.txt").call();
566
567 assertEquals(
568 "[sub/a.txt, mode:100644, content:content]",
569 indexState(CONTENT));
570 }
571 }
572
573 @Test
574 public void testAddExistingSingleFileTwice() throws IOException,
575 GitAPIException {
576 File file = new File(db.getWorkTree(), "a.txt");
577 FileUtils.createNewFile(file);
578 PrintWriter writer = new PrintWriter(file);
579 writer.print("content");
580 writer.close();
581
582 try (Git git = new Git(db)) {
583 DirCache dc = git.add().addFilepattern("a.txt").call();
584
585 dc.getEntry(0).getObjectId();
586
587 writer = new PrintWriter(file);
588 writer.print("other content");
589 writer.close();
590
591 dc = git.add().addFilepattern("a.txt").call();
592
593 assertEquals(
594 "[a.txt, mode:100644, content:other content]",
595 indexState(CONTENT));
596 }
597 }
598
599 @Test
600 public void testAddExistingSingleFileTwiceWithCommit() throws Exception {
601 File file = new File(db.getWorkTree(), "a.txt");
602 FileUtils.createNewFile(file);
603 PrintWriter writer = new PrintWriter(file);
604 writer.print("content");
605 writer.close();
606
607 try (Git git = new Git(db)) {
608 DirCache dc = git.add().addFilepattern("a.txt").call();
609
610 dc.getEntry(0).getObjectId();
611
612 git.commit().setMessage("commit a.txt").call();
613
614 writer = new PrintWriter(file);
615 writer.print("other content");
616 writer.close();
617
618 dc = git.add().addFilepattern("a.txt").call();
619
620 assertEquals(
621 "[a.txt, mode:100644, content:other content]",
622 indexState(CONTENT));
623 }
624 }
625
626 @Test
627 public void testAddRemovedFile() throws Exception {
628 File file = new File(db.getWorkTree(), "a.txt");
629 FileUtils.createNewFile(file);
630 PrintWriter writer = new PrintWriter(file);
631 writer.print("content");
632 writer.close();
633
634 try (Git git = new Git(db)) {
635 DirCache dc = git.add().addFilepattern("a.txt").call();
636
637 dc.getEntry(0).getObjectId();
638 FileUtils.delete(file);
639
640
641 dc = git.add().addFilepattern("a.txt").call();
642
643 assertEquals(
644 "[a.txt, mode:100644, content:content]",
645 indexState(CONTENT));
646 }
647 }
648
649 @Test
650 public void testAddRemovedCommittedFile() throws Exception {
651 File file = new File(db.getWorkTree(), "a.txt");
652 FileUtils.createNewFile(file);
653 PrintWriter writer = new PrintWriter(file);
654 writer.print("content");
655 writer.close();
656
657 try (Git git = new Git(db)) {
658 DirCache dc = git.add().addFilepattern("a.txt").call();
659
660 git.commit().setMessage("commit a.txt").call();
661
662 dc.getEntry(0).getObjectId();
663 FileUtils.delete(file);
664
665
666 dc = git.add().addFilepattern("a.txt").call();
667
668 assertEquals(
669 "[a.txt, mode:100644, content:content]",
670 indexState(CONTENT));
671 }
672 }
673
674 @Test
675 public void testAddWithConflicts() throws Exception {
676
677
678 File file = new File(db.getWorkTree(), "a.txt");
679 FileUtils.createNewFile(file);
680 PrintWriter writer = new PrintWriter(file);
681 writer.print("content");
682 writer.close();
683
684 File file2 = new File(db.getWorkTree(), "b.txt");
685 FileUtils.createNewFile(file2);
686 writer = new PrintWriter(file2);
687 writer.print("content b");
688 writer.close();
689
690 ObjectInserter newObjectInserter = db.newObjectInserter();
691 DirCache dc = db.lockDirCache();
692 DirCacheBuilder builder = dc.builder();
693
694 addEntryToBuilder("b.txt", file2, newObjectInserter, builder, 0);
695 addEntryToBuilder("a.txt", file, newObjectInserter, builder, 1);
696
697 writer = new PrintWriter(file);
698 writer.print("other content");
699 writer.close();
700 addEntryToBuilder("a.txt", file, newObjectInserter, builder, 3);
701
702 writer = new PrintWriter(file);
703 writer.print("our content");
704 writer.close();
705 addEntryToBuilder("a.txt", file, newObjectInserter, builder, 2)
706 .getObjectId();
707
708 builder.commit();
709
710 assertEquals(
711 "[a.txt, mode:100644, stage:1, content:content]" +
712 "[a.txt, mode:100644, stage:2, content:our content]" +
713 "[a.txt, mode:100644, stage:3, content:other content]" +
714 "[b.txt, mode:100644, content:content b]",
715 indexState(CONTENT));
716
717
718
719 try (Git git = new Git(db)) {
720 dc = git.add().addFilepattern("a.txt").call();
721
722 assertEquals(
723 "[a.txt, mode:100644, content:our content]" +
724 "[b.txt, mode:100644, content:content b]",
725 indexState(CONTENT));
726 }
727 }
728
729 @Test
730 public void testAddTwoFiles() throws Exception {
731 File file = new File(db.getWorkTree(), "a.txt");
732 FileUtils.createNewFile(file);
733 PrintWriter writer = new PrintWriter(file);
734 writer.print("content");
735 writer.close();
736
737 File file2 = new File(db.getWorkTree(), "b.txt");
738 FileUtils.createNewFile(file2);
739 writer = new PrintWriter(file2);
740 writer.print("content b");
741 writer.close();
742
743 try (Git git = new Git(db)) {
744 git.add().addFilepattern("a.txt").addFilepattern("b.txt").call();
745 assertEquals(
746 "[a.txt, mode:100644, content:content]" +
747 "[b.txt, mode:100644, content:content b]",
748 indexState(CONTENT));
749 }
750 }
751
752 @Test
753 public void testAddFolder() throws Exception {
754 FileUtils.mkdir(new File(db.getWorkTree(), "sub"));
755 File file = new File(db.getWorkTree(), "sub/a.txt");
756 FileUtils.createNewFile(file);
757 PrintWriter writer = new PrintWriter(file);
758 writer.print("content");
759 writer.close();
760
761 File file2 = new File(db.getWorkTree(), "sub/b.txt");
762 FileUtils.createNewFile(file2);
763 writer = new PrintWriter(file2);
764 writer.print("content b");
765 writer.close();
766
767 try (Git git = new Git(db)) {
768 git.add().addFilepattern("sub").call();
769 assertEquals(
770 "[sub/a.txt, mode:100644, content:content]" +
771 "[sub/b.txt, mode:100644, content:content b]",
772 indexState(CONTENT));
773 }
774 }
775
776 @Test
777 public void testAddIgnoredFile() throws Exception {
778 FileUtils.mkdir(new File(db.getWorkTree(), "sub"));
779 File file = new File(db.getWorkTree(), "sub/a.txt");
780 FileUtils.createNewFile(file);
781 PrintWriter writer = new PrintWriter(file);
782 writer.print("content");
783 writer.close();
784
785 File ignoreFile = new File(db.getWorkTree(), ".gitignore");
786 FileUtils.createNewFile(ignoreFile);
787 writer = new PrintWriter(ignoreFile);
788 writer.print("sub/b.txt");
789 writer.close();
790
791 File file2 = new File(db.getWorkTree(), "sub/b.txt");
792 FileUtils.createNewFile(file2);
793 writer = new PrintWriter(file2);
794 writer.print("content b");
795 writer.close();
796
797 try (Git git = new Git(db)) {
798 git.add().addFilepattern("sub").call();
799
800 assertEquals(
801 "[sub/a.txt, mode:100644, content:content]",
802 indexState(CONTENT));
803 }
804 }
805
806 @Test
807 public void testAddWholeRepo() throws Exception {
808 FileUtils.mkdir(new File(db.getWorkTree(), "sub"));
809 File file = new File(db.getWorkTree(), "sub/a.txt");
810 FileUtils.createNewFile(file);
811 PrintWriter writer = new PrintWriter(file);
812 writer.print("content");
813 writer.close();
814
815 File file2 = new File(db.getWorkTree(), "sub/b.txt");
816 FileUtils.createNewFile(file2);
817 writer = new PrintWriter(file2);
818 writer.print("content b");
819 writer.close();
820
821 try (Git git = new Git(db)) {
822 git.add().addFilepattern(".").call();
823 assertEquals(
824 "[sub/a.txt, mode:100644, content:content]" +
825 "[sub/b.txt, mode:100644, content:content b]",
826 indexState(CONTENT));
827 }
828 }
829
830
831
832
833
834 @Test
835 public void testAddWithoutParameterUpdate() throws Exception {
836 FileUtils.mkdir(new File(db.getWorkTree(), "sub"));
837 File file = new File(db.getWorkTree(), "sub/a.txt");
838 FileUtils.createNewFile(file);
839 PrintWriter writer = new PrintWriter(file);
840 writer.print("content");
841 writer.close();
842
843 File file2 = new File(db.getWorkTree(), "sub/b.txt");
844 FileUtils.createNewFile(file2);
845 writer = new PrintWriter(file2);
846 writer.print("content b");
847 writer.close();
848
849 try (Git git = new Git(db)) {
850 git.add().addFilepattern("sub").call();
851
852 assertEquals(
853 "[sub/a.txt, mode:100644, content:content]" +
854 "[sub/b.txt, mode:100644, content:content b]",
855 indexState(CONTENT));
856
857 git.commit().setMessage("commit").call();
858
859
860 File file3 = new File(db.getWorkTree(), "sub/c.txt");
861 FileUtils.createNewFile(file3);
862 writer = new PrintWriter(file3);
863 writer.print("content c");
864 writer.close();
865
866
867 writer = new PrintWriter(file);
868 writer.print("modified content");
869 writer.close();
870
871
872 FileUtils.delete(file2);
873
874 git.add().addFilepattern("sub").call();
875
876
877
878 assertEquals(
879 "[sub/a.txt, mode:100644, content:modified content]" +
880 "[sub/b.txt, mode:100644, content:content b]" +
881 "[sub/c.txt, mode:100644, content:content c]",
882 indexState(CONTENT));
883 }
884 }
885
886
887
888
889 @Test
890 public void testAddWithParameterUpdate() throws Exception {
891 FileUtils.mkdir(new File(db.getWorkTree(), "sub"));
892 File file = new File(db.getWorkTree(), "sub/a.txt");
893 FileUtils.createNewFile(file);
894 PrintWriter writer = new PrintWriter(file);
895 writer.print("content");
896 writer.close();
897
898 File file2 = new File(db.getWorkTree(), "sub/b.txt");
899 FileUtils.createNewFile(file2);
900 writer = new PrintWriter(file2);
901 writer.print("content b");
902 writer.close();
903
904 try (Git git = new Git(db)) {
905 git.add().addFilepattern("sub").call();
906
907 assertEquals(
908 "[sub/a.txt, mode:100644, content:content]" +
909 "[sub/b.txt, mode:100644, content:content b]",
910 indexState(CONTENT));
911
912 git.commit().setMessage("commit").call();
913
914
915 File file3 = new File(db.getWorkTree(), "sub/c.txt");
916 FileUtils.createNewFile(file3);
917 writer = new PrintWriter(file3);
918 writer.print("content c");
919 writer.close();
920
921
922 writer = new PrintWriter(file);
923 writer.print("modified content");
924 writer.close();
925
926 FileUtils.delete(file2);
927
928
929
930
931 git.add().addFilepattern("sub").setUpdate(true).call();
932
933 assertEquals(
934 "[sub/a.txt, mode:100644, content:modified content]",
935 indexState(CONTENT));
936 }
937 }
938
939 @Test
940 public void testAssumeUnchanged() throws Exception {
941 try (Git git = new Git(db)) {
942 String path = "a.txt";
943 writeTrashFile(path, "content");
944 git.add().addFilepattern(path).call();
945 String path2 = "b.txt";
946 writeTrashFile(path2, "content");
947 git.add().addFilepattern(path2).call();
948 git.commit().setMessage("commit").call();
949 assertEquals("[a.txt, mode:100644, content:"
950 + "content, assume-unchanged:false]"
951 + "[b.txt, mode:100644, content:content, "
952 + "assume-unchanged:false]", indexState(CONTENT
953 | ASSUME_UNCHANGED));
954 assumeUnchanged(path2);
955 assertEquals("[a.txt, mode:100644, content:content, "
956 + "assume-unchanged:false][b.txt, mode:100644, "
957 + "content:content, assume-unchanged:true]", indexState(CONTENT
958 | ASSUME_UNCHANGED));
959 writeTrashFile(path, "more content");
960 writeTrashFile(path2, "more content");
961
962 git.add().addFilepattern(".").call();
963
964 assertEquals("[a.txt, mode:100644, content:more content,"
965 + " assume-unchanged:false][b.txt, mode:100644,"
966 + " content:content, assume-unchanged:true]",
967 indexState(CONTENT
968 | ASSUME_UNCHANGED));
969 }
970 }
971
972 @Test
973 public void testReplaceFileWithDirectory()
974 throws IOException, NoFilepatternException, GitAPIException {
975 try (Git git = new Git(db)) {
976 writeTrashFile("df", "before replacement");
977 git.add().addFilepattern("df").call();
978 assertEquals("[df, mode:100644, content:before replacement]",
979 indexState(CONTENT));
980 FileUtils.delete(new File(db.getWorkTree(), "df"));
981 writeTrashFile("df/f", "after replacement");
982 git.add().addFilepattern("df").call();
983 assertEquals("[df/f, mode:100644, content:after replacement]",
984 indexState(CONTENT));
985 }
986 }
987
988 @Test
989 public void testReplaceDirectoryWithFile()
990 throws IOException, NoFilepatternException, GitAPIException {
991 try (Git git = new Git(db)) {
992 writeTrashFile("df/f", "before replacement");
993 git.add().addFilepattern("df").call();
994 assertEquals("[df/f, mode:100644, content:before replacement]",
995 indexState(CONTENT));
996 FileUtils.delete(new File(db.getWorkTree(), "df"), RECURSIVE);
997 writeTrashFile("df", "after replacement");
998 git.add().addFilepattern("df").call();
999 assertEquals("[df, mode:100644, content:after replacement]",
1000 indexState(CONTENT));
1001 }
1002 }
1003
1004 @Test
1005 public void testReplaceFileByPartOfDirectory()
1006 throws IOException, NoFilepatternException, GitAPIException {
1007 try (Git git = new Git(db)) {
1008 writeTrashFile("src/main", "df", "before replacement");
1009 writeTrashFile("src/main", "z", "z");
1010 writeTrashFile("z", "z2");
1011 git.add().addFilepattern("src/main/df")
1012 .addFilepattern("src/main/z")
1013 .addFilepattern("z")
1014 .call();
1015 assertEquals(
1016 "[src/main/df, mode:100644, content:before replacement]" +
1017 "[src/main/z, mode:100644, content:z]" +
1018 "[z, mode:100644, content:z2]",
1019 indexState(CONTENT));
1020 FileUtils.delete(new File(db.getWorkTree(), "src/main/df"));
1021 writeTrashFile("src/main/df", "a", "after replacement");
1022 writeTrashFile("src/main/df", "b", "unrelated file");
1023 git.add().addFilepattern("src/main/df/a").call();
1024 assertEquals(
1025 "[src/main/df/a, mode:100644, content:after replacement]" +
1026 "[src/main/z, mode:100644, content:z]" +
1027 "[z, mode:100644, content:z2]",
1028 indexState(CONTENT));
1029 }
1030 }
1031
1032 @Test
1033 public void testReplaceDirectoryConflictsWithFile()
1034 throws IOException, NoFilepatternException, GitAPIException {
1035 DirCache dc = db.lockDirCache();
1036 try (ObjectInserter oi = db.newObjectInserter()) {
1037 DirCacheBuilder builder = dc.builder();
1038 File f = writeTrashFile("a", "df", "content");
1039 addEntryToBuilder("a", f, oi, builder, 1);
1040
1041 f = writeTrashFile("a", "df", "other content");
1042 addEntryToBuilder("a/df", f, oi, builder, 3);
1043
1044 f = writeTrashFile("a", "df", "our content");
1045 addEntryToBuilder("a/df", f, oi, builder, 2);
1046
1047 f = writeTrashFile("z", "z");
1048 addEntryToBuilder("z", f, oi, builder, 0);
1049 builder.commit();
1050 }
1051 assertEquals(
1052 "[a, mode:100644, stage:1, content:content]" +
1053 "[a/df, mode:100644, stage:2, content:our content]" +
1054 "[a/df, mode:100644, stage:3, content:other content]" +
1055 "[z, mode:100644, content:z]",
1056 indexState(CONTENT));
1057
1058 try (Git git = new Git(db)) {
1059 FileUtils.delete(new File(db.getWorkTree(), "a"), RECURSIVE);
1060 writeTrashFile("a", "merged");
1061 git.add().addFilepattern("a").call();
1062 assertEquals("[a, mode:100644, content:merged]" +
1063 "[z, mode:100644, content:z]",
1064 indexState(CONTENT));
1065 }
1066 }
1067
1068 @Test
1069 public void testExecutableRetention() throws Exception {
1070 StoredConfig config = db.getConfig();
1071 config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
1072 ConfigConstants.CONFIG_KEY_FILEMODE, true);
1073 config.save();
1074
1075 FS executableFs = new FS() {
1076
1077 @Override
1078 public boolean supportsExecute() {
1079 return true;
1080 }
1081
1082 @Override
1083 public boolean setExecute(File f, boolean canExec) {
1084 return true;
1085 }
1086
1087 @Override
1088 public ProcessBuilder runInShell(String cmd, String[] args) {
1089 return null;
1090 }
1091
1092 @Override
1093 public boolean retryFailedLockFileCommit() {
1094 return false;
1095 }
1096
1097 @Override
1098 public FS newInstance() {
1099 return this;
1100 }
1101
1102 @Override
1103 protected File discoverGitExe() {
1104 return null;
1105 }
1106
1107 @Override
1108 public boolean canExecute(File f) {
1109 try {
1110 return read(f).startsWith("binary:");
1111 } catch (IOException e) {
1112 return false;
1113 }
1114 }
1115
1116 @Override
1117 public boolean isCaseSensitive() {
1118 return false;
1119 }
1120 };
1121
1122 Git git = Git.open(db.getDirectory(), executableFs);
1123 String path = "a.txt";
1124 String path2 = "a.sh";
1125 writeTrashFile(path, "content");
1126 writeTrashFile(path2, "binary: content");
1127 git.add().addFilepattern(path).addFilepattern(path2).call();
1128 RevCommit commit1 = git.commit().setMessage("commit").call();
1129 try (TreeWalk walk = new TreeWalk(db)) {
1130 walk.addTree(commit1.getTree());
1131 walk.next();
1132 assertEquals(path2, walk.getPathString());
1133 assertEquals(FileMode.EXECUTABLE_FILE, walk.getFileMode(0));
1134 walk.next();
1135 assertEquals(path, walk.getPathString());
1136 assertEquals(FileMode.REGULAR_FILE, walk.getFileMode(0));
1137 }
1138
1139 config = db.getConfig();
1140 config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
1141 ConfigConstants.CONFIG_KEY_FILEMODE, false);
1142 config.save();
1143
1144 Git git2 = Git.open(db.getDirectory(), executableFs);
1145 writeTrashFile(path2, "content2");
1146 writeTrashFile(path, "binary: content2");
1147 git2.add().addFilepattern(path).addFilepattern(path2).call();
1148 RevCommit commit2 = git2.commit().setMessage("commit2").call();
1149 try (TreeWalk walk = new TreeWalk(db)) {
1150 walk.addTree(commit2.getTree());
1151 walk.next();
1152 assertEquals(path2, walk.getPathString());
1153 assertEquals(FileMode.EXECUTABLE_FILE, walk.getFileMode(0));
1154 walk.next();
1155 assertEquals(path, walk.getPathString());
1156 assertEquals(FileMode.REGULAR_FILE, walk.getFileMode(0));
1157 }
1158 }
1159
1160 @Test
1161 public void testAddGitlink() throws Exception {
1162 createNestedRepo("git-link-dir");
1163 try (Git git = new Git(db)) {
1164 git.add().addFilepattern("git-link-dir").call();
1165
1166 assertEquals(
1167 "[git-link-dir, mode:160000]",
1168 indexState(0));
1169 Set<String> untrackedFiles = git.status().call().getUntracked();
1170 assert (untrackedFiles.isEmpty());
1171 }
1172
1173 }
1174
1175 @Test
1176 public void testAddSubrepoWithDirNoGitlinks() throws Exception {
1177 createNestedRepo("nested-repo");
1178
1179
1180 StoredConfig config = db.getConfig();
1181 config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
1182 ConfigConstants.CONFIG_KEY_DIRNOGITLINKS, true);
1183 config.save();
1184
1185 assert (db.getConfig().get(WorkingTreeOptions.KEY).isDirNoGitLinks());
1186
1187 try (Git git = new Git(db)) {
1188 git.add().addFilepattern("nested-repo").call();
1189
1190 assertEquals(
1191 "[nested-repo/README1.md, mode:100644]" +
1192 "[nested-repo/README2.md, mode:100644]",
1193 indexState(0));
1194 }
1195
1196
1197
1198
1199 config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
1200 ConfigConstants.CONFIG_KEY_DIRNOGITLINKS, false);
1201 config.save();
1202
1203 writeTrashFile("nested-repo", "README3.md", "content");
1204
1205 try (Git git = new Git(db)) {
1206 git.add().addFilepattern("nested-repo").call();
1207
1208 assertEquals(
1209 "[nested-repo/README1.md, mode:100644]" +
1210 "[nested-repo/README2.md, mode:100644]" +
1211 "[nested-repo/README3.md, mode:100644]",
1212 indexState(0));
1213 }
1214 }
1215
1216 @Test
1217 public void testAddGitlinkDoesNotChange() throws Exception {
1218 createNestedRepo("nested-repo");
1219
1220 try (Git git = new Git(db)) {
1221 git.add().addFilepattern("nested-repo").call();
1222
1223 assertEquals(
1224 "[nested-repo, mode:160000]",
1225 indexState(0));
1226 }
1227
1228
1229 StoredConfig config = db.getConfig();
1230 config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
1231 ConfigConstants.CONFIG_KEY_DIRNOGITLINKS, true);
1232 config.save();
1233
1234 assert (db.getConfig().get(WorkingTreeOptions.KEY).isDirNoGitLinks());
1235
1236 try (Git git = new Git(db)) {
1237 git.add().addFilepattern("nested-repo").call();
1238
1239 assertEquals(
1240 "[nested-repo, mode:160000]",
1241 indexState(0));
1242 }
1243 }
1244
1245 private static DirCacheEntry addEntryToBuilder(String path, File file,
1246 ObjectInserter newObjectInserter, DirCacheBuilder builder, int stage)
1247 throws IOException {
1248 FileInputStream inputStream = new FileInputStream(file);
1249 ObjectId id = newObjectInserter.insert(
1250 Constants.OBJ_BLOB, file.length(), inputStream);
1251 inputStream.close();
1252 DirCacheEntry entry = new DirCacheEntry(path, stage);
1253 entry.setObjectId(id);
1254 entry.setFileMode(FileMode.REGULAR_FILE);
1255 entry.setLastModified(file.lastModified());
1256 entry.setLength((int) file.length());
1257
1258 builder.add(entry);
1259 return entry;
1260 }
1261
1262 private void assumeUnchanged(String path) throws IOException {
1263 final DirCache dirc = db.lockDirCache();
1264 final DirCacheEntry ent = dirc.getEntry(path);
1265 if (ent != null)
1266 ent.setAssumeValid(true);
1267 dirc.write();
1268 if (!dirc.commit())
1269 throw new IOException("could not commit");
1270 }
1271
1272 private void createNestedRepo(String path) throws IOException {
1273 File gitLinkDir = new File(db.getWorkTree(), path);
1274 FileUtils.mkdir(gitLinkDir);
1275
1276 FileRepositoryBuilder nestedBuilder = new FileRepositoryBuilder();
1277 nestedBuilder.setWorkTree(gitLinkDir);
1278
1279 Repository nestedRepo = nestedBuilder.build();
1280 nestedRepo.create();
1281
1282 writeTrashFile(path, "README1.md", "content");
1283 writeTrashFile(path, "README2.md", "content");
1284
1285
1286 try (Git git = new Git(nestedRepo)) {
1287 git.add().addFilepattern(".").call();
1288 git.commit().setMessage("subrepo commit").call();
1289 } catch (GitAPIException e) {
1290 throw new RuntimeException(e);
1291 }
1292 }
1293 }