Merge branch 'master' into stable-4.3

* master:
  Remove repository from cache when it's closed
  Fix RefDirectory not closing resources
  Fix repository cache never closing repository

Change-Id: I9dc9d017806cba25125f69b53812cc3e062ef975
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
index 4fefdfd..126ca5c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
@@ -53,6 +53,7 @@
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.util.Set;
 
 import org.eclipse.jgit.api.errors.FilterFailedException;
 import org.eclipse.jgit.api.errors.GitAPIException;
@@ -62,14 +63,11 @@
 import org.eclipse.jgit.dircache.DirCacheEntry;
 import org.eclipse.jgit.junit.JGitTestUtil;
 import org.eclipse.jgit.junit.RepositoryTestCase;
-import org.eclipse.jgit.lib.ConfigConstants;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.FileMode;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.lib.*;
 import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
 import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.WorkingTreeOptions;
 import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.FileUtils;
 import org.junit.Test;
@@ -1002,6 +1000,91 @@ public boolean isCaseSensitive() {
 		assertEquals(FileMode.EXECUTABLE_FILE, walk.getFileMode(0));
 	}
 
+	@Test
+	public void testAddGitlink() throws Exception {
+		createNestedRepo("git-link-dir");
+		try (Git git = new Git(db)) {
+			git.add().addFilepattern("git-link-dir").call();
+
+			assertEquals(
+					"[git-link-dir, mode:160000]",
+					indexState(0));
+			Set<String> untrackedFiles = git.status().call().getUntracked();
+			assert (untrackedFiles.isEmpty());
+		}
+
+	}
+
+	@Test
+	public void testAddSubrepoWithDirNoGitlinks() throws Exception {
+		createNestedRepo("nested-repo");
+
+		// Set DIR_NO_GITLINKS
+		StoredConfig config = db.getConfig();
+		config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+				ConfigConstants.CONFIG_KEY_DIRNOGITLINKS, true);
+		config.save();
+
+		assert (db.getConfig().get(WorkingTreeOptions.KEY).isDirNoGitLinks());
+
+		try (Git git = new Git(db)) {
+			git.add().addFilepattern("nested-repo").call();
+
+			assertEquals(
+					"[nested-repo/README1.md, mode:100644]" +
+							"[nested-repo/README2.md, mode:100644]",
+					indexState(0));
+		}
+
+		// Turn off DIR_NO_GITLINKS, ensure nested-repo is still treated as
+		// a normal directory
+		// Set DIR_NO_GITLINKS
+		config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+				ConfigConstants.CONFIG_KEY_DIRNOGITLINKS, false);
+		config.save();
+
+		writeTrashFile("nested-repo", "README3.md", "content");
+
+		try (Git git = new Git(db)) {
+			git.add().addFilepattern("nested-repo").call();
+
+			assertEquals(
+					"[nested-repo/README1.md, mode:100644]" +
+							"[nested-repo/README2.md, mode:100644]" +
+							"[nested-repo/README3.md, mode:100644]",
+					indexState(0));
+		}
+	}
+
+	@Test
+	public void testAddGitlinkDoesNotChange() throws Exception {
+		createNestedRepo("nested-repo");
+
+		try (Git git = new Git(db)) {
+			git.add().addFilepattern("nested-repo").call();
+
+			assertEquals(
+					"[nested-repo, mode:160000]",
+					indexState(0));
+		}
+
+		// Set DIR_NO_GITLINKS
+		StoredConfig config = db.getConfig();
+		config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+				ConfigConstants.CONFIG_KEY_DIRNOGITLINKS, true);
+		config.save();
+
+		assert (db.getConfig().get(WorkingTreeOptions.KEY).isDirNoGitLinks());
+
+		try (Git git = new Git(db)) {
+			git.add().addFilepattern("nested-repo").call();
+
+			assertEquals(
+					"[nested-repo, mode:160000]",
+					indexState(0));
+		}
+	}
+
 	private static DirCacheEntry addEntryToBuilder(String path, File file,
 			ObjectInserter newObjectInserter, DirCacheBuilder builder, int stage)
 			throws IOException {
@@ -1029,4 +1112,25 @@ private void assumeUnchanged(String path) throws IOException {
 			throw new IOException("could not commit");
 	}
 
+	private void createNestedRepo(String path) throws IOException {
+		File gitLinkDir = new File(db.getWorkTree(), path);
+		FileUtils.mkdir(gitLinkDir);
+
+		FileRepositoryBuilder nestedBuilder = new FileRepositoryBuilder();
+		nestedBuilder.setWorkTree(gitLinkDir);
+
+		Repository nestedRepo = nestedBuilder.build();
+		nestedRepo.create();
+
+		writeTrashFile(path, "README1.md", "content");
+		writeTrashFile(path, "README2.md", "content");
+
+		// Commit these changes in the subrepo
+		try (Git git = new Git(nestedRepo)) {
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("subrepo commit").call();
+		} catch (GitAPIException e) {
+			throw new RuntimeException(e);
+		}
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java
index 362d7ac..8162ac4 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java
@@ -633,6 +633,113 @@ public void testSmudgeFilter_createNew()
 	}
 
 	@Test
+	public void testSmudgeFilter_deleteFileAndRestoreFromCommit()
+			throws IOException, GitAPIException {
+		File script = writeTempFile("sed s/o/e/g");
+		StoredConfig config = git.getRepository().getConfig();
+		config.setString("filter", "tstFilter", "smudge",
+				"sh " + slashify(script.getPath()));
+		config.save();
+
+		writeTrashFile("foo", "foo");
+		git.add().addFilepattern("foo").call();
+		git.commit().setMessage("initial").call();
+
+		writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
+		git.add().addFilepattern(".gitattributes").call();
+		git.commit().setMessage("add filter").call();
+
+		writeTrashFile("src/a.tmp", "foo");
+		// Caution: we need a trailing '\n' since sed on mac always appends
+		// linefeeds if missing
+		writeTrashFile("src/a.txt", "foo\n");
+		git.add().addFilepattern("src/a.tmp").addFilepattern("src/a.txt")
+				.call();
+		RevCommit content = git.commit().setMessage("added content").call();
+
+		deleteTrashFile("src/a.txt");
+		git.checkout().setStartPoint(content.getName()).addPath("src/a.txt")
+				.call();
+
+		assertEquals(
+				"[.gitattributes, mode:100644, content:*.txt filter=tstFilter][Test.txt, mode:100644, content:Some change][foo, mode:100644, content:foo][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:foo\n]",
+				indexState(CONTENT));
+		assertEquals("foo", read("src/a.tmp"));
+		assertEquals("fee\n", read("src/a.txt"));
+	}
+
+	@Test
+	public void testSmudgeFilter_deleteFileAndRestoreFromIndex()
+			throws IOException, GitAPIException {
+		File script = writeTempFile("sed s/o/e/g");
+		StoredConfig config = git.getRepository().getConfig();
+		config.setString("filter", "tstFilter", "smudge",
+				"sh " + slashify(script.getPath()));
+		config.save();
+
+		writeTrashFile("foo", "foo");
+		git.add().addFilepattern("foo").call();
+		git.commit().setMessage("initial").call();
+
+		writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
+		git.add().addFilepattern(".gitattributes").call();
+		git.commit().setMessage("add filter").call();
+
+		writeTrashFile("src/a.tmp", "foo");
+		// Caution: we need a trailing '\n' since sed on mac always appends
+		// linefeeds if missing
+		writeTrashFile("src/a.txt", "foo\n");
+		git.add().addFilepattern("src/a.tmp").addFilepattern("src/a.txt")
+				.call();
+		git.commit().setMessage("added content").call();
+
+		deleteTrashFile("src/a.txt");
+		git.checkout().addPath("src/a.txt").call();
+
+		assertEquals(
+				"[.gitattributes, mode:100644, content:*.txt filter=tstFilter][Test.txt, mode:100644, content:Some change][foo, mode:100644, content:foo][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:foo\n]",
+				indexState(CONTENT));
+		assertEquals("foo", read("src/a.tmp"));
+		assertEquals("fee\n", read("src/a.txt"));
+	}
+
+	@Test
+	public void testSmudgeFilter_deleteFileAndCreateBranchAndRestoreFromCommit()
+			throws IOException, GitAPIException {
+		File script = writeTempFile("sed s/o/e/g");
+		StoredConfig config = git.getRepository().getConfig();
+		config.setString("filter", "tstFilter", "smudge",
+				"sh " + slashify(script.getPath()));
+		config.save();
+
+		writeTrashFile("foo", "foo");
+		git.add().addFilepattern("foo").call();
+		git.commit().setMessage("initial").call();
+
+		writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
+		git.add().addFilepattern(".gitattributes").call();
+		git.commit().setMessage("add filter").call();
+
+		writeTrashFile("src/a.tmp", "foo");
+		// Caution: we need a trailing '\n' since sed on mac always appends
+		// linefeeds if missing
+		writeTrashFile("src/a.txt", "foo\n");
+		git.add().addFilepattern("src/a.tmp").addFilepattern("src/a.txt")
+				.call();
+		RevCommit content = git.commit().setMessage("added content").call();
+
+		deleteTrashFile("src/a.txt");
+		git.checkout().setName("newBranch").setCreateBranch(true)
+				.setStartPoint(content).addPath("src/a.txt").call();
+
+		assertEquals(
+				"[.gitattributes, mode:100644, content:*.txt filter=tstFilter][Test.txt, mode:100644, content:Some change][foo, mode:100644, content:foo][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:foo\n]",
+				indexState(CONTENT));
+		assertEquals("foo", read("src/a.tmp"));
+		assertEquals("fee\n", read("src/a.txt"));
+	}
+
+	@Test
 	@Ignore
 	public void testSmudgeAndClean() throws IOException, GitAPIException {
 		// @TODO: fix this test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java
index f80e117..bf1d0e6d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java
@@ -64,7 +64,12 @@
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.junit.JGitTestUtil;
 import org.eclipse.jgit.junit.RepositoryTestCase;
-import org.eclipse.jgit.lib.*;
+import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
 import org.eclipse.jgit.treewalk.WorkingTreeIterator.MetadataDiff;
@@ -280,6 +285,37 @@ public void testDirCacheMatchingId() throws Exception {
 	}
 
 	@Test
+	public void testTreewalkEnterSubtree() throws Exception {
+		try (Git git = new Git(db)) {
+			writeTrashFile("b/c", "b/c");
+			writeTrashFile("z/.git", "gitdir: /tmp/somewhere");
+			git.add().addFilepattern(".").call();
+			git.rm().addFilepattern("a,").addFilepattern("a,b")
+					.addFilepattern("a0b").call();
+			assertEquals("[a/b, mode:100644][b/c, mode:100644][z, mode:160000]",
+					indexState(0));
+			FileUtils.delete(new File(db.getWorkTree(), "b"),
+					FileUtils.RECURSIVE);
+
+			TreeWalk tw = new TreeWalk(db);
+			tw.addTree(new DirCacheIterator(db.readDirCache()));
+			tw.addTree(new FileTreeIterator(db));
+			assertTrue(tw.next());
+			assertEquals("a", tw.getPathString());
+			tw.enterSubtree();
+			tw.next();
+			assertEquals("a/b", tw.getPathString());
+			tw.next();
+			assertEquals("b", tw.getPathString());
+			tw.enterSubtree();
+			tw.next();
+			assertEquals("b/c", tw.getPathString());
+			assertNotNull(tw.getTree(0, AbstractTreeIterator.class));
+			assertNotNull(tw.getTree(EmptyTreeIterator.class));
+		}
+	}
+
+	@Test
 	public void testIsModifiedSymlinkAsFile() throws Exception {
 		writeTrashFile("symlink", "content");
 		try (Git git = new Git(db)) {
@@ -345,7 +381,7 @@ public void submoduleHeadMatchesIndex() throws Exception {
 			DirCache cache = db.lockDirCache();
 			DirCacheEditor editor = cache.editor();
 			editor.add(new PathEdit(path) {
-	
+
 				public void apply(DirCacheEntry ent) {
 					ent.setFileMode(FileMode.GITLINK);
 					ent.setObjectId(id);
@@ -362,7 +398,7 @@ public void apply(DirCacheEntry ent) {
 			walk.addTree(indexIter);
 			walk.addTree(workTreeIter);
 			walk.setFilter(PathFilter.create(path));
-	
+
 			assertTrue(walk.next());
 			assertTrue(indexIter.idEqual(workTreeIter));
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
index 3b94f16..1f37833 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
@@ -45,6 +45,7 @@
 
 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
 import static org.eclipse.jgit.lib.FileMode.GITLINK;
+import static org.eclipse.jgit.lib.FileMode.TYPE_GITLINK;
 import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;
 
 import java.io.IOException;
@@ -201,7 +202,10 @@ public DirCache call() throws GitAPIException, NoFilepatternException {
 					continue;
 				}
 
-				if (f.getEntryRawMode() == TYPE_TREE) {
+				if ((f.getEntryRawMode() == TYPE_TREE
+						&& f.getIndexFileMode(c) != FileMode.GITLINK) ||
+						(f.getEntryRawMode() == TYPE_GITLINK
+								&& f.getIndexFileMode(c) == FileMode.TREE)) {
 					// Index entry exists and is symlink, gitlink or file,
 					// otherwise the tree would have been entered above.
 					// Replace the index entry by diving into tree of files.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
index c37c317..6c80289 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
@@ -430,6 +430,8 @@ private void checkoutPathsFromIndex(TreeWalk treeWalk, DirCache dc)
 				continue;
 
 			final EolStreamType eolStreamType = treeWalk.getEolStreamType();
+			final String filterCommand = treeWalk
+					.getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE);
 			editor.add(new PathEdit(path) {
 				public void apply(DirCacheEntry ent) {
 					int stage = ent.getStage();
@@ -437,15 +439,15 @@ public void apply(DirCacheEntry ent) {
 						if (checkoutStage != null) {
 							if (stage == checkoutStage.number)
 								checkoutPath(ent, r, new CheckoutMetadata(
-										eolStreamType, null));
+										eolStreamType, filterCommand));
 						} else {
 							UnmergedPathException e = new UnmergedPathException(
 									ent);
 							throw new JGitInternalException(e.getMessage(), e);
 						}
 					} else {
-						checkoutPath(ent, r,
-								new CheckoutMetadata(eolStreamType, null));
+						checkoutPath(ent, r, new CheckoutMetadata(eolStreamType,
+								filterCommand));
 					}
 				}
 			});
@@ -464,12 +466,14 @@ private void checkoutPathsFromCommit(TreeWalk treeWalk, DirCache dc,
 			final ObjectId blobId = treeWalk.getObjectId(0);
 			final FileMode mode = treeWalk.getFileMode(0);
 			final EolStreamType eolStreamType = treeWalk.getEolStreamType();
+			final String filterCommand = treeWalk
+					.getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE);
 			editor.add(new PathEdit(treeWalk.getPathString()) {
 				public void apply(DirCacheEntry ent) {
 					ent.setObjectId(blobId);
 					ent.setFileMode(mode);
 					checkoutPath(ent, r,
-							new CheckoutMetadata(eolStreamType, null));
+							new CheckoutMetadata(eolStreamType, filterCommand));
 				}
 			});
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
index 3233eba..9e3e0b7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
@@ -235,6 +235,12 @@ public class ConfigConstants {
 	 */
 	public static final String CONFIG_KEY_HIDEDOTFILES = "hidedotfiles";
 
+	/**
+	 * The "dirnogitlinks" key
+	 * @since 4.3
+	 */
+	public static final String CONFIG_KEY_DIRNOGITLINKS = "dirNoGitLinks";
+
 	/** The "precomposeunicode" key */
 	public static final String CONFIG_KEY_PRECOMPOSEUNICODE = "precomposeunicode";
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java
index dc835e4..07fc829 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java
@@ -729,4 +729,13 @@ public void getName(byte[] buffer, int offset) {
 	public String toString() {
 		return getClass().getSimpleName() + "[" + getEntryPathString() + "]"; //$NON-NLS-1$
 	}
+
+	/**
+	 * @return whether or not this Iterator is iterating through the Work Tree
+	 *
+	 * @since 4.3
+	 */
+	public boolean isWorkTree() {
+		return false;
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
index eb4f1a8..db81e1a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
@@ -94,7 +94,10 @@ public class FileTreeIterator extends WorkingTreeIterator {
 	 *            the repository whose working tree will be scanned.
 	 */
 	public FileTreeIterator(Repository repo) {
-		this(repo, DefaultFileModeStrategy.INSTANCE);
+		this(repo,
+				repo.getConfig().get(WorkingTreeOptions.KEY).isDirNoGitLinks() ?
+						NoGitlinksStrategy.INSTANCE :
+						DefaultFileModeStrategy.INSTANCE);
 	}
 
 	/**
@@ -291,6 +294,35 @@ public FileMode getMode(File f, FS.Attributes attributes) {
 		}
 	}
 
+	/**
+	 * A FileModeStrategy that implements native git's DIR_NO_GITLINKS
+	 * behavior. This is the same as the default FileModeStrategy, except
+	 * all directories will be treated as directories regardless of whether
+	 * or not they contain a .git directory or file.
+	 *
+	 * @since 4.3
+	 */
+	static public class NoGitlinksStrategy implements FileModeStrategy {
+
+		/**
+		 * a singleton instance of the default FileModeStrategy
+		 */
+		public final static NoGitlinksStrategy INSTANCE = new NoGitlinksStrategy();
+
+		@Override
+		public FileMode getMode(File f, FS.Attributes attributes) {
+			if (attributes.isSymbolicLink()) {
+				return FileMode.SYMLINK;
+			} else if (attributes.isDirectory()) {
+				return FileMode.TREE;
+			} else if (attributes.isExecutable()) {
+				return FileMode.EXECUTABLE_FILE;
+			} else {
+				return FileMode.REGULAR_FILE;
+			}
+		}
+	}
+
 
 	/**
 	 * Wrapper for a standard Java IO file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
index aecbac1..501d676 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
@@ -1187,7 +1187,12 @@ public void enterSubtree() throws MissingObjectException,
 		for (int i = 0; i < trees.length; i++) {
 			final AbstractTreeIterator t = trees[i];
 			final AbstractTreeIterator n;
-			if (t.matches == ch && !t.eof() && FileMode.TREE.equals(t.mode))
+			// If we find a GITLINK when attempting to enter a subtree, then the
+			// GITLINK must exist as a TREE in the index, meaning the working tree
+			// entry should be treated as a TREE
+			if (t.matches == ch && !t.eof() &&
+					(FileMode.TREE.equals(t.mode)
+							|| (FileMode.GITLINK.equals(t.mode) && t.isWorkTree())))
 				n = t.createSubtreeIterator(reader, idBuffer);
 			else
 				n = t.createEmptyTreeIterator();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
index ca8f9aa..39176c6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
@@ -264,7 +264,7 @@ public boolean hasId() {
 			// the cached index information for the path.
 			//
 			DirCacheIterator i = state.walk.getTree(state.dirCacheTree,
-					DirCacheIterator.class);
+							DirCacheIterator.class);
 			if (i != null) {
 				DirCacheEntry ent = i.getDirCacheEntry();
 				if (ent != null && compareMetadata(ent) == MetadataDiff.EQUAL) {
@@ -289,6 +289,11 @@ public boolean hasId() {
 		return zeroid;
 	}
 
+	@Override
+	public boolean isWorkTree() {
+		return true;
+	}
+
 	/**
 	 * Get submodule id for given entry.
 	 *
@@ -916,17 +921,31 @@ public boolean isModified(DirCacheEntry entry, boolean forceContentCheck,
 	 */
 	public FileMode getIndexFileMode(final DirCacheIterator indexIter) {
 		final FileMode wtMode = getEntryFileMode();
-		if (indexIter == null)
+		if (indexIter == null) {
 			return wtMode;
-		if (getOptions().isFileMode())
-			return wtMode;
+		}
 		final FileMode iMode = indexIter.getEntryFileMode();
-		if (FileMode.REGULAR_FILE == wtMode
-				&& FileMode.EXECUTABLE_FILE == iMode)
+		if (getOptions().isFileMode() && iMode != FileMode.GITLINK && iMode != FileMode.TREE) {
+			return wtMode;
+		}
+		if (!getOptions().isFileMode()) {
+			if (FileMode.REGULAR_FILE == wtMode
+					&& FileMode.EXECUTABLE_FILE == iMode) {
+				return iMode;
+			}
+			if (FileMode.EXECUTABLE_FILE == wtMode
+					&& FileMode.REGULAR_FILE == iMode) {
+				return iMode;
+			}
+		}
+		if (FileMode.GITLINK == iMode
+				&& FileMode.TREE == wtMode) {
 			return iMode;
-		if (FileMode.EXECUTABLE_FILE == wtMode
-				&& FileMode.REGULAR_FILE == iMode)
+		}
+		if (FileMode.TREE == iMode
+				&& FileMode.GITLINK == wtMode) {
 			return iMode;
+		}
 		return wtMode;
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeOptions.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeOptions.java
index a8990b1..dea07c1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeOptions.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeOptions.java
@@ -73,6 +73,8 @@ public WorkingTreeOptions parse(final Config cfg) {
 
 	private final HideDotFiles hideDotFiles;
 
+	private final boolean dirNoGitLinks;
+
 	private WorkingTreeOptions(final Config rc) {
 		fileMode = rc.getBoolean(ConfigConstants.CONFIG_CORE_SECTION,
 				ConfigConstants.CONFIG_KEY_FILEMODE, true);
@@ -87,6 +89,9 @@ private WorkingTreeOptions(final Config rc) {
 		hideDotFiles = rc.getEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
 				ConfigConstants.CONFIG_KEY_HIDEDOTFILES,
 				HideDotFiles.DOTGITONLY);
+		dirNoGitLinks = rc.getBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+				ConfigConstants.CONFIG_KEY_DIRNOGITLINKS,
+				false);
 	}
 
 	/** @return true if the execute bit on working files should be trusted. */
@@ -131,4 +136,12 @@ public SymLinks getSymLinks() {
 	public HideDotFiles getHideDotFiles() {
 		return hideDotFiles;
 	}
+
+	/**
+	 * @return whether or not we treat nested repos as directories.
+	 * 		   If true, folders containing .git entries will not be
+	 * 		   treated as gitlinks.
+	 * @since 4.3
+	 */
+	public boolean isDirNoGitLinks() { return dirNoGitLinks; }
 }