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