Merge changes Ib6689f54,I3b5c22ee

* changes:
  Remove unused API problem filters
  Add missing @since tag for RevCommit#parents introduced in 61b4d105e4
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 5dfdfcf..57661a7 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
@@ -15,13 +15,16 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.nio.file.Files;
 import java.util.Set;
 
+import org.eclipse.jgit.api.ResetCommand.ResetType;
 import org.eclipse.jgit.api.errors.FilterFailedException;
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.api.errors.NoFilepatternException;
@@ -34,6 +37,7 @@
 import org.eclipse.jgit.lfs.BuiltinLFS;
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.CoreConfig.SymLinks;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectInserter;
@@ -100,6 +104,43 @@
 	}
 
 	@Test
+	public void testAddLink() throws IOException, GitAPIException {
+		assumeTrue(db.getFS().supportsSymlinks());
+		try (Git git = new Git(db)) {
+			writeTrashFile("a.txt", "a");
+			File link = new File(db.getWorkTree(), "link");
+			db.getFS().createSymLink(link, "a.txt");
+			git.add().addFilepattern(".").call();
+			assertEquals(
+					"[a.txt, mode:100644, content:a][link, mode:120000, content:a.txt]",
+					indexState(CONTENT));
+			git.commit().setMessage("link").call();
+			StoredConfig config = db.getConfig();
+			config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
+					ConfigConstants.CONFIG_KEY_SYMLINKS, SymLinks.FALSE);
+			config.save();
+			Files.delete(link.toPath());
+			git.reset().setMode(ResetType.HARD).call();
+			assertTrue(Files.isRegularFile(link.toPath()));
+			assertEquals(
+					"[a.txt, mode:100644, content:a][link, mode:120000, content:a.txt]",
+					indexState(CONTENT));
+			writeTrashFile("link", "b.txt");
+			git.add().addFilepattern("link").call();
+			assertEquals(
+					"[a.txt, mode:100644, content:a][link, mode:120000, content:b.txt]",
+					indexState(CONTENT));
+			config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
+					ConfigConstants.CONFIG_KEY_SYMLINKS, SymLinks.TRUE);
+			config.save();
+			git.add().addFilepattern("link").call();
+			assertEquals(
+					"[a.txt, mode:100644, content:a][link, mode:100644, content:b.txt]",
+					indexState(CONTENT));
+		}
+	}
+
+	@Test
 	public void testCleanFilter() throws IOException, GitAPIException {
 		writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
 		writeTrashFile("src/a.tmp", "foo");
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 0d49cd3..e463e90 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
@@ -303,12 +303,16 @@
 		DirCacheEntry dce = db.readDirCache().getEntry("symlink");
 		dce.setFileMode(FileMode.SYMLINK);
 		try (ObjectReader objectReader = db.newObjectReader()) {
-			DirCacheCheckout.checkoutEntry(db, dce, objectReader, false, null);
+			WorkingTreeOptions options = db.getConfig()
+					.get(WorkingTreeOptions.KEY);
+			DirCacheCheckout.checkoutEntry(db, dce, objectReader, false, null,
+					options);
 
 			FileTreeIterator fti = new FileTreeIterator(trash, db.getFS(),
-					db.getConfig().get(WorkingTreeOptions.KEY));
-			while (!fti.getEntryPathString().equals("symlink"))
+					options);
+			while (!fti.getEntryPathString().equals("symlink")) {
 				fti.next(1);
+			}
 			assertFalse(fti.isModified(dce, false, objectReader));
 		}
 	}
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 847ab0a..7319ff4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
@@ -55,6 +55,7 @@
 import org.eclipse.jgit.revwalk.RevTree;
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.WorkingTreeOptions;
 import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
 
 /**
@@ -411,6 +412,8 @@
 	protected CheckoutCommand checkoutPaths() throws IOException,
 			RefNotFoundException {
 		actuallyModifiedPaths = new HashSet<>();
+		WorkingTreeOptions options = repo.getConfig()
+				.get(WorkingTreeOptions.KEY);
 		DirCache dc = repo.lockDirCache();
 		try (RevWalk revWalk = new RevWalk(repo);
 				TreeWalk treeWalk = new TreeWalk(repo,
@@ -419,10 +422,10 @@
 			if (!checkoutAllPaths)
 				treeWalk.setFilter(PathFilterGroup.createFromStrings(paths));
 			if (isCheckoutIndex())
-				checkoutPathsFromIndex(treeWalk, dc);
+				checkoutPathsFromIndex(treeWalk, dc, options);
 			else {
 				RevCommit commit = revWalk.parseCommit(getStartPointObjectId());
-				checkoutPathsFromCommit(treeWalk, dc, commit);
+				checkoutPathsFromCommit(treeWalk, dc, commit, options);
 			}
 		} finally {
 			try {
@@ -439,7 +442,8 @@
 		return this;
 	}
 
-	private void checkoutPathsFromIndex(TreeWalk treeWalk, DirCache dc)
+	private void checkoutPathsFromIndex(TreeWalk treeWalk, DirCache dc,
+			WorkingTreeOptions options)
 			throws IOException {
 		DirCacheIterator dci = new DirCacheIterator(dc);
 		treeWalk.addTree(dci);
@@ -465,8 +469,9 @@
 					if (stage > DirCacheEntry.STAGE_0) {
 						if (checkoutStage != null) {
 							if (stage == checkoutStage.number) {
-								checkoutPath(ent, r, new CheckoutMetadata(
-										eolStreamType, filterCommand));
+								checkoutPath(ent, r, options,
+										new CheckoutMetadata(eolStreamType,
+												filterCommand));
 								actuallyModifiedPaths.add(path);
 							}
 						} else {
@@ -475,8 +480,9 @@
 							throw new JGitInternalException(e.getMessage(), e);
 						}
 					} else {
-						checkoutPath(ent, r, new CheckoutMetadata(eolStreamType,
-								filterCommand));
+						checkoutPath(ent, r, options,
+								new CheckoutMetadata(eolStreamType,
+										filterCommand));
 						actuallyModifiedPaths.add(path);
 					}
 				}
@@ -488,7 +494,7 @@
 	}
 
 	private void checkoutPathsFromCommit(TreeWalk treeWalk, DirCache dc,
-			RevCommit commit) throws IOException {
+			RevCommit commit, WorkingTreeOptions options) throws IOException {
 		treeWalk.addTree(commit.getTree());
 		final ObjectReader r = treeWalk.getObjectReader();
 		DirCacheEditor editor = dc.editor();
@@ -510,7 +516,7 @@
 					}
 					ent.setObjectId(blobId);
 					ent.setFileMode(mode);
-					checkoutPath(ent, r,
+					checkoutPath(ent, r, options,
 							new CheckoutMetadata(eolStreamType, filterCommand));
 					actuallyModifiedPaths.add(path);
 				}
@@ -520,10 +526,10 @@
 	}
 
 	private void checkoutPath(DirCacheEntry entry, ObjectReader reader,
-			CheckoutMetadata checkoutMetadata) {
+			WorkingTreeOptions options, CheckoutMetadata checkoutMetadata) {
 		try {
 			DirCacheCheckout.checkoutEntry(repo, entry, reader, true,
-					checkoutMetadata);
+					checkoutMetadata, options);
 		} catch (IOException e) {
 			throw new JGitInternalException(MessageFormat.format(
 					JGitText.get().checkoutConflictWithFile,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
index 1004d3e..17036a9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
@@ -48,6 +48,7 @@
 import org.eclipse.jgit.treewalk.AbstractTreeIterator;
 import org.eclipse.jgit.treewalk.FileTreeIterator;
 import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.WorkingTreeOptions;
 
 /**
  * Command class to apply a stashed commit.
@@ -382,6 +383,8 @@
 	private void resetUntracked(RevTree tree) throws CheckoutConflictException,
 			IOException {
 		Set<String> actuallyModifiedPaths = new HashSet<>();
+		WorkingTreeOptions options = repo.getConfig()
+				.get(WorkingTreeOptions.KEY);
 		// TODO maybe NameConflictTreeWalk ?
 		try (TreeWalk walk = new TreeWalk(repo)) {
 			walk.addTree(tree);
@@ -413,7 +416,7 @@
 					}
 				}
 
-				checkoutPath(entry, reader,
+				checkoutPath(entry, reader, options,
 						new CheckoutMetadata(eolStreamType, null));
 				actuallyModifiedPaths.add(entry.getPathString());
 			}
@@ -426,10 +429,10 @@
 	}
 
 	private void checkoutPath(DirCacheEntry entry, ObjectReader reader,
-			CheckoutMetadata checkoutMetadata) {
+			WorkingTreeOptions options, CheckoutMetadata checkoutMetadata) {
 		try {
 			DirCacheCheckout.checkoutEntry(repo, entry, reader, true,
-					checkoutMetadata);
+					checkoutMetadata, options);
 		} catch (IOException e) {
 			throw new JGitInternalException(MessageFormat.format(
 					JGitText.get().checkoutConflictWithFile,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
index f6fc393..2365c90 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
@@ -143,6 +143,8 @@
 
 	private boolean performingCheckout;
 
+	private WorkingTreeOptions options;
+
 	private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
 
 	/**
@@ -362,9 +364,12 @@
 	 * Processing an entry in the context of {@link #prescanOneTree()} when only
 	 * one tree is given
 	 *
-	 * @param m the tree to merge
-	 * @param i the index
-	 * @param f the working tree
+	 * @param m
+	 *            the tree to merge
+	 * @param i
+	 *            the index
+	 * @param f
+	 *            the working tree
 	 * @throws IOException
 	 */
 	void processEntry(CanonicalTreeParser m, DirCacheBuildIterator i,
@@ -489,6 +494,8 @@
 			MissingObjectException, IncorrectObjectTypeException,
 			CheckoutConflictException, IndexWriteException, CanceledException {
 		toBeDeleted.clear();
+		options = repo.getConfig()
+				.get(WorkingTreeOptions.KEY);
 		try (ObjectReader objectReader = repo.getObjectDatabase().newReader()) {
 			if (headCommitTree != null)
 				preScanTwoTrees();
@@ -558,7 +565,8 @@
 					if (FileMode.GITLINK.equals(entry.getRawMode())) {
 						checkoutGitlink(path, entry);
 					} else {
-						checkoutEntry(repo, entry, objectReader, false, meta);
+						checkoutEntry(repo, entry, objectReader, false, meta,
+								options);
 					}
 					e = null;
 
@@ -594,7 +602,7 @@
 						}
 						if (entry.getStage() == DirCacheEntry.STAGE_3) {
 							checkoutEntry(repo, entry, objectReader, false,
-									null);
+									null, options);
 							break;
 						}
 						++entryIdx;
@@ -1226,7 +1234,7 @@
 				checkoutEntry(repo, e, walk.getObjectReader(), false,
 						new CheckoutMetadata(walk.getEolStreamType(CHECKOUT_OP),
 								walk.getFilterCommand(
-										Constants.ATTR_FILTER_TYPE_SMUDGE)));
+										Constants.ATTR_FILTER_TYPE_SMUDGE)), options);
 			}
 		}
 	}
@@ -1392,12 +1400,6 @@
 	 * cannot be renamed to file or link without deleting it recursively.
 	 * </p>
 	 *
-	 * <p>
-	 * TODO: this method works directly on File IO, we may need another
-	 * abstraction (like WorkingTreeIterator). This way we could tell e.g.
-	 * Eclipse that Files in the workspace got changed
-	 * </p>
-	 *
 	 * @param repo
 	 *            repository managing the destination work tree.
 	 * @param entry
@@ -1407,15 +1409,16 @@
 	 * @throws java.io.IOException
 	 * @since 3.6
 	 * @deprecated since 5.1, use
-	 *             {@link #checkoutEntry(Repository, DirCacheEntry, ObjectReader, boolean, CheckoutMetadata)}
+	 *             {@link #checkoutEntry(Repository, DirCacheEntry, ObjectReader, boolean, CheckoutMetadata, WorkingTreeOptions)}
 	 *             instead
 	 */
 	@Deprecated
 	public static void checkoutEntry(Repository repo, DirCacheEntry entry,
 			ObjectReader or) throws IOException {
-		checkoutEntry(repo, entry, or, false, null);
+		checkoutEntry(repo, entry, or, false, null, null);
 	}
 
+
 	/**
 	 * Updates the file in the working tree with content and mode from an entry
 	 * in the index. The new content is first written to a new temporary file in
@@ -1428,12 +1431,6 @@
 	 * recursively, independently if has any content.
 	 * </p>
 	 *
-	 * <p>
-	 * TODO: this method works directly on File IO, we may need another
-	 * abstraction (like WorkingTreeIterator). This way we could tell e.g.
-	 * Eclipse that Files in the workspace got changed
-	 * </p>
-	 *
 	 * @param repo
 	 *            repository managing the destination work tree.
 	 * @param entry
@@ -1452,12 +1449,58 @@
 	 *            </ul>
 	 * @throws java.io.IOException
 	 * @since 4.2
+	 * @deprecated since 6.3, use
+	 *             {@link #checkoutEntry(Repository, DirCacheEntry, ObjectReader, boolean, CheckoutMetadata, WorkingTreeOptions)}
+	 *             instead
 	 */
+	@Deprecated
 	public static void checkoutEntry(Repository repo, DirCacheEntry entry,
 			ObjectReader or, boolean deleteRecursive,
 			CheckoutMetadata checkoutMetadata) throws IOException {
-		if (checkoutMetadata == null)
+		checkoutEntry(repo, entry, or, deleteRecursive, checkoutMetadata, null);
+	}
+
+	/**
+	 * Updates the file in the working tree with content and mode from an entry
+	 * in the index. The new content is first written to a new temporary file in
+	 * the same directory as the real file. Then that new file is renamed to the
+	 * final filename.
+	 *
+	 * <p>
+	 * <b>Note:</b> if the entry path on local file system exists as a file, it
+	 * will be deleted and if it exists as a directory, it will be deleted
+	 * recursively, independently if has any content.
+	 * </p>
+	 *
+	 * @param repo
+	 *            repository managing the destination work tree.
+	 * @param entry
+	 *            the entry containing new mode and content
+	 * @param or
+	 *            object reader to use for checkout
+	 * @param deleteRecursive
+	 *            true to recursively delete final path if it exists on the file
+	 *            system
+	 * @param checkoutMetadata
+	 *            containing
+	 *            <ul>
+	 *            <li>smudgeFilterCommand to be run for smudging the entry to be
+	 *            checked out</li>
+	 *            <li>eolStreamType used for stream conversion</li>
+	 *            </ul>
+	 * @param options
+	 *            {@link WorkingTreeOptions} that are effective; if {@code null}
+	 *            they are loaded from the repository config
+	 * @throws java.io.IOException
+	 * @since 6.3
+	 */
+	public static void checkoutEntry(Repository repo, DirCacheEntry entry,
+			ObjectReader or, boolean deleteRecursive,
+			CheckoutMetadata checkoutMetadata, WorkingTreeOptions options)
+			throws IOException {
+		if (checkoutMetadata == null) {
 			checkoutMetadata = CheckoutMetadata.EMPTY;
+		}
 		ObjectLoader ol = or.open(entry.getObjectId());
 		File f = new File(repo.getWorkTree(), entry.getPathString());
 		File parentDir = f.getParentFile();
@@ -1466,7 +1509,8 @@
 		}
 		FileUtils.mkdirs(parentDir, true);
 		FS fs = repo.getFS();
-		WorkingTreeOptions opt = repo.getConfig().get(WorkingTreeOptions.KEY);
+		WorkingTreeOptions opt = options != null ? options
+				: repo.getConfig().get(WorkingTreeOptions.KEY);
 		if (entry.getFileMode() == FileMode.SYMLINK
 				&& opt.getSymLinks() == SymLinks.TRUE) {
 			byte[] bytes = ol.getBytes();
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 b108b0a..d8a61ec 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
@@ -1004,6 +1004,12 @@
 			return wtMode;
 		}
 		final FileMode iMode = indexIter.getEntryFileMode();
+		if (iMode == FileMode.SYMLINK
+				&& getOptions().getSymLinks() == SymLinks.FALSE
+				&& (wtMode == FileMode.REGULAR_FILE
+						|| wtMode == FileMode.EXECUTABLE_FILE)) {
+			return iMode;
+		}
 		if (getOptions().isFileMode() && iMode != FileMode.GITLINK && iMode != FileMode.TREE) {
 			return wtMode;
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/WorkTreeUpdater.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/WorkTreeUpdater.java
index fb0b33a..88529ba 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/WorkTreeUpdater.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/WorkTreeUpdater.java
@@ -495,7 +495,9 @@
 				new File(nonNullNonBareRepo().getWorkTree(), entry.getKey()).mkdirs();
 			} else {
 				DirCacheCheckout.checkoutEntry(
-						repo, dirCacheEntry, reader, false, checkoutMetadata.get(entry.getKey()));
+						repo, dirCacheEntry, reader, false,
+						checkoutMetadata.get(entry.getKey()),
+						workingTreeOptions);
 				result.modifiedFiles.add(entry.getKey());
 			}
 		}
@@ -520,7 +522,8 @@
 			DirCacheEntry entry = dirCache.getEntry(path);
 			if (entry != null) {
 				DirCacheCheckout.checkoutEntry(
-						repo, entry, reader, false, cleanupMetadata.get(path));
+						repo, entry, reader, false, cleanupMetadata.get(path),
+						workingTreeOptions);
 			}
 		}
 	}
@@ -554,22 +557,30 @@
 		if (inCore) {
 			return;
 		}
-		CheckoutMetadata checkoutMetadata = new CheckoutMetadata(streamType, smudgeCommand);
+		CheckoutMetadata metadata = new CheckoutMetadata(streamType,
+				smudgeCommand);
 		if (safeWrite) {
-			try (org.eclipse.jgit.util.TemporaryBuffer buffer =
-					new org.eclipse.jgit.util.TemporaryBuffer.LocalFile(null)) {
-				// Write to a buffer and copy to the file only if everything was fine.
-				DirCacheCheckout.getContent(
-						repo, path, checkoutMetadata, resultStreamLoader, null, buffer);
-				InputStream bufIn = buffer.openInputStream();
-				Files.copy(bufIn, file.toPath(), StandardCopyOption.REPLACE_EXISTING);
+			// Write to a buffer and copy to the file only if everything was
+			// fine.
+			TemporaryBuffer buffer = new TemporaryBuffer.LocalFile(null);
+			try {
+				try (TemporaryBuffer buf = buffer) {
+					DirCacheCheckout.getContent(repo, path, metadata,
+							resultStreamLoader, workingTreeOptions, buf);
+				}
+				try (InputStream bufIn = buffer.openInputStream()) {
+					Files.copy(bufIn, file.toPath(),
+							StandardCopyOption.REPLACE_EXISTING);
+				}
+			} finally {
+				buffer.destroy();
 			}
 			return;
 		}
-		OutputStream outputStream = new FileOutputStream(file);
-		DirCacheCheckout.getContent(
-				repo, path, checkoutMetadata, resultStreamLoader, null, outputStream);
-
+		try (OutputStream outputStream = new FileOutputStream(file)) {
+			DirCacheCheckout.getContent(repo, path, metadata,
+					resultStreamLoader, workingTreeOptions, outputStream);
+		}
 	}
 
 	/**