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