View Javadoc
1   /*
2    * Copyright (C) 2008, Google Inc.
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  
44  package org.eclipse.jgit.treewalk;
45  
46  import static org.junit.Assert.assertEquals;
47  import static org.junit.Assert.assertFalse;
48  import static org.junit.Assert.assertNotNull;
49  import static org.junit.Assert.assertTrue;
50  
51  import java.io.File;
52  import java.io.IOException;
53  import java.security.MessageDigest;
54  
55  import org.eclipse.jgit.api.Git;
56  import org.eclipse.jgit.dircache.DirCache;
57  import org.eclipse.jgit.dircache.DirCacheCheckout;
58  import org.eclipse.jgit.dircache.DirCacheEditor;
59  import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
60  import org.eclipse.jgit.dircache.DirCacheEntry;
61  import org.eclipse.jgit.dircache.DirCacheIterator;
62  import org.eclipse.jgit.errors.CorruptObjectException;
63  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
64  import org.eclipse.jgit.errors.MissingObjectException;
65  import org.eclipse.jgit.junit.JGitTestUtil;
66  import org.eclipse.jgit.junit.RepositoryTestCase;
67  import org.eclipse.jgit.lib.ConfigConstants;
68  import org.eclipse.jgit.lib.Constants;
69  import org.eclipse.jgit.lib.FileMode;
70  import org.eclipse.jgit.lib.ObjectId;
71  import org.eclipse.jgit.lib.ObjectReader;
72  import org.eclipse.jgit.lib.Repository;
73  import org.eclipse.jgit.revwalk.RevCommit;
74  import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
75  import org.eclipse.jgit.treewalk.WorkingTreeIterator.MetadataDiff;
76  import org.eclipse.jgit.treewalk.filter.PathFilter;
77  import org.eclipse.jgit.util.FS;
78  import org.eclipse.jgit.util.FileUtils;
79  import org.eclipse.jgit.util.RawParseUtils;
80  import org.junit.Before;
81  import org.junit.Test;
82  
83  public class FileTreeIteratorTest extends RepositoryTestCase {
84  	private final String[] paths = { "a,", "a,b", "a/b", "a0b" };
85  
86  	private long[] mtime;
87  
88  	@Before
89  	public void setUp() throws Exception {
90  		super.setUp();
91  
92  		// We build the entries backwards so that on POSIX systems we
93  		// are likely to get the entries in the trash directory in the
94  		// opposite order of what they should be in for the iteration.
95  		// This should stress the sorting code better than doing it in
96  		// the correct order.
97  		//
98  		mtime = new long[paths.length];
99  		for (int i = paths.length - 1; i >= 0; i--) {
100 			final String s = paths[i];
101 			writeTrashFile(s, s);
102 			mtime[i] = new File(trash, s).lastModified();
103 		}
104 	}
105 
106 	@Test
107 	public void testGetEntryContentLength() throws Exception {
108 		final FileTreeIterator fti = new FileTreeIterator(db);
109 		fti.next(1);
110 		assertEquals(3, fti.getEntryContentLength());
111 		fti.back(1);
112 		assertEquals(2, fti.getEntryContentLength());
113 		fti.next(1);
114 		assertEquals(3, fti.getEntryContentLength());
115 		fti.reset();
116 		assertEquals(2, fti.getEntryContentLength());
117 	}
118 
119 	@Test
120 	public void testEmptyIfRootIsFile() throws Exception {
121 		final File r = new File(trash, paths[0]);
122 		assertTrue(r.isFile());
123 		final FileTreeIterator fti = new FileTreeIterator(r, db.getFS(),
124 				db.getConfig().get(WorkingTreeOptions.KEY));
125 		assertTrue(fti.first());
126 		assertTrue(fti.eof());
127 	}
128 
129 	@Test
130 	public void testEmptyIfRootDoesNotExist() throws Exception {
131 		final File r = new File(trash, "not-existing-file");
132 		assertFalse(r.exists());
133 		final FileTreeIterator fti = new FileTreeIterator(r, db.getFS(),
134 				db.getConfig().get(WorkingTreeOptions.KEY));
135 		assertTrue(fti.first());
136 		assertTrue(fti.eof());
137 	}
138 
139 	@Test
140 	public void testEmptyIfRootIsEmpty() throws Exception {
141 		final File r = new File(trash, "not-existing-file");
142 		assertFalse(r.exists());
143 		FileUtils.mkdir(r);
144 
145 		final FileTreeIterator fti = new FileTreeIterator(r, db.getFS(),
146 				db.getConfig().get(WorkingTreeOptions.KEY));
147 		assertTrue(fti.first());
148 		assertTrue(fti.eof());
149 	}
150 
151 	@Test
152 	public void testEmptyIteratorOnEmptyDirectory() throws Exception {
153 		String nonExistingFileName = "not-existing-file";
154 		final File r = new File(trash, nonExistingFileName);
155 		assertFalse(r.exists());
156 		FileUtils.mkdir(r);
157 
158 		final FileTreeIterator parent = new FileTreeIterator(db);
159 
160 		while (!parent.getEntryPathString().equals(nonExistingFileName))
161 			parent.next(1);
162 
163 		final FileTreeIterator childIter = new FileTreeIterator(parent, r,
164 				db.getFS());
165 		assertTrue(childIter.first());
166 		assertTrue(childIter.eof());
167 
168 		String parentPath = parent.getEntryPathString();
169 		assertEquals(nonExistingFileName, parentPath);
170 
171 		// must be "not-existing-file/", but getEntryPathString() was broken by
172 		// 445363 too
173 		String childPath = childIter.getEntryPathString();
174 
175 		// in bug 445363 the iterator wrote garbage to the parent "path" field
176 		EmptyTreeIterator e = childIter.createEmptyTreeIterator();
177 		assertNotNull(e);
178 
179 		// check if parent path is not overridden by empty iterator (bug 445363)
180 		// due bug 445363 this was "/ot-existing-file" instead of
181 		// "not-existing-file"
182 		assertEquals(parentPath, parent.getEntryPathString());
183 		assertEquals(parentPath + "/", childPath);
184 		assertEquals(parentPath + "/", childIter.getEntryPathString());
185 		assertEquals(childPath + "/", e.getEntryPathString());
186 	}
187 
188 	@Test
189 	public void testSimpleIterate() throws Exception {
190 		final FileTreeIterator top = new FileTreeIterator(trash, db.getFS(),
191 				db.getConfig().get(WorkingTreeOptions.KEY));
192 
193 		assertTrue(top.first());
194 		assertFalse(top.eof());
195 		assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode);
196 		assertEquals(paths[0], nameOf(top));
197 		assertEquals(paths[0].length(), top.getEntryLength());
198 		assertEquals(mtime[0], top.getEntryLastModified());
199 
200 		top.next(1);
201 		assertFalse(top.first());
202 		assertFalse(top.eof());
203 		assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode);
204 		assertEquals(paths[1], nameOf(top));
205 		assertEquals(paths[1].length(), top.getEntryLength());
206 		assertEquals(mtime[1], top.getEntryLastModified());
207 
208 		top.next(1);
209 		assertFalse(top.first());
210 		assertFalse(top.eof());
211 		assertEquals(FileMode.TREE.getBits(), top.mode);
212 
213 		final ObjectReader reader = db.newObjectReader();
214 		final AbstractTreeIterator sub = top.createSubtreeIterator(reader);
215 		assertTrue(sub instanceof FileTreeIterator);
216 		final FileTreeIterator subfti = (FileTreeIterator) sub;
217 		assertTrue(sub.first());
218 		assertFalse(sub.eof());
219 		assertEquals(paths[2], nameOf(sub));
220 		assertEquals(paths[2].length(), subfti.getEntryLength());
221 		assertEquals(mtime[2], subfti.getEntryLastModified());
222 
223 		sub.next(1);
224 		assertTrue(sub.eof());
225 
226 		top.next(1);
227 		assertFalse(top.first());
228 		assertFalse(top.eof());
229 		assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode);
230 		assertEquals(paths[3], nameOf(top));
231 		assertEquals(paths[3].length(), top.getEntryLength());
232 		assertEquals(mtime[3], top.getEntryLastModified());
233 
234 		top.next(1);
235 		assertTrue(top.eof());
236 	}
237 
238 	@Test
239 	public void testComputeFileObjectId() throws Exception {
240 		final FileTreeIterator top = new FileTreeIterator(trash, db.getFS(),
241 				db.getConfig().get(WorkingTreeOptions.KEY));
242 
243 		final MessageDigest md = Constants.newMessageDigest();
244 		md.update(Constants.encodeASCII(Constants.TYPE_BLOB));
245 		md.update((byte) ' ');
246 		md.update(Constants.encodeASCII(paths[0].length()));
247 		md.update((byte) 0);
248 		md.update(Constants.encode(paths[0]));
249 		final ObjectId expect = ObjectId.fromRaw(md.digest());
250 
251 		assertEquals(expect, top.getEntryObjectId());
252 
253 		// Verify it was cached by removing the file and getting it again.
254 		//
255 		FileUtils.delete(new File(trash, paths[0]));
256 		assertEquals(expect, top.getEntryObjectId());
257 	}
258 
259 	@Test
260 	public void testDirCacheMatchingId() throws Exception {
261 		File f = writeTrashFile("file", "content");
262 		try (Git git = new Git(db)) {
263 			writeTrashFile("file", "content");
264 			fsTick(f);
265 			git.add().addFilepattern("file").call();
266 		}
267 		DirCacheEntry dce = db.readDirCache().getEntry("file");
268 		TreeWalk tw = new TreeWalk(db);
269 		FileTreeIterator fti = new FileTreeIterator(trash, db.getFS(), db
270 				.getConfig().get(WorkingTreeOptions.KEY));
271 		tw.addTree(fti);
272 		DirCacheIterator dci = new DirCacheIterator(db.readDirCache());
273 		tw.addTree(dci);
274 		fti.setDirCacheIterator(tw, 1);
275 		while (tw.next() && !tw.getPathString().equals("file")) {
276 			//
277 		}
278 		assertEquals(MetadataDiff.EQUAL, fti.compareMetadata(dce));
279 		ObjectId fromRaw = ObjectId.fromRaw(fti.idBuffer(), fti.idOffset());
280 		assertEquals("6b584e8ece562ebffc15d38808cd6b98fc3d97ea",
281 				fromRaw.getName());
282 		try (ObjectReader objectReader = db.newObjectReader()) {
283 			assertFalse(fti.isModified(dce, false, objectReader));
284 		}
285 	}
286 
287 	@Test
288 	public void testTreewalkEnterSubtree() throws Exception {
289 		try (Git git = new Git(db)) {
290 			writeTrashFile("b/c", "b/c");
291 			writeTrashFile("z/.git", "gitdir: /tmp/somewhere");
292 			git.add().addFilepattern(".").call();
293 			git.rm().addFilepattern("a,").addFilepattern("a,b")
294 					.addFilepattern("a0b").call();
295 			assertEquals("[a/b, mode:100644][b/c, mode:100644][z, mode:160000]",
296 					indexState(0));
297 			FileUtils.delete(new File(db.getWorkTree(), "b"),
298 					FileUtils.RECURSIVE);
299 
300 			TreeWalk tw = new TreeWalk(db);
301 			tw.addTree(new DirCacheIterator(db.readDirCache()));
302 			tw.addTree(new FileTreeIterator(db));
303 			assertTrue(tw.next());
304 			assertEquals("a", tw.getPathString());
305 			tw.enterSubtree();
306 			tw.next();
307 			assertEquals("a/b", tw.getPathString());
308 			tw.next();
309 			assertEquals("b", tw.getPathString());
310 			tw.enterSubtree();
311 			tw.next();
312 			assertEquals("b/c", tw.getPathString());
313 			assertNotNull(tw.getTree(0, AbstractTreeIterator.class));
314 			assertNotNull(tw.getTree(EmptyTreeIterator.class));
315 		}
316 	}
317 
318 	@Test
319 	public void testIsModifiedSymlinkAsFile() throws Exception {
320 		writeTrashFile("symlink", "content");
321 		try (Git git = new Git(db)) {
322 			db.getConfig().setString(ConfigConstants.CONFIG_CORE_SECTION, null,
323 					ConfigConstants.CONFIG_KEY_SYMLINKS, "false");
324 			git.add().addFilepattern("symlink").call();
325 			git.commit().setMessage("commit").call();
326 		}
327 
328 		// Modify previously committed DirCacheEntry and write it back to disk
329 		DirCacheEntry dce = db.readDirCache().getEntry("symlink");
330 		dce.setFileMode(FileMode.SYMLINK);
331 		try (ObjectReader objectReader = db.newObjectReader()) {
332 			DirCacheCheckout.checkoutEntry(db, dce, objectReader);
333 
334 			FileTreeIterator fti = new FileTreeIterator(trash, db.getFS(),
335 					db.getConfig().get(WorkingTreeOptions.KEY));
336 			while (!fti.getEntryPathString().equals("symlink"))
337 				fti.next(1);
338 			assertFalse(fti.isModified(dce, false, objectReader));
339 		}
340 	}
341 
342 	@Test
343 	public void testIsModifiedFileSmudged() throws Exception {
344 		File f = writeTrashFile("file", "content");
345 		try (Git git = new Git(db)) {
346 			// The idea of this test is to check the smudged handling
347 			// Hopefully fsTick will make sure our entry gets smudged
348 			fsTick(f);
349 			writeTrashFile("file", "content");
350 			long lastModified = f.lastModified();
351 			git.add().addFilepattern("file").call();
352 			writeTrashFile("file", "conten2");
353 			f.setLastModified(lastModified);
354 			// We cannot trust this to go fast enough on
355 			// a system with less than one-second lastModified
356 			// resolution, so we force the index to have the
357 			// same timestamp as the file we look at.
358 			db.getIndexFile().setLastModified(lastModified);
359 		}
360 		DirCacheEntry dce = db.readDirCache().getEntry("file");
361 		FileTreeIterator fti = new FileTreeIterator(trash, db.getFS(), db
362 				.getConfig().get(WorkingTreeOptions.KEY));
363 		while (!fti.getEntryPathString().equals("file"))
364 			fti.next(1);
365 		// If the rounding trick does not work we could skip the compareMetaData
366 		// test and hope that we are usually testing the intended code path.
367 		assertEquals(MetadataDiff.SMUDGED, fti.compareMetadata(dce));
368 		try (ObjectReader objectReader = db.newObjectReader()) {
369 			assertTrue(fti.isModified(dce, false, objectReader));
370 		}
371 	}
372 
373 	@Test
374 	public void submoduleHeadMatchesIndex() throws Exception {
375 		try (Git git = new Git(db);
376 				TreeWalk walk = new TreeWalk(db)) {
377 			writeTrashFile("file.txt", "content");
378 			git.add().addFilepattern("file.txt").call();
379 			final RevCommit id = git.commit().setMessage("create file").call();
380 			final String path = "sub";
381 			DirCache cache = db.lockDirCache();
382 			DirCacheEditor editor = cache.editor();
383 			editor.add(new PathEdit(path) {
384 
385 				public void apply(DirCacheEntry ent) {
386 					ent.setFileMode(FileMode.GITLINK);
387 					ent.setObjectId(id);
388 				}
389 			});
390 			editor.commit();
391 
392 			Git.cloneRepository().setURI(db.getDirectory().toURI().toString())
393 					.setDirectory(new File(db.getWorkTree(), path)).call()
394 					.getRepository().close();
395 
396 			DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache());
397 			FileTreeIterator workTreeIter = new FileTreeIterator(db);
398 			walk.addTree(indexIter);
399 			walk.addTree(workTreeIter);
400 			walk.setFilter(PathFilter.create(path));
401 
402 			assertTrue(walk.next());
403 			assertTrue(indexIter.idEqual(workTreeIter));
404 		}
405 	}
406 
407 	@Test
408 	public void submoduleWithNoGitDirectory() throws Exception {
409 		try (Git git = new Git(db);
410 				TreeWalk walk = new TreeWalk(db)) {
411 			writeTrashFile("file.txt", "content");
412 			git.add().addFilepattern("file.txt").call();
413 			final RevCommit id = git.commit().setMessage("create file").call();
414 			final String path = "sub";
415 			DirCache cache = db.lockDirCache();
416 			DirCacheEditor editor = cache.editor();
417 			editor.add(new PathEdit(path) {
418 
419 				public void apply(DirCacheEntry ent) {
420 					ent.setFileMode(FileMode.GITLINK);
421 					ent.setObjectId(id);
422 				}
423 			});
424 			editor.commit();
425 
426 			File submoduleRoot = new File(db.getWorkTree(), path);
427 			assertTrue(submoduleRoot.mkdir());
428 			assertTrue(new File(submoduleRoot, Constants.DOT_GIT).mkdir());
429 
430 			DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache());
431 			FileTreeIterator workTreeIter = new FileTreeIterator(db);
432 			walk.addTree(indexIter);
433 			walk.addTree(workTreeIter);
434 			walk.setFilter(PathFilter.create(path));
435 
436 			assertTrue(walk.next());
437 			assertFalse(indexIter.idEqual(workTreeIter));
438 			assertEquals(ObjectId.zeroId(), workTreeIter.getEntryObjectId());
439 		}
440 	}
441 
442 	@Test
443 	public void submoduleWithNoHead() throws Exception {
444 		try (Git git = new Git(db);
445 				TreeWalk walk = new TreeWalk(db)) {
446 			writeTrashFile("file.txt", "content");
447 			git.add().addFilepattern("file.txt").call();
448 			final RevCommit id = git.commit().setMessage("create file").call();
449 			final String path = "sub";
450 			DirCache cache = db.lockDirCache();
451 			DirCacheEditor editor = cache.editor();
452 			editor.add(new PathEdit(path) {
453 
454 				public void apply(DirCacheEntry ent) {
455 					ent.setFileMode(FileMode.GITLINK);
456 					ent.setObjectId(id);
457 				}
458 			});
459 			editor.commit();
460 
461 			assertNotNull(Git.init().setDirectory(new File(db.getWorkTree(), path))
462 					.call().getRepository());
463 
464 			DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache());
465 			FileTreeIterator workTreeIter = new FileTreeIterator(db);
466 			walk.addTree(indexIter);
467 			walk.addTree(workTreeIter);
468 			walk.setFilter(PathFilter.create(path));
469 
470 			assertTrue(walk.next());
471 			assertFalse(indexIter.idEqual(workTreeIter));
472 			assertEquals(ObjectId.zeroId(), workTreeIter.getEntryObjectId());
473 		}
474 	}
475 
476 	@Test
477 	public void submoduleDirectoryIterator() throws Exception {
478 		try (Git git = new Git(db);
479 				TreeWalk walk = new TreeWalk(db)) {
480 			writeTrashFile("file.txt", "content");
481 			git.add().addFilepattern("file.txt").call();
482 			final RevCommit id = git.commit().setMessage("create file").call();
483 			final String path = "sub";
484 			DirCache cache = db.lockDirCache();
485 			DirCacheEditor editor = cache.editor();
486 			editor.add(new PathEdit(path) {
487 
488 				public void apply(DirCacheEntry ent) {
489 					ent.setFileMode(FileMode.GITLINK);
490 					ent.setObjectId(id);
491 				}
492 			});
493 			editor.commit();
494 
495 			Git.cloneRepository().setURI(db.getDirectory().toURI().toString())
496 					.setDirectory(new File(db.getWorkTree(), path)).call()
497 					.getRepository().close();
498 
499 			DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache());
500 			FileTreeIterator workTreeIter = new FileTreeIterator(db.getWorkTree(),
501 					db.getFS(), db.getConfig().get(WorkingTreeOptions.KEY));
502 			walk.addTree(indexIter);
503 			walk.addTree(workTreeIter);
504 			walk.setFilter(PathFilter.create(path));
505 
506 			assertTrue(walk.next());
507 			assertTrue(indexIter.idEqual(workTreeIter));
508 		}
509 	}
510 
511 	@Test
512 	public void submoduleNestedWithHeadMatchingIndex() throws Exception {
513 		try (Git git = new Git(db);
514 				TreeWalk walk = new TreeWalk(db)) {
515 			writeTrashFile("file.txt", "content");
516 			git.add().addFilepattern("file.txt").call();
517 			final RevCommit id = git.commit().setMessage("create file").call();
518 			final String path = "sub/dir1/dir2";
519 			DirCache cache = db.lockDirCache();
520 			DirCacheEditor editor = cache.editor();
521 			editor.add(new PathEdit(path) {
522 
523 				public void apply(DirCacheEntry ent) {
524 					ent.setFileMode(FileMode.GITLINK);
525 					ent.setObjectId(id);
526 				}
527 			});
528 			editor.commit();
529 
530 			Git.cloneRepository().setURI(db.getDirectory().toURI().toString())
531 					.setDirectory(new File(db.getWorkTree(), path)).call()
532 					.getRepository().close();
533 
534 			DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache());
535 			FileTreeIterator workTreeIter = new FileTreeIterator(db);
536 			walk.addTree(indexIter);
537 			walk.addTree(workTreeIter);
538 			walk.setFilter(PathFilter.create(path));
539 
540 			assertTrue(walk.next());
541 			assertTrue(indexIter.idEqual(workTreeIter));
542 		}
543 	}
544 
545 	@Test
546 	public void idOffset() throws Exception {
547 		try (Git git = new Git(db);
548 				TreeWalk tw = new TreeWalk(db)) {
549 			writeTrashFile("fileAinfsonly", "A");
550 			File fileBinindex = writeTrashFile("fileBinindex", "B");
551 			fsTick(fileBinindex);
552 			git.add().addFilepattern("fileBinindex").call();
553 			writeTrashFile("fileCinfsonly", "C");
554 			DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache());
555 			FileTreeIterator workTreeIter = new FileTreeIterator(db);
556 			tw.addTree(indexIter);
557 			tw.addTree(workTreeIter);
558 			workTreeIter.setDirCacheIterator(tw, 0);
559 			assertEntry("d46c305e85b630558ee19cc47e73d2e5c8c64cdc", "a,", tw);
560 			assertEntry("58ee403f98538ec02409538b3f80adf610accdec", "a,b", tw);
561 			assertEntry("0000000000000000000000000000000000000000", "a", tw);
562 			assertEntry("b8d30ff397626f0f1d3538d66067edf865e201d6", "a0b", tw);
563 			// The reason for adding this test. Check that the id is correct for
564 			// mixed
565 			assertEntry("8c7e5a667f1b771847fe88c01c3de34413a1b220",
566 					"fileAinfsonly", tw);
567 			assertEntry("7371f47a6f8bd23a8fa1a8b2a9479cdd76380e54", "fileBinindex",
568 					tw);
569 			assertEntry("96d80cd6c4e7158dbebd0849f4fb7ce513e5828c",
570 					"fileCinfsonly", tw);
571 			assertFalse(tw.next());
572 		}
573 	}
574 
575 	private final FileTreeIterator.FileModeStrategy NO_GITLINKS_STRATEGY =
576 			new FileTreeIterator.FileModeStrategy() {
577 				@Override
578 				public FileMode getMode(File f, FS.Attributes attributes) {
579 					if (attributes.isSymbolicLink()) {
580 						return FileMode.SYMLINK;
581 					} else if (attributes.isDirectory()) {
582 						// NOTE: in the production DefaultFileModeStrategy, there is
583 						// a check here for a subdirectory called '.git', and if it
584 						// exists, we create a GITLINK instead of recursing into the
585 						// tree.  In this custom strategy, we ignore nested git dirs
586 						// and treat all directories the same.
587 						return FileMode.TREE;
588 					} else if (attributes.isExecutable()) {
589 						return FileMode.EXECUTABLE_FILE;
590 					} else {
591 						return FileMode.REGULAR_FILE;
592 					}
593 				}
594 			};
595 
596 	private Repository createNestedRepo() throws IOException {
597 		File gitdir = createUniqueTestGitDir(false);
598 		FileRepositoryBuilder builder = new FileRepositoryBuilder();
599 		builder.setGitDir(gitdir);
600 		Repository nestedRepo = builder.build();
601 		nestedRepo.create();
602 
603 		JGitTestUtil.writeTrashFile(nestedRepo, "sub", "a.txt", "content");
604 
605 		File nestedRepoPath = new File(nestedRepo.getWorkTree(), "sub/nested");
606 		FileRepositoryBuilder nestedBuilder = new FileRepositoryBuilder();
607 		nestedBuilder.setWorkTree(nestedRepoPath);
608 		nestedBuilder.build().create();
609 
610 		JGitTestUtil.writeTrashFile(nestedRepo, "sub/nested", "b.txt",
611 				"content b");
612 
613 		return nestedRepo;
614 	}
615 
616 	@Test
617 	public void testCustomFileModeStrategy() throws Exception {
618 		Repository nestedRepo = createNestedRepo();
619 
620 		Git git = new Git(nestedRepo);
621 		// validate that our custom strategy is honored
622 		WorkingTreeIterator customIterator =
623 				new FileTreeIterator(nestedRepo, NO_GITLINKS_STRATEGY);
624 		git.add().setWorkingTreeIterator(customIterator)
625 				.addFilepattern(".").call();
626 		assertEquals(
627 				"[sub/a.txt, mode:100644, content:content]" +
628 				"[sub/nested/b.txt, mode:100644, content:content b]",
629 				indexState(nestedRepo, CONTENT));
630 
631 	}
632 
633 	@Test
634 	public void testCustomFileModeStrategyFromParentIterator() throws Exception {
635 		Repository nestedRepo = createNestedRepo();
636 
637 		Git git = new Git(nestedRepo);
638 
639 		FileTreeIterator customIterator =
640 				new FileTreeIterator(nestedRepo, NO_GITLINKS_STRATEGY);
641 		File r = new File(nestedRepo.getWorkTree(), "sub");
642 
643 		// here we want to validate that if we create a new iterator using the
644 		// constructor that accepts a parent iterator, that the child iterator
645 		// correctly inherits the FileModeStrategy from the parent iterator.
646 		FileTreeIterator childIterator =
647 				new FileTreeIterator(customIterator, r, nestedRepo.getFS());
648 		git.add().setWorkingTreeIterator(childIterator).addFilepattern(".").call();
649 		assertEquals(
650 				"[sub/a.txt, mode:100644, content:content]" +
651 				"[sub/nested/b.txt, mode:100644, content:content b]",
652 				indexState(nestedRepo, CONTENT));
653 	}
654 
655 
656 	private static void assertEntry(String sha1string, String path, TreeWalk tw)
657 			throws MissingObjectException, IncorrectObjectTypeException,
658 			CorruptObjectException, IOException {
659 		assertTrue(tw.next());
660 		assertEquals(path, tw.getPathString());
661 		assertEquals(sha1string, tw.getObjectId(1).getName() /* 1=filetree here */);
662 	}
663 
664 	private static String nameOf(final AbstractTreeIterator i) {
665 		return RawParseUtils.decode(Constants.CHARSET, i.path, 0, i.pathLen);
666 	}
667 }