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