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.treewalk;
45
46 import static java.nio.charset.StandardCharsets.UTF_8;
47 import static org.junit.Assert.assertEquals;
48 import static org.junit.Assert.assertFalse;
49 import static org.junit.Assert.assertNotNull;
50 import static org.junit.Assert.assertTrue;
51 import static org.junit.Assume.assumeNoException;
52
53 import java.io.File;
54 import java.io.IOException;
55 import java.nio.file.InvalidPathException;
56 import java.security.MessageDigest;
57 import java.time.Instant;
58
59 import org.eclipse.jgit.api.Git;
60 import org.eclipse.jgit.api.ResetCommand.ResetType;
61 import org.eclipse.jgit.dircache.DirCache;
62 import org.eclipse.jgit.dircache.DirCacheCheckout;
63 import org.eclipse.jgit.dircache.DirCacheEditor;
64 import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
65 import org.eclipse.jgit.dircache.DirCacheEntry;
66 import org.eclipse.jgit.dircache.DirCacheIterator;
67 import org.eclipse.jgit.errors.CorruptObjectException;
68 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
69 import org.eclipse.jgit.errors.MissingObjectException;
70 import org.eclipse.jgit.junit.JGitTestUtil;
71 import org.eclipse.jgit.junit.RepositoryTestCase;
72 import org.eclipse.jgit.lib.ConfigConstants;
73 import org.eclipse.jgit.lib.Constants;
74 import org.eclipse.jgit.lib.FileMode;
75 import org.eclipse.jgit.lib.ObjectId;
76 import org.eclipse.jgit.lib.ObjectInserter;
77 import org.eclipse.jgit.lib.ObjectReader;
78 import org.eclipse.jgit.lib.Repository;
79 import org.eclipse.jgit.revwalk.RevCommit;
80 import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
81 import org.eclipse.jgit.treewalk.WorkingTreeIterator.MetadataDiff;
82 import org.eclipse.jgit.treewalk.filter.PathFilter;
83 import org.eclipse.jgit.util.FS;
84 import org.eclipse.jgit.util.FileUtils;
85 import org.eclipse.jgit.util.RawParseUtils;
86 import org.junit.Before;
87 import org.junit.Test;
88
89 public class FileTreeIteratorTest extends RepositoryTestCase {
90 private final String[] paths = { "a,", "a,b", "a/b", "a0b" };
91
92 private Instant[] mtime;
93
94 @Override
95 @Before
96 public void setUp() throws Exception {
97 super.setUp();
98
99
100
101
102
103
104
105 mtime = new Instant[paths.length];
106 for (int i = paths.length - 1; i >= 0; i--) {
107 final String s = paths[i];
108 writeTrashFile(s, s);
109 mtime[i] = db.getFS().lastModifiedInstant(new File(trash, s));
110 }
111 }
112
113 @Test
114 public void testGetEntryContentLength() throws Exception {
115 final FileTreeIterator fti = new FileTreeIterator(db);
116 fti.next(1);
117 assertEquals(3, fti.getEntryContentLength());
118 fti.back(1);
119 assertEquals(2, fti.getEntryContentLength());
120 fti.next(1);
121 assertEquals(3, fti.getEntryContentLength());
122 fti.reset();
123 assertEquals(2, fti.getEntryContentLength());
124 }
125
126 @Test
127 public void testEmptyIfRootIsFile() throws Exception {
128 final File r = new File(trash, paths[0]);
129 assertTrue(r.isFile());
130 final FileTreeIterator fti = new FileTreeIterator(r, db.getFS(),
131 db.getConfig().get(WorkingTreeOptions.KEY));
132 assertTrue(fti.first());
133 assertTrue(fti.eof());
134 }
135
136 @Test
137 public void testEmptyIfRootDoesNotExist() throws Exception {
138 final File r = new File(trash, "not-existing-file");
139 assertFalse(r.exists());
140 final FileTreeIterator fti = new FileTreeIterator(r, db.getFS(),
141 db.getConfig().get(WorkingTreeOptions.KEY));
142 assertTrue(fti.first());
143 assertTrue(fti.eof());
144 }
145
146 @Test
147 public void testEmptyIfRootIsEmpty() throws Exception {
148 final File r = new File(trash, "not-existing-file");
149 assertFalse(r.exists());
150 FileUtils.mkdir(r);
151
152 final FileTreeIterator fti = new FileTreeIterator(r, db.getFS(),
153 db.getConfig().get(WorkingTreeOptions.KEY));
154 assertTrue(fti.first());
155 assertTrue(fti.eof());
156 }
157
158 @Test
159 public void testEmptyIteratorOnEmptyDirectory() throws Exception {
160 String nonExistingFileName = "not-existing-file";
161 final File r = new File(trash, nonExistingFileName);
162 assertFalse(r.exists());
163 FileUtils.mkdir(r);
164
165 final FileTreeIterator parent = new FileTreeIterator(db);
166
167 while (!parent.getEntryPathString().equals(nonExistingFileName))
168 parent.next(1);
169
170 final FileTreeIterator childIter = new FileTreeIterator(parent, r,
171 db.getFS());
172 assertTrue(childIter.first());
173 assertTrue(childIter.eof());
174
175 String parentPath = parent.getEntryPathString();
176 assertEquals(nonExistingFileName, parentPath);
177
178
179
180 String childPath = childIter.getEntryPathString();
181
182
183 EmptyTreeIterator e = childIter.createEmptyTreeIterator();
184 assertNotNull(e);
185
186
187
188
189 assertEquals(parentPath, parent.getEntryPathString());
190 assertEquals(parentPath + "/", childPath);
191 assertEquals(parentPath + "/", childIter.getEntryPathString());
192 assertEquals(childPath + "/", e.getEntryPathString());
193 }
194
195 @Test
196 public void testSimpleIterate() throws Exception {
197 final FileTreeIterator top = new FileTreeIterator(trash, db.getFS(),
198 db.getConfig().get(WorkingTreeOptions.KEY));
199
200 assertTrue(top.first());
201 assertFalse(top.eof());
202 assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode);
203 assertEquals(paths[0], nameOf(top));
204 assertEquals(paths[0].length(), top.getEntryLength());
205 assertEquals(mtime[0], top.getEntryLastModifiedInstant());
206
207 top.next(1);
208 assertFalse(top.first());
209 assertFalse(top.eof());
210 assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode);
211 assertEquals(paths[1], nameOf(top));
212 assertEquals(paths[1].length(), top.getEntryLength());
213 assertEquals(mtime[1], top.getEntryLastModifiedInstant());
214
215 top.next(1);
216 assertFalse(top.first());
217 assertFalse(top.eof());
218 assertEquals(FileMode.TREE.getBits(), top.mode);
219
220 final ObjectReader reader = db.newObjectReader();
221 final AbstractTreeIterator sub = top.createSubtreeIterator(reader);
222 assertTrue(sub instanceof FileTreeIterator);
223 final FileTreeIterator subfti = (FileTreeIterator) sub;
224 assertTrue(sub.first());
225 assertFalse(sub.eof());
226 assertEquals(paths[2], nameOf(sub));
227 assertEquals(paths[2].length(), subfti.getEntryLength());
228 assertEquals(mtime[2], subfti.getEntryLastModifiedInstant());
229
230 sub.next(1);
231 assertTrue(sub.eof());
232
233 top.next(1);
234 assertFalse(top.first());
235 assertFalse(top.eof());
236 assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode);
237 assertEquals(paths[3], nameOf(top));
238 assertEquals(paths[3].length(), top.getEntryLength());
239 assertEquals(mtime[3], top.getEntryLastModifiedInstant());
240
241 top.next(1);
242 assertTrue(top.eof());
243 }
244
245 @Test
246 public void testComputeFileObjectId() throws Exception {
247 final FileTreeIterator top = new FileTreeIterator(trash, db.getFS(),
248 db.getConfig().get(WorkingTreeOptions.KEY));
249
250 final MessageDigest md = Constants.newMessageDigest();
251 md.update(Constants.encodeASCII(Constants.TYPE_BLOB));
252 md.update((byte) ' ');
253 md.update(Constants.encodeASCII(paths[0].length()));
254 md.update((byte) 0);
255 md.update(Constants.encode(paths[0]));
256 final ObjectId expect = ObjectId.fromRaw(md.digest());
257
258 assertEquals(expect, top.getEntryObjectId());
259
260
261
262 FileUtils.delete(new File(trash, paths[0]));
263 assertEquals(expect, top.getEntryObjectId());
264 }
265
266 @Test
267 public void testDirCacheMatchingId() throws Exception {
268 File f = writeTrashFile("file", "content");
269 try (Git git = new Git(db)) {
270 writeTrashFile("file", "content");
271 fsTick(f);
272 git.add().addFilepattern("file").call();
273 }
274 DirCacheEntry dce = db.readDirCache().getEntry("file");
275 TreeWalk tw = new TreeWalk(db);
276 FileTreeIterator fti = new FileTreeIterator(trash, db.getFS(), db
277 .getConfig().get(WorkingTreeOptions.KEY));
278 tw.addTree(fti);
279 DirCacheIterator dci = new DirCacheIterator(db.readDirCache());
280 tw.addTree(dci);
281 fti.setDirCacheIterator(tw, 1);
282 while (tw.next() && !tw.getPathString().equals("file")) {
283
284 }
285 assertEquals(MetadataDiff.EQUAL, fti.compareMetadata(dce));
286 ObjectId fromRaw = ObjectId.fromRaw(fti.idBuffer(), fti.idOffset());
287 assertEquals("6b584e8ece562ebffc15d38808cd6b98fc3d97ea",
288 fromRaw.getName());
289 try (ObjectReader objectReader = db.newObjectReader()) {
290 assertFalse(fti.isModified(dce, false, objectReader));
291 }
292 }
293
294 @Test
295 public void testTreewalkEnterSubtree() throws Exception {
296 try (Git git = new Git(db); TreeWalk tw = new TreeWalk(db)) {
297 writeTrashFile("b/c", "b/c");
298 writeTrashFile("z/.git", "gitdir: /tmp/somewhere");
299 git.add().addFilepattern(".").call();
300 git.rm().addFilepattern("a,").addFilepattern("a,b")
301 .addFilepattern("a0b").call();
302 assertEquals("[a/b, mode:100644][b/c, mode:100644][z, mode:160000]",
303 indexState(0));
304 FileUtils.delete(new File(db.getWorkTree(), "b"),
305 FileUtils.RECURSIVE);
306
307 tw.addTree(new DirCacheIterator(db.readDirCache()));
308 tw.addTree(new FileTreeIterator(db));
309 assertTrue(tw.next());
310 assertEquals("a", tw.getPathString());
311 tw.enterSubtree();
312 tw.next();
313 assertEquals("a/b", tw.getPathString());
314 tw.next();
315 assertEquals("b", tw.getPathString());
316 tw.enterSubtree();
317 tw.next();
318 assertEquals("b/c", tw.getPathString());
319 assertNotNull(tw.getTree(0, AbstractTreeIterator.class));
320 assertNotNull(tw.getTree(EmptyTreeIterator.class));
321 }
322 }
323
324 @Test
325 public void testIsModifiedSymlinkAsFile() throws Exception {
326 writeTrashFile("symlink", "content");
327 try (Git git = new Git(db)) {
328 db.getConfig().setString(ConfigConstants.CONFIG_CORE_SECTION, null,
329 ConfigConstants.CONFIG_KEY_SYMLINKS, "false");
330 git.add().addFilepattern("symlink").call();
331 git.commit().setMessage("commit").call();
332 }
333
334
335 DirCacheEntry dce = db.readDirCache().getEntry("symlink");
336 dce.setFileMode(FileMode.SYMLINK);
337 try (ObjectReader objectReader = db.newObjectReader()) {
338 DirCacheCheckout.checkoutEntry(db, dce, objectReader, false, null);
339
340 FileTreeIterator fti = new FileTreeIterator(trash, db.getFS(),
341 db.getConfig().get(WorkingTreeOptions.KEY));
342 while (!fti.getEntryPathString().equals("symlink"))
343 fti.next(1);
344 assertFalse(fti.isModified(dce, false, objectReader));
345 }
346 }
347
348 @Test
349 public void testIsModifiedFileSmudged() throws Exception {
350 File f = writeTrashFile("file", "content");
351 FS fs = db.getFS();
352 try (Git git = new Git(db)) {
353
354
355 fsTick(f);
356 writeTrashFile("file", "content");
357 Instant lastModified = fs.lastModifiedInstant(f);
358 git.add().addFilepattern("file").call();
359 writeTrashFile("file", "conten2");
360 fs.setLastModified(f.toPath(), lastModified);
361
362
363
364
365 fs.setLastModified(db.getIndexFile().toPath(), lastModified);
366 }
367 DirCacheEntry dce = db.readDirCache().getEntry("file");
368 FileTreeIterator fti = new FileTreeIterator(trash, db.getFS(), db
369 .getConfig().get(WorkingTreeOptions.KEY));
370 while (!fti.getEntryPathString().equals("file"))
371 fti.next(1);
372
373
374 assertEquals(MetadataDiff.SMUDGED, fti.compareMetadata(dce));
375 try (ObjectReader objectReader = db.newObjectReader()) {
376 assertTrue(fti.isModified(dce, false, objectReader));
377 }
378 }
379
380 @Test
381 public void submoduleHeadMatchesIndex() throws Exception {
382 try (Git git = new Git(db);
383 TreeWalk walk = new TreeWalk(db)) {
384 writeTrashFile("file.txt", "content");
385 git.add().addFilepattern("file.txt").call();
386 final RevCommit id = git.commit().setMessage("create file").call();
387 final String path = "sub";
388 DirCache cache = db.lockDirCache();
389 DirCacheEditor editor = cache.editor();
390 editor.add(new PathEdit(path) {
391
392 @Override
393 public void apply(DirCacheEntry ent) {
394 ent.setFileMode(FileMode.GITLINK);
395 ent.setObjectId(id);
396 }
397 });
398 editor.commit();
399
400 Git.cloneRepository().setURI(db.getDirectory().toURI().toString())
401 .setDirectory(new File(db.getWorkTree(), path)).call()
402 .getRepository().close();
403
404 DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache());
405 FileTreeIterator workTreeIter = new FileTreeIterator(db);
406 walk.addTree(indexIter);
407 walk.addTree(workTreeIter);
408 walk.setFilter(PathFilter.create(path));
409
410 assertTrue(walk.next());
411 assertTrue(indexIter.idEqual(workTreeIter));
412 }
413 }
414
415 @Test
416 public void submoduleWithNoGitDirectory() throws Exception {
417 try (Git git = new Git(db);
418 TreeWalk walk = new TreeWalk(db)) {
419 writeTrashFile("file.txt", "content");
420 git.add().addFilepattern("file.txt").call();
421 final RevCommit id = git.commit().setMessage("create file").call();
422 final String path = "sub";
423 DirCache cache = db.lockDirCache();
424 DirCacheEditor editor = cache.editor();
425 editor.add(new PathEdit(path) {
426
427 @Override
428 public void apply(DirCacheEntry ent) {
429 ent.setFileMode(FileMode.GITLINK);
430 ent.setObjectId(id);
431 }
432 });
433 editor.commit();
434
435 File submoduleRoot = new File(db.getWorkTree(), path);
436 assertTrue(submoduleRoot.mkdir());
437 assertTrue(new File(submoduleRoot, Constants.DOT_GIT).mkdir());
438
439 DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache());
440 FileTreeIterator workTreeIter = new FileTreeIterator(db);
441 walk.addTree(indexIter);
442 walk.addTree(workTreeIter);
443 walk.setFilter(PathFilter.create(path));
444
445 assertTrue(walk.next());
446 assertFalse(indexIter.idEqual(workTreeIter));
447 assertEquals(ObjectId.zeroId(), workTreeIter.getEntryObjectId());
448 }
449 }
450
451 @Test
452 public void submoduleWithNoHead() throws Exception {
453 try (Git git = new Git(db);
454 TreeWalk walk = new TreeWalk(db)) {
455 writeTrashFile("file.txt", "content");
456 git.add().addFilepattern("file.txt").call();
457 final RevCommit id = git.commit().setMessage("create file").call();
458 final String path = "sub";
459 DirCache cache = db.lockDirCache();
460 DirCacheEditor editor = cache.editor();
461 editor.add(new PathEdit(path) {
462
463 @Override
464 public void apply(DirCacheEntry ent) {
465 ent.setFileMode(FileMode.GITLINK);
466 ent.setObjectId(id);
467 }
468 });
469 editor.commit();
470
471 assertNotNull(Git.init().setDirectory(new File(db.getWorkTree(), path))
472 .call().getRepository());
473
474 DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache());
475 FileTreeIterator workTreeIter = new FileTreeIterator(db);
476 walk.addTree(indexIter);
477 walk.addTree(workTreeIter);
478 walk.setFilter(PathFilter.create(path));
479
480 assertTrue(walk.next());
481 assertFalse(indexIter.idEqual(workTreeIter));
482 assertEquals(ObjectId.zeroId(), workTreeIter.getEntryObjectId());
483 }
484 }
485
486 @Test
487 public void submoduleDirectoryIterator() throws Exception {
488 try (Git git = new Git(db);
489 TreeWalk walk = new TreeWalk(db)) {
490 writeTrashFile("file.txt", "content");
491 git.add().addFilepattern("file.txt").call();
492 final RevCommit id = git.commit().setMessage("create file").call();
493 final String path = "sub";
494 DirCache cache = db.lockDirCache();
495 DirCacheEditor editor = cache.editor();
496 editor.add(new PathEdit(path) {
497
498 @Override
499 public void apply(DirCacheEntry ent) {
500 ent.setFileMode(FileMode.GITLINK);
501 ent.setObjectId(id);
502 }
503 });
504 editor.commit();
505
506 Git.cloneRepository().setURI(db.getDirectory().toURI().toString())
507 .setDirectory(new File(db.getWorkTree(), path)).call()
508 .getRepository().close();
509
510 DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache());
511 FileTreeIterator workTreeIter = new FileTreeIterator(db.getWorkTree(),
512 db.getFS(), db.getConfig().get(WorkingTreeOptions.KEY));
513 walk.addTree(indexIter);
514 walk.addTree(workTreeIter);
515 walk.setFilter(PathFilter.create(path));
516
517 assertTrue(walk.next());
518 assertTrue(indexIter.idEqual(workTreeIter));
519 }
520 }
521
522 @Test
523 public void submoduleNestedWithHeadMatchingIndex() throws Exception {
524 try (Git git = new Git(db);
525 TreeWalk walk = new TreeWalk(db)) {
526 writeTrashFile("file.txt", "content");
527 git.add().addFilepattern("file.txt").call();
528 final RevCommit id = git.commit().setMessage("create file").call();
529 final String path = "sub/dir1/dir2";
530 DirCache cache = db.lockDirCache();
531 DirCacheEditor editor = cache.editor();
532 editor.add(new PathEdit(path) {
533
534 @Override
535 public void apply(DirCacheEntry ent) {
536 ent.setFileMode(FileMode.GITLINK);
537 ent.setObjectId(id);
538 }
539 });
540 editor.commit();
541
542 Git.cloneRepository().setURI(db.getDirectory().toURI().toString())
543 .setDirectory(new File(db.getWorkTree(), path)).call()
544 .getRepository().close();
545
546 DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache());
547 FileTreeIterator workTreeIter = new FileTreeIterator(db);
548 walk.addTree(indexIter);
549 walk.addTree(workTreeIter);
550 walk.setFilter(PathFilter.create(path));
551
552 assertTrue(walk.next());
553 assertTrue(indexIter.idEqual(workTreeIter));
554 }
555 }
556
557 @Test
558 public void idOffset() throws Exception {
559 try (Git git = new Git(db);
560 TreeWalk tw = new TreeWalk(db)) {
561 writeTrashFile("fileAinfsonly", "A");
562 File fileBinindex = writeTrashFile("fileBinindex", "B");
563 fsTick(fileBinindex);
564 git.add().addFilepattern("fileBinindex").call();
565 writeTrashFile("fileCinfsonly", "C");
566 DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache());
567 FileTreeIterator workTreeIter = new FileTreeIterator(db);
568 tw.addTree(indexIter);
569 tw.addTree(workTreeIter);
570 workTreeIter.setDirCacheIterator(tw, 0);
571 assertEntry("d46c305e85b630558ee19cc47e73d2e5c8c64cdc", "a,", tw);
572 assertEntry("58ee403f98538ec02409538b3f80adf610accdec", "a,b", tw);
573 assertEntry("0000000000000000000000000000000000000000", "a", tw);
574 assertEntry("b8d30ff397626f0f1d3538d66067edf865e201d6", "a0b", tw);
575
576
577 assertEntry("8c7e5a667f1b771847fe88c01c3de34413a1b220",
578 "fileAinfsonly", tw);
579 assertEntry("7371f47a6f8bd23a8fa1a8b2a9479cdd76380e54", "fileBinindex",
580 tw);
581 assertEntry("96d80cd6c4e7158dbebd0849f4fb7ce513e5828c",
582 "fileCinfsonly", tw);
583 assertFalse(tw.next());
584 }
585 }
586
587 private final FileTreeIterator.FileModeStrategy NO_GITLINKS_STRATEGY =
588 new FileTreeIterator.FileModeStrategy() {
589 @Override
590 public FileMode getMode(File f, FS.Attributes attributes) {
591 if (attributes.isSymbolicLink()) {
592 return FileMode.SYMLINK;
593 } else if (attributes.isDirectory()) {
594
595
596
597
598
599 return FileMode.TREE;
600 } else if (attributes.isExecutable()) {
601 return FileMode.EXECUTABLE_FILE;
602 } else {
603 return FileMode.REGULAR_FILE;
604 }
605 }
606 };
607
608 private Repository createNestedRepo() throws IOException {
609 File gitdir = createUniqueTestGitDir(false);
610 FileRepositoryBuilder builder = new FileRepositoryBuilder();
611 builder.setGitDir(gitdir);
612 Repository nestedRepo = builder.build();
613 nestedRepo.create();
614
615 JGitTestUtil.writeTrashFile(nestedRepo, "sub", "a.txt", "content");
616
617 File nestedRepoPath = new File(nestedRepo.getWorkTree(), "sub/nested");
618 FileRepositoryBuilder nestedBuilder = new FileRepositoryBuilder();
619 nestedBuilder.setWorkTree(nestedRepoPath);
620 nestedBuilder.build().create();
621
622 JGitTestUtil.writeTrashFile(nestedRepo, "sub/nested", "b.txt",
623 "content b");
624
625 return nestedRepo;
626 }
627
628 @Test
629 public void testCustomFileModeStrategy() throws Exception {
630 Repository nestedRepo = createNestedRepo();
631
632 try (Git git = new Git(nestedRepo)) {
633
634 WorkingTreeIterator customIterator = new FileTreeIterator(
635 nestedRepo, NO_GITLINKS_STRATEGY);
636 git.add().setWorkingTreeIterator(customIterator).addFilepattern(".")
637 .call();
638 assertEquals(
639 "[sub/a.txt, mode:100644, content:content]"
640 + "[sub/nested/b.txt, mode:100644, content:content b]",
641 indexState(nestedRepo, CONTENT));
642 }
643 }
644
645 @Test
646 public void testCustomFileModeStrategyFromParentIterator() throws Exception {
647 Repository nestedRepo = createNestedRepo();
648
649 try (Git git = new Git(nestedRepo)) {
650 FileTreeIterator customIterator = new FileTreeIterator(nestedRepo,
651 NO_GITLINKS_STRATEGY);
652 File r = new File(nestedRepo.getWorkTree(), "sub");
653
654
655
656
657
658 FileTreeIterator childIterator = new FileTreeIterator(
659 customIterator, r, nestedRepo.getFS());
660 git.add().setWorkingTreeIterator(childIterator).addFilepattern(".")
661 .call();
662 assertEquals(
663 "[sub/a.txt, mode:100644, content:content]"
664 + "[sub/nested/b.txt, mode:100644, content:content b]",
665 indexState(nestedRepo, CONTENT));
666 }
667 }
668
669 @Test
670 public void testFileModeSymLinkIsNotATree() throws IOException {
671 org.junit.Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
672 FS fs = db.getFS();
673
674 writeTrashFile("mål/data", "targetdata");
675 File file = new File(trash, "länk");
676
677 try {
678 file.toPath();
679 } catch (InvalidPathException e) {
680
681
682
683
684
685
686 assumeNoException(e);
687 }
688
689 fs.createSymLink(file, "mål");
690 FileTreeIterator fti = new FileTreeIterator(db);
691 assertFalse(fti.eof());
692 while (!fti.getEntryPathString().equals("länk")) {
693 fti.next(1);
694 }
695 assertEquals("länk", fti.getEntryPathString());
696 assertEquals(FileMode.SYMLINK, fti.getEntryFileMode());
697 fti.next(1);
698 assertFalse(fti.eof());
699 assertEquals("mål", fti.getEntryPathString());
700 assertEquals(FileMode.TREE, fti.getEntryFileMode());
701 fti.next(1);
702 assertTrue(fti.eof());
703 }
704
705 @Test
706 public void testSymlinkNotModifiedThoughNormalized() throws Exception {
707 DirCache dc = db.lockDirCache();
708 DirCacheEditor dce = dc.editor();
709 final String UNNORMALIZED = "target/";
710 final byte[] UNNORMALIZED_BYTES = Constants.encode(UNNORMALIZED);
711 try (ObjectInserter oi = db.newObjectInserter()) {
712 final ObjectId linkid = oi.insert(Constants.OBJ_BLOB,
713 UNNORMALIZED_BYTES, 0, UNNORMALIZED_BYTES.length);
714 dce.add(new DirCacheEditor.PathEdit("link") {
715 @Override
716 public void apply(DirCacheEntry ent) {
717 ent.setFileMode(FileMode.SYMLINK);
718 ent.setObjectId(linkid);
719 ent.setLength(UNNORMALIZED_BYTES.length);
720 }
721 });
722 assertTrue(dce.commit());
723 }
724 try (Git git = new Git(db)) {
725 git.commit().setMessage("Adding link").call();
726 git.reset().setMode(ResetType.HARD).call();
727 DirCacheIterator dci = new DirCacheIterator(db.readDirCache());
728 FileTreeIterator fti = new FileTreeIterator(db);
729
730
731 while (!fti.getEntryPathString().equals("link")) {
732 fti.next(1);
733 }
734 assertEquals("link", fti.getEntryPathString());
735 assertEquals("link", dci.getEntryPathString());
736
737
738 assertFalse(fti.isModified(dci.getDirCacheEntry(), true,
739 db.newObjectReader()));
740 }
741 }
742
743
744
745
746
747
748
749 @Test
750 public void testSymlinkModifiedNotNormalized() throws Exception {
751 DirCache dc = db.lockDirCache();
752 DirCacheEditor dce = dc.editor();
753 final String NORMALIZED = "target";
754 final byte[] NORMALIZED_BYTES = Constants.encode(NORMALIZED);
755 try (ObjectInserter oi = db.newObjectInserter()) {
756 final ObjectId linkid = oi.insert(Constants.OBJ_BLOB,
757 NORMALIZED_BYTES, 0, NORMALIZED_BYTES.length);
758 dce.add(new DirCacheEditor.PathEdit("link") {
759 @Override
760 public void apply(DirCacheEntry ent) {
761 ent.setFileMode(FileMode.SYMLINK);
762 ent.setObjectId(linkid);
763 ent.setLength(NORMALIZED_BYTES.length);
764 }
765 });
766 assertTrue(dce.commit());
767 }
768 try (Git git = new Git(db)) {
769 git.commit().setMessage("Adding link").call();
770 git.reset().setMode(ResetType.HARD).call();
771 DirCacheIterator dci = new DirCacheIterator(db.readDirCache());
772 FileTreeIterator fti = new FileTreeIterator(db);
773
774
775 while (!fti.getEntryPathString().equals("link")) {
776 fti.next(1);
777 }
778 assertEquals("link", fti.getEntryPathString());
779 assertEquals("link", dci.getEntryPathString());
780
781
782 assertFalse(fti.isModified(dci.getDirCacheEntry(), true,
783 db.newObjectReader()));
784 }
785 }
786
787
788
789
790
791
792
793 @Test
794 public void testSymlinkActuallyModified() throws Exception {
795 org.junit.Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
796 final String NORMALIZED = "target";
797 final byte[] NORMALIZED_BYTES = Constants.encode(NORMALIZED);
798 try (ObjectInserter oi = db.newObjectInserter()) {
799 final ObjectId linkid = oi.insert(Constants.OBJ_BLOB,
800 NORMALIZED_BYTES, 0, NORMALIZED_BYTES.length);
801 DirCache dc = db.lockDirCache();
802 DirCacheEditor dce = dc.editor();
803 dce.add(new DirCacheEditor.PathEdit("link") {
804 @Override
805 public void apply(DirCacheEntry ent) {
806 ent.setFileMode(FileMode.SYMLINK);
807 ent.setObjectId(linkid);
808 ent.setLength(NORMALIZED_BYTES.length);
809 }
810 });
811 assertTrue(dce.commit());
812 }
813 try (Git git = new Git(db)) {
814 git.commit().setMessage("Adding link").call();
815 git.reset().setMode(ResetType.HARD).call();
816
817 FileUtils.delete(new File(trash, "link"), FileUtils.NONE);
818 FS.DETECTED.createSymLink(new File(trash, "link"), "newtarget");
819 DirCacheIterator dci = new DirCacheIterator(db.readDirCache());
820 FileTreeIterator fti = new FileTreeIterator(db);
821
822
823 while (!fti.getEntryPathString().equals("link")) {
824 fti.next(1);
825 }
826 assertEquals("link", fti.getEntryPathString());
827 assertEquals("link", dci.getEntryPathString());
828
829
830 assertTrue(fti.isModified(dci.getDirCacheEntry(), true,
831 db.newObjectReader()));
832 }
833 }
834
835 private static void assertEntry(String sha1string, String path, TreeWalk tw)
836 throws MissingObjectException, IncorrectObjectTypeException,
837 CorruptObjectException, IOException {
838 assertTrue(tw.next());
839 assertEquals(path, tw.getPathString());
840 assertEquals(sha1string, tw.getObjectId(1).getName() );
841 }
842
843 private static String nameOf(AbstractTreeIterator i) {
844 return RawParseUtils.decode(UTF_8, i.path, 0, i.pathLen);
845 }
846 }