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 testAttributesConflictingMatch() throws Exception {
307 writeTrashFile(".gitattributes", "foo/** crlf=input\n*.jar binary");
308 writeTrashFile("foo/bar.jar", "\r\n");
309
310
311 try (Git git = new Git(db)) {
312 git.add().addFilepattern(".").call();
313 assertEquals(
314 "[.gitattributes, mode:100644, content:foo/** crlf=input\n*.jar binary]"
315 + "[foo/bar.jar, mode:100644, content:\r\n]",
316 indexState(CONTENT));
317 }
318 }
319
320 @Test
321 public void testCleanFilterEnvironment()
322 throws IOException, GitAPIException {
323 writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
324 writeTrashFile("src/a.txt", "foo");
325 File script = writeTempFile("echo $GIT_DIR; echo 1 >xyz");
326
327 try (Git git = new Git(db)) {
328 StoredConfig config = git.getRepository().getConfig();
329 config.setString("filter", "tstFilter", "clean",
330 "sh " + slashify(script.getPath()));
331 config.save();
332 git.add().addFilepattern("src/a.txt").call();
333
334 String gitDir = db.getDirectory().getAbsolutePath();
335 assertEquals("[src/a.txt, mode:100644, content:" + gitDir
336 + "\n]", indexState(CONTENT));
337 assertTrue(new File(db.getWorkTree(), "xyz").exists());
338 }
339 }
340
341 @Test
342 public void testMultipleCleanFilter() throws IOException, GitAPIException {
343 writeTrashFile(".gitattributes",
344 "*.txt filter=tstFilter\n*.tmp filter=tstFilter2");
345
346
347 writeTrashFile("src/a.tmp", "foo\n");
348 writeTrashFile("src/a.txt", "foo\n");
349 File script = writeTempFile("sed s/o/e/g");
350 File script2 = writeTempFile("sed s/f/x/g");
351
352 try (Git git = new Git(db)) {
353 StoredConfig config = git.getRepository().getConfig();
354 config.setString("filter", "tstFilter", "clean",
355 "sh " + slashify(script.getPath()));
356 config.setString("filter", "tstFilter2", "clean",
357 "sh " + slashify(script2.getPath()));
358 config.save();
359
360 git.add().addFilepattern("src/a.txt").addFilepattern("src/a.tmp")
361 .call();
362
363 assertEquals(
364 "[src/a.tmp, mode:100644, content:xoo\n][src/a.txt, mode:100644, content:fee\n]",
365 indexState(CONTENT));
366
367
368 }
369 }
370
371
372
373
374
375
376
377
378
379 @Test
380 public void testCommandInjection() throws IOException, GitAPIException {
381
382
383 writeTrashFile("; echo virus", "foo\n");
384 File script = writeTempFile("sed s/o/e/g");
385
386 try (Git git = new Git(db)) {
387 StoredConfig config = git.getRepository().getConfig();
388 config.setString("filter", "tstFilter", "clean",
389 "sh " + slashify(script.getPath()) + " %f");
390 writeTrashFile(".gitattributes", "* filter=tstFilter");
391
392 git.add().addFilepattern("; echo virus").call();
393
394
395 assertEquals("[; echo virus, mode:100644, content:fee\n]",
396 indexState(CONTENT));
397 }
398 }
399
400 @Test
401 public void testBadCleanFilter() throws IOException, GitAPIException {
402 writeTrashFile("a.txt", "foo");
403 File script = writeTempFile("sedfoo s/o/e/g");
404
405 try (Git git = new Git(db)) {
406 StoredConfig config = git.getRepository().getConfig();
407 config.setString("filter", "tstFilter", "clean",
408 "sh " + script.getPath());
409 config.save();
410 writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
411
412 try {
413 git.add().addFilepattern("a.txt").call();
414 fail("Didn't received the expected exception");
415 } catch (FilterFailedException e) {
416 assertEquals(127, e.getReturnCode());
417 }
418 }
419 }
420
421 @Test
422 public void testBadCleanFilter2() throws IOException, GitAPIException {
423 writeTrashFile("a.txt", "foo");
424 File script = writeTempFile("sed s/o/e/g");
425
426 try (Git git = new Git(db)) {
427 StoredConfig config = git.getRepository().getConfig();
428 config.setString("filter", "tstFilter", "clean",
429 "shfoo " + script.getPath());
430 config.save();
431 writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
432
433 try {
434 git.add().addFilepattern("a.txt").call();
435 fail("Didn't received the expected exception");
436 } catch (FilterFailedException e) {
437 assertEquals(127, e.getReturnCode());
438 }
439 }
440 }
441
442 @Test
443 public void testCleanFilterReturning12() throws IOException,
444 GitAPIException {
445 writeTrashFile("a.txt", "foo");
446 File script = writeTempFile("exit 12");
447
448 try (Git git = new Git(db)) {
449 StoredConfig config = git.getRepository().getConfig();
450 config.setString("filter", "tstFilter", "clean",
451 "sh " + slashify(script.getPath()));
452 config.save();
453 writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
454
455 try {
456 git.add().addFilepattern("a.txt").call();
457 fail("Didn't received the expected exception");
458 } catch (FilterFailedException e) {
459 assertEquals(12, e.getReturnCode());
460 }
461 }
462 }
463
464 @Test
465 public void testNotApplicableFilter() throws IOException, GitAPIException {
466 writeTrashFile("a.txt", "foo");
467 File script = writeTempFile("sed s/o/e/g");
468
469 try (Git git = new Git(db)) {
470 StoredConfig config = git.getRepository().getConfig();
471 config.setString("filter", "tstFilter", "something",
472 "sh " + script.getPath());
473 config.save();
474 writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
475
476 git.add().addFilepattern("a.txt").call();
477
478 assertEquals("[a.txt, mode:100644, content:foo]",
479 indexState(CONTENT));
480 }
481 }
482
483 private File writeTempFile(String body) throws IOException {
484 File f = File.createTempFile("AddCommandTest_", "");
485 JGitTestUtil.write(f, body);
486 return f;
487 }
488
489 @Test
490 public void testAddExistingSingleSmallFileWithNewLine() throws IOException,
491 GitAPIException {
492 File file = new File(db.getWorkTree(), "a.txt");
493 FileUtils.createNewFile(file);
494 PrintWriter writer = new PrintWriter(file);
495 writer.print("row1\r\nrow2");
496 writer.close();
497
498 try (Git git = new Git(db)) {
499 db.getConfig().setString("core", null, "autocrlf", "false");
500 git.add().addFilepattern("a.txt").call();
501 assertEquals("[a.txt, mode:100644, content:row1\r\nrow2]",
502 indexState(CONTENT));
503 db.getConfig().setString("core", null, "autocrlf", "true");
504 git.add().addFilepattern("a.txt").call();
505 assertEquals("[a.txt, mode:100644, content:row1\nrow2]",
506 indexState(CONTENT));
507 db.getConfig().setString("core", null, "autocrlf", "input");
508 git.add().addFilepattern("a.txt").call();
509 assertEquals("[a.txt, mode:100644, content:row1\nrow2]",
510 indexState(CONTENT));
511 }
512 }
513
514 @Test
515 public void testAddExistingSingleMediumSizeFileWithNewLine()
516 throws IOException, GitAPIException {
517 File file = new File(db.getWorkTree(), "a.txt");
518 FileUtils.createNewFile(file);
519 StringBuilder data = new StringBuilder();
520 for (int i = 0; i < 1000; ++i) {
521 data.append("row1\r\nrow2");
522 }
523 String crData = data.toString();
524 PrintWriter writer = new PrintWriter(file);
525 writer.print(crData);
526 writer.close();
527 String lfData = data.toString().replaceAll("\r", "");
528 try (Git git = new Git(db)) {
529 db.getConfig().setString("core", null, "autocrlf", "false");
530 git.add().addFilepattern("a.txt").call();
531 assertEquals("[a.txt, mode:100644, content:" + data + "]",
532 indexState(CONTENT));
533 db.getConfig().setString("core", null, "autocrlf", "true");
534 git.add().addFilepattern("a.txt").call();
535 assertEquals("[a.txt, mode:100644, content:" + lfData + "]",
536 indexState(CONTENT));
537 db.getConfig().setString("core", null, "autocrlf", "input");
538 git.add().addFilepattern("a.txt").call();
539 assertEquals("[a.txt, mode:100644, content:" + lfData + "]",
540 indexState(CONTENT));
541 }
542 }
543
544 @Test
545 public void testAddExistingSingleBinaryFile() throws IOException,
546 GitAPIException {
547 File file = new File(db.getWorkTree(), "a.txt");
548 FileUtils.createNewFile(file);
549 PrintWriter writer = new PrintWriter(file);
550 writer.print("row1\r\nrow2\u0000");
551 writer.close();
552
553 try (Git git = new Git(db)) {
554 db.getConfig().setString("core", null, "autocrlf", "false");
555 git.add().addFilepattern("a.txt").call();
556 assertEquals("[a.txt, mode:100644, content:row1\r\nrow2\u0000]",
557 indexState(CONTENT));
558 db.getConfig().setString("core", null, "autocrlf", "true");
559 git.add().addFilepattern("a.txt").call();
560 assertEquals("[a.txt, mode:100644, content:row1\r\nrow2\u0000]",
561 indexState(CONTENT));
562 db.getConfig().setString("core", null, "autocrlf", "input");
563 git.add().addFilepattern("a.txt").call();
564 assertEquals("[a.txt, mode:100644, content:row1\r\nrow2\u0000]",
565 indexState(CONTENT));
566 }
567 }
568
569 @Test
570 public void testAddExistingSingleFileInSubDir() throws IOException,
571 GitAPIException {
572 FileUtils.mkdir(new File(db.getWorkTree(), "sub"));
573 File file = new File(db.getWorkTree(), "sub/a.txt");
574 FileUtils.createNewFile(file);
575 PrintWriter writer = new PrintWriter(file);
576 writer.print("content");
577 writer.close();
578
579 try (Git git = new Git(db)) {
580 git.add().addFilepattern("sub/a.txt").call();
581
582 assertEquals(
583 "[sub/a.txt, mode:100644, content:content]",
584 indexState(CONTENT));
585 }
586 }
587
588 @Test
589 public void testAddExistingSingleFileTwice() throws IOException,
590 GitAPIException {
591 File file = new File(db.getWorkTree(), "a.txt");
592 FileUtils.createNewFile(file);
593 PrintWriter writer = new PrintWriter(file);
594 writer.print("content");
595 writer.close();
596
597 try (Git git = new Git(db)) {
598 DirCache dc = git.add().addFilepattern("a.txt").call();
599
600 dc.getEntry(0).getObjectId();
601
602 writer = new PrintWriter(file);
603 writer.print("other content");
604 writer.close();
605
606 dc = git.add().addFilepattern("a.txt").call();
607
608 assertEquals(
609 "[a.txt, mode:100644, content:other content]",
610 indexState(CONTENT));
611 }
612 }
613
614 @Test
615 public void testAddExistingSingleFileTwiceWithCommit() throws Exception {
616 File file = new File(db.getWorkTree(), "a.txt");
617 FileUtils.createNewFile(file);
618 PrintWriter writer = new PrintWriter(file);
619 writer.print("content");
620 writer.close();
621
622 try (Git git = new Git(db)) {
623 DirCache dc = git.add().addFilepattern("a.txt").call();
624
625 dc.getEntry(0).getObjectId();
626
627 git.commit().setMessage("commit a.txt").call();
628
629 writer = new PrintWriter(file);
630 writer.print("other content");
631 writer.close();
632
633 dc = git.add().addFilepattern("a.txt").call();
634
635 assertEquals(
636 "[a.txt, mode:100644, content:other content]",
637 indexState(CONTENT));
638 }
639 }
640
641 @Test
642 public void testAddRemovedFile() throws Exception {
643 File file = new File(db.getWorkTree(), "a.txt");
644 FileUtils.createNewFile(file);
645 PrintWriter writer = new PrintWriter(file);
646 writer.print("content");
647 writer.close();
648
649 try (Git git = new Git(db)) {
650 DirCache dc = git.add().addFilepattern("a.txt").call();
651
652 dc.getEntry(0).getObjectId();
653 FileUtils.delete(file);
654
655
656 dc = git.add().addFilepattern("a.txt").call();
657
658 assertEquals(
659 "[a.txt, mode:100644, content:content]",
660 indexState(CONTENT));
661 }
662 }
663
664 @Test
665 public void testAddRemovedCommittedFile() throws Exception {
666 File file = new File(db.getWorkTree(), "a.txt");
667 FileUtils.createNewFile(file);
668 PrintWriter writer = new PrintWriter(file);
669 writer.print("content");
670 writer.close();
671
672 try (Git git = new Git(db)) {
673 DirCache dc = git.add().addFilepattern("a.txt").call();
674
675 git.commit().setMessage("commit a.txt").call();
676
677 dc.getEntry(0).getObjectId();
678 FileUtils.delete(file);
679
680
681 dc = git.add().addFilepattern("a.txt").call();
682
683 assertEquals(
684 "[a.txt, mode:100644, content:content]",
685 indexState(CONTENT));
686 }
687 }
688
689 @Test
690 public void testAddWithConflicts() throws Exception {
691
692
693 File file = new File(db.getWorkTree(), "a.txt");
694 FileUtils.createNewFile(file);
695 PrintWriter writer = new PrintWriter(file);
696 writer.print("content");
697 writer.close();
698
699 File file2 = new File(db.getWorkTree(), "b.txt");
700 FileUtils.createNewFile(file2);
701 writer = new PrintWriter(file2);
702 writer.print("content b");
703 writer.close();
704
705 ObjectInserter newObjectInserter = db.newObjectInserter();
706 DirCache dc = db.lockDirCache();
707 DirCacheBuilder builder = dc.builder();
708
709 addEntryToBuilder("b.txt", file2, newObjectInserter, builder, 0);
710 addEntryToBuilder("a.txt", file, newObjectInserter, builder, 1);
711
712 writer = new PrintWriter(file);
713 writer.print("other content");
714 writer.close();
715 addEntryToBuilder("a.txt", file, newObjectInserter, builder, 3);
716
717 writer = new PrintWriter(file);
718 writer.print("our content");
719 writer.close();
720 addEntryToBuilder("a.txt", file, newObjectInserter, builder, 2)
721 .getObjectId();
722
723 builder.commit();
724
725 assertEquals(
726 "[a.txt, mode:100644, stage:1, content:content]" +
727 "[a.txt, mode:100644, stage:2, content:our content]" +
728 "[a.txt, mode:100644, stage:3, content:other content]" +
729 "[b.txt, mode:100644, content:content b]",
730 indexState(CONTENT));
731
732
733
734 try (Git git = new Git(db)) {
735 dc = git.add().addFilepattern("a.txt").call();
736
737 assertEquals(
738 "[a.txt, mode:100644, content:our content]" +
739 "[b.txt, mode:100644, content:content b]",
740 indexState(CONTENT));
741 }
742 }
743
744 @Test
745 public void testAddTwoFiles() throws Exception {
746 File file = new File(db.getWorkTree(), "a.txt");
747 FileUtils.createNewFile(file);
748 PrintWriter writer = new PrintWriter(file);
749 writer.print("content");
750 writer.close();
751
752 File file2 = new File(db.getWorkTree(), "b.txt");
753 FileUtils.createNewFile(file2);
754 writer = new PrintWriter(file2);
755 writer.print("content b");
756 writer.close();
757
758 try (Git git = new Git(db)) {
759 git.add().addFilepattern("a.txt").addFilepattern("b.txt").call();
760 assertEquals(
761 "[a.txt, mode:100644, content:content]" +
762 "[b.txt, mode:100644, content:content b]",
763 indexState(CONTENT));
764 }
765 }
766
767 @Test
768 public void testAddFolder() throws Exception {
769 FileUtils.mkdir(new File(db.getWorkTree(), "sub"));
770 File file = new File(db.getWorkTree(), "sub/a.txt");
771 FileUtils.createNewFile(file);
772 PrintWriter writer = new PrintWriter(file);
773 writer.print("content");
774 writer.close();
775
776 File file2 = new File(db.getWorkTree(), "sub/b.txt");
777 FileUtils.createNewFile(file2);
778 writer = new PrintWriter(file2);
779 writer.print("content b");
780 writer.close();
781
782 try (Git git = new Git(db)) {
783 git.add().addFilepattern("sub").call();
784 assertEquals(
785 "[sub/a.txt, mode:100644, content:content]" +
786 "[sub/b.txt, mode:100644, content:content b]",
787 indexState(CONTENT));
788 }
789 }
790
791 @Test
792 public void testAddIgnoredFile() throws Exception {
793 FileUtils.mkdir(new File(db.getWorkTree(), "sub"));
794 File file = new File(db.getWorkTree(), "sub/a.txt");
795 FileUtils.createNewFile(file);
796 PrintWriter writer = new PrintWriter(file);
797 writer.print("content");
798 writer.close();
799
800 File ignoreFile = new File(db.getWorkTree(), ".gitignore");
801 FileUtils.createNewFile(ignoreFile);
802 writer = new PrintWriter(ignoreFile);
803 writer.print("sub/b.txt");
804 writer.close();
805
806 File file2 = new File(db.getWorkTree(), "sub/b.txt");
807 FileUtils.createNewFile(file2);
808 writer = new PrintWriter(file2);
809 writer.print("content b");
810 writer.close();
811
812 try (Git git = new Git(db)) {
813 git.add().addFilepattern("sub").call();
814
815 assertEquals(
816 "[sub/a.txt, mode:100644, content:content]",
817 indexState(CONTENT));
818 }
819 }
820
821 @Test
822 public void testAddWholeRepo() throws Exception {
823 FileUtils.mkdir(new File(db.getWorkTree(), "sub"));
824 File file = new File(db.getWorkTree(), "sub/a.txt");
825 FileUtils.createNewFile(file);
826 PrintWriter writer = new PrintWriter(file);
827 writer.print("content");
828 writer.close();
829
830 File file2 = new File(db.getWorkTree(), "sub/b.txt");
831 FileUtils.createNewFile(file2);
832 writer = new PrintWriter(file2);
833 writer.print("content b");
834 writer.close();
835
836 try (Git git = new Git(db)) {
837 git.add().addFilepattern(".").call();
838 assertEquals(
839 "[sub/a.txt, mode:100644, content:content]" +
840 "[sub/b.txt, mode:100644, content:content b]",
841 indexState(CONTENT));
842 }
843 }
844
845
846
847
848
849 @Test
850 public void testAddWithoutParameterUpdate() throws Exception {
851 FileUtils.mkdir(new File(db.getWorkTree(), "sub"));
852 File file = new File(db.getWorkTree(), "sub/a.txt");
853 FileUtils.createNewFile(file);
854 PrintWriter writer = new PrintWriter(file);
855 writer.print("content");
856 writer.close();
857
858 File file2 = new File(db.getWorkTree(), "sub/b.txt");
859 FileUtils.createNewFile(file2);
860 writer = new PrintWriter(file2);
861 writer.print("content b");
862 writer.close();
863
864 try (Git git = new Git(db)) {
865 git.add().addFilepattern("sub").call();
866
867 assertEquals(
868 "[sub/a.txt, mode:100644, content:content]" +
869 "[sub/b.txt, mode:100644, content:content b]",
870 indexState(CONTENT));
871
872 git.commit().setMessage("commit").call();
873
874
875 File file3 = new File(db.getWorkTree(), "sub/c.txt");
876 FileUtils.createNewFile(file3);
877 writer = new PrintWriter(file3);
878 writer.print("content c");
879 writer.close();
880
881
882 writer = new PrintWriter(file);
883 writer.print("modified content");
884 writer.close();
885
886
887 FileUtils.delete(file2);
888
889 git.add().addFilepattern("sub").call();
890
891
892
893 assertEquals(
894 "[sub/a.txt, mode:100644, content:modified content]" +
895 "[sub/b.txt, mode:100644, content:content b]" +
896 "[sub/c.txt, mode:100644, content:content c]",
897 indexState(CONTENT));
898 }
899 }
900
901
902
903
904 @Test
905 public void testAddWithParameterUpdate() throws Exception {
906 FileUtils.mkdir(new File(db.getWorkTree(), "sub"));
907 File file = new File(db.getWorkTree(), "sub/a.txt");
908 FileUtils.createNewFile(file);
909 PrintWriter writer = new PrintWriter(file);
910 writer.print("content");
911 writer.close();
912
913 File file2 = new File(db.getWorkTree(), "sub/b.txt");
914 FileUtils.createNewFile(file2);
915 writer = new PrintWriter(file2);
916 writer.print("content b");
917 writer.close();
918
919 try (Git git = new Git(db)) {
920 git.add().addFilepattern("sub").call();
921
922 assertEquals(
923 "[sub/a.txt, mode:100644, content:content]" +
924 "[sub/b.txt, mode:100644, content:content b]",
925 indexState(CONTENT));
926
927 git.commit().setMessage("commit").call();
928
929
930 File file3 = new File(db.getWorkTree(), "sub/c.txt");
931 FileUtils.createNewFile(file3);
932 writer = new PrintWriter(file3);
933 writer.print("content c");
934 writer.close();
935
936
937 writer = new PrintWriter(file);
938 writer.print("modified content");
939 writer.close();
940
941 FileUtils.delete(file2);
942
943
944
945
946 git.add().addFilepattern("sub").setUpdate(true).call();
947
948 assertEquals(
949 "[sub/a.txt, mode:100644, content:modified content]",
950 indexState(CONTENT));
951 }
952 }
953
954 @Test
955 public void testAssumeUnchanged() throws Exception {
956 try (Git git = new Git(db)) {
957 String path = "a.txt";
958 writeTrashFile(path, "content");
959 git.add().addFilepattern(path).call();
960 String path2 = "b.txt";
961 writeTrashFile(path2, "content");
962 git.add().addFilepattern(path2).call();
963 git.commit().setMessage("commit").call();
964 assertEquals("[a.txt, mode:100644, content:"
965 + "content, assume-unchanged:false]"
966 + "[b.txt, mode:100644, content:content, "
967 + "assume-unchanged:false]", indexState(CONTENT
968 | ASSUME_UNCHANGED));
969 assumeUnchanged(path2);
970 assertEquals("[a.txt, mode:100644, content:content, "
971 + "assume-unchanged:false][b.txt, mode:100644, "
972 + "content:content, assume-unchanged:true]", indexState(CONTENT
973 | ASSUME_UNCHANGED));
974 writeTrashFile(path, "more content");
975 writeTrashFile(path2, "more content");
976
977 git.add().addFilepattern(".").call();
978
979 assertEquals("[a.txt, mode:100644, content:more content,"
980 + " assume-unchanged:false][b.txt, mode:100644,"
981 + " content:content, assume-unchanged:true]",
982 indexState(CONTENT
983 | ASSUME_UNCHANGED));
984 }
985 }
986
987 @Test
988 public void testReplaceFileWithDirectory()
989 throws IOException, NoFilepatternException, GitAPIException {
990 try (Git git = new Git(db)) {
991 writeTrashFile("df", "before replacement");
992 git.add().addFilepattern("df").call();
993 assertEquals("[df, mode:100644, content:before replacement]",
994 indexState(CONTENT));
995 FileUtils.delete(new File(db.getWorkTree(), "df"));
996 writeTrashFile("df/f", "after replacement");
997 git.add().addFilepattern("df").call();
998 assertEquals("[df/f, mode:100644, content:after replacement]",
999 indexState(CONTENT));
1000 }
1001 }
1002
1003 @Test
1004 public void testReplaceDirectoryWithFile()
1005 throws IOException, NoFilepatternException, GitAPIException {
1006 try (Git git = new Git(db)) {
1007 writeTrashFile("df/f", "before replacement");
1008 git.add().addFilepattern("df").call();
1009 assertEquals("[df/f, mode:100644, content:before replacement]",
1010 indexState(CONTENT));
1011 FileUtils.delete(new File(db.getWorkTree(), "df"), RECURSIVE);
1012 writeTrashFile("df", "after replacement");
1013 git.add().addFilepattern("df").call();
1014 assertEquals("[df, mode:100644, content:after replacement]",
1015 indexState(CONTENT));
1016 }
1017 }
1018
1019 @Test
1020 public void testReplaceFileByPartOfDirectory()
1021 throws IOException, NoFilepatternException, GitAPIException {
1022 try (Git git = new Git(db)) {
1023 writeTrashFile("src/main", "df", "before replacement");
1024 writeTrashFile("src/main", "z", "z");
1025 writeTrashFile("z", "z2");
1026 git.add().addFilepattern("src/main/df")
1027 .addFilepattern("src/main/z")
1028 .addFilepattern("z")
1029 .call();
1030 assertEquals(
1031 "[src/main/df, mode:100644, content:before replacement]" +
1032 "[src/main/z, mode:100644, content:z]" +
1033 "[z, mode:100644, content:z2]",
1034 indexState(CONTENT));
1035 FileUtils.delete(new File(db.getWorkTree(), "src/main/df"));
1036 writeTrashFile("src/main/df", "a", "after replacement");
1037 writeTrashFile("src/main/df", "b", "unrelated file");
1038 git.add().addFilepattern("src/main/df/a").call();
1039 assertEquals(
1040 "[src/main/df/a, mode:100644, content:after replacement]" +
1041 "[src/main/z, mode:100644, content:z]" +
1042 "[z, mode:100644, content:z2]",
1043 indexState(CONTENT));
1044 }
1045 }
1046
1047 @Test
1048 public void testReplaceDirectoryConflictsWithFile()
1049 throws IOException, NoFilepatternException, GitAPIException {
1050 DirCache dc = db.lockDirCache();
1051 try (ObjectInserter oi = db.newObjectInserter()) {
1052 DirCacheBuilder builder = dc.builder();
1053 File f = writeTrashFile("a", "df", "content");
1054 addEntryToBuilder("a", f, oi, builder, 1);
1055
1056 f = writeTrashFile("a", "df", "other content");
1057 addEntryToBuilder("a/df", f, oi, builder, 3);
1058
1059 f = writeTrashFile("a", "df", "our content");
1060 addEntryToBuilder("a/df", f, oi, builder, 2);
1061
1062 f = writeTrashFile("z", "z");
1063 addEntryToBuilder("z", f, oi, builder, 0);
1064 builder.commit();
1065 }
1066 assertEquals(
1067 "[a, mode:100644, stage:1, content:content]" +
1068 "[a/df, mode:100644, stage:2, content:our content]" +
1069 "[a/df, mode:100644, stage:3, content:other content]" +
1070 "[z, mode:100644, content:z]",
1071 indexState(CONTENT));
1072
1073 try (Git git = new Git(db)) {
1074 FileUtils.delete(new File(db.getWorkTree(), "a"), RECURSIVE);
1075 writeTrashFile("a", "merged");
1076 git.add().addFilepattern("a").call();
1077 assertEquals("[a, mode:100644, content:merged]" +
1078 "[z, mode:100644, content:z]",
1079 indexState(CONTENT));
1080 }
1081 }
1082
1083 @Test
1084 public void testExecutableRetention() throws Exception {
1085 StoredConfig config = db.getConfig();
1086 config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
1087 ConfigConstants.CONFIG_KEY_FILEMODE, true);
1088 config.save();
1089
1090 FS executableFs = new FS() {
1091
1092 @Override
1093 public boolean supportsExecute() {
1094 return true;
1095 }
1096
1097 @Override
1098 public boolean setExecute(File f, boolean canExec) {
1099 return true;
1100 }
1101
1102 @Override
1103 public ProcessBuilder runInShell(String cmd, String[] args) {
1104 return null;
1105 }
1106
1107 @Override
1108 public boolean retryFailedLockFileCommit() {
1109 return false;
1110 }
1111
1112 @Override
1113 public FS newInstance() {
1114 return this;
1115 }
1116
1117 @Override
1118 protected File discoverGitExe() {
1119 return null;
1120 }
1121
1122 @Override
1123 public boolean canExecute(File f) {
1124 try {
1125 return read(f).startsWith("binary:");
1126 } catch (IOException e) {
1127 return false;
1128 }
1129 }
1130
1131 @Override
1132 public boolean isCaseSensitive() {
1133 return false;
1134 }
1135 };
1136
1137 Git git = Git.open(db.getDirectory(), executableFs);
1138 String path = "a.txt";
1139 String path2 = "a.sh";
1140 writeTrashFile(path, "content");
1141 writeTrashFile(path2, "binary: content");
1142 git.add().addFilepattern(path).addFilepattern(path2).call();
1143 RevCommit commit1 = git.commit().setMessage("commit").call();
1144 try (TreeWalk walk = new TreeWalk(db)) {
1145 walk.addTree(commit1.getTree());
1146 walk.next();
1147 assertEquals(path2, walk.getPathString());
1148 assertEquals(FileMode.EXECUTABLE_FILE, walk.getFileMode(0));
1149 walk.next();
1150 assertEquals(path, walk.getPathString());
1151 assertEquals(FileMode.REGULAR_FILE, walk.getFileMode(0));
1152 }
1153
1154 config = db.getConfig();
1155 config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
1156 ConfigConstants.CONFIG_KEY_FILEMODE, false);
1157 config.save();
1158
1159 Git git2 = Git.open(db.getDirectory(), executableFs);
1160 writeTrashFile(path2, "content2");
1161 writeTrashFile(path, "binary: content2");
1162 git2.add().addFilepattern(path).addFilepattern(path2).call();
1163 RevCommit commit2 = git2.commit().setMessage("commit2").call();
1164 try (TreeWalk walk = new TreeWalk(db)) {
1165 walk.addTree(commit2.getTree());
1166 walk.next();
1167 assertEquals(path2, walk.getPathString());
1168 assertEquals(FileMode.EXECUTABLE_FILE, walk.getFileMode(0));
1169 walk.next();
1170 assertEquals(path, walk.getPathString());
1171 assertEquals(FileMode.REGULAR_FILE, walk.getFileMode(0));
1172 }
1173 }
1174
1175 @Test
1176 public void testAddGitlink() throws Exception {
1177 createNestedRepo("git-link-dir");
1178 try (Git git = new Git(db)) {
1179 git.add().addFilepattern("git-link-dir").call();
1180
1181 assertEquals(
1182 "[git-link-dir, mode:160000]",
1183 indexState(0));
1184 Set<String> untrackedFiles = git.status().call().getUntracked();
1185 assert (untrackedFiles.isEmpty());
1186 }
1187
1188 }
1189
1190 @Test
1191 public void testAddSubrepoWithDirNoGitlinks() throws Exception {
1192 createNestedRepo("nested-repo");
1193
1194
1195 StoredConfig config = db.getConfig();
1196 config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
1197 ConfigConstants.CONFIG_KEY_DIRNOGITLINKS, true);
1198 config.save();
1199
1200 assert (db.getConfig().get(WorkingTreeOptions.KEY).isDirNoGitLinks());
1201
1202 try (Git git = new Git(db)) {
1203 git.add().addFilepattern("nested-repo").call();
1204
1205 assertEquals(
1206 "[nested-repo/README1.md, mode:100644]" +
1207 "[nested-repo/README2.md, mode:100644]",
1208 indexState(0));
1209 }
1210
1211
1212
1213
1214 config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
1215 ConfigConstants.CONFIG_KEY_DIRNOGITLINKS, false);
1216 config.save();
1217
1218 writeTrashFile("nested-repo", "README3.md", "content");
1219
1220 try (Git git = new Git(db)) {
1221 git.add().addFilepattern("nested-repo").call();
1222
1223 assertEquals(
1224 "[nested-repo/README1.md, mode:100644]" +
1225 "[nested-repo/README2.md, mode:100644]" +
1226 "[nested-repo/README3.md, mode:100644]",
1227 indexState(0));
1228 }
1229 }
1230
1231 @Test
1232 public void testAddGitlinkDoesNotChange() throws Exception {
1233 createNestedRepo("nested-repo");
1234
1235 try (Git git = new Git(db)) {
1236 git.add().addFilepattern("nested-repo").call();
1237
1238 assertEquals(
1239 "[nested-repo, mode:160000]",
1240 indexState(0));
1241 }
1242
1243
1244 StoredConfig config = db.getConfig();
1245 config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
1246 ConfigConstants.CONFIG_KEY_DIRNOGITLINKS, true);
1247 config.save();
1248
1249 assert (db.getConfig().get(WorkingTreeOptions.KEY).isDirNoGitLinks());
1250
1251 try (Git git = new Git(db)) {
1252 git.add().addFilepattern("nested-repo").call();
1253
1254 assertEquals(
1255 "[nested-repo, mode:160000]",
1256 indexState(0));
1257 }
1258 }
1259
1260 private static DirCacheEntry addEntryToBuilder(String path, File file,
1261 ObjectInserter newObjectInserter, DirCacheBuilder builder, int stage)
1262 throws IOException {
1263 FileInputStream inputStream = new FileInputStream(file);
1264 ObjectId id = newObjectInserter.insert(
1265 Constants.OBJ_BLOB, file.length(), inputStream);
1266 inputStream.close();
1267 DirCacheEntry entry = new DirCacheEntry(path, stage);
1268 entry.setObjectId(id);
1269 entry.setFileMode(FileMode.REGULAR_FILE);
1270 entry.setLastModified(file.lastModified());
1271 entry.setLength((int) file.length());
1272
1273 builder.add(entry);
1274 return entry;
1275 }
1276
1277 private void assumeUnchanged(String path) throws IOException {
1278 final DirCache dirc = db.lockDirCache();
1279 final DirCacheEntry ent = dirc.getEntry(path);
1280 if (ent != null)
1281 ent.setAssumeValid(true);
1282 dirc.write();
1283 if (!dirc.commit())
1284 throw new IOException("could not commit");
1285 }
1286
1287 private void createNestedRepo(String path) throws IOException {
1288 File gitLinkDir = new File(db.getWorkTree(), path);
1289 FileUtils.mkdir(gitLinkDir);
1290
1291 FileRepositoryBuilder nestedBuilder = new FileRepositoryBuilder();
1292 nestedBuilder.setWorkTree(gitLinkDir);
1293
1294 Repository nestedRepo = nestedBuilder.build();
1295 nestedRepo.create();
1296
1297 writeTrashFile(path, "README1.md", "content");
1298 writeTrashFile(path, "README2.md", "content");
1299
1300
1301 try (Git git = new Git(nestedRepo)) {
1302 git.add().addFilepattern(".").call();
1303 git.commit().setMessage("subrepo commit").call();
1304 } catch (GitAPIException e) {
1305 throw new RuntimeException(e);
1306 }
1307 }
1308 }