Merge branch 'stable-5.6' into stable-5.7

* stable-5.6:
  FS.runInShell(): handle quoted filters and hooksPath containing blanks
  Handle non-normalized index also for executable files

Change-Id: I240377e87c073ee7a621a88e39fc319c59fa037a
Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
index 4cc3ca0..8084505 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
@@ -16,6 +16,7 @@
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
 import java.io.File;
 import java.util.Date;
@@ -600,41 +601,64 @@
 		}
 	}
 
-	@Test
-	public void commitWithAutoCrlfAndNonNormalizedIndex() throws Exception {
+	private void nonNormalizedIndexTest(boolean executable) throws Exception {
+		String mode = executable ? "100755" : "100644";
 		try (Git git = new Git(db)) {
 			// Commit a file with CR/LF into the index
 			FileBasedConfig config = db.getConfig();
 			config.setString("core", null, "autocrlf", "false");
 			config.save();
-			writeTrashFile("file.txt", "line 1\r\nline 2\r\n");
+			File testFile = writeTrashFile("file.txt", "line 1\r\nline 2\r\n");
+			if (executable) {
+				FS.DETECTED.setExecute(testFile, true);
+			}
 			git.add().addFilepattern("file.txt").call();
 			git.commit().setMessage("Initial").call();
 			assertEquals(
-					"[file.txt, mode:100644, content:line 1\r\nline 2\r\n]",
+					"[file.txt, mode:" + mode
+							+ ", content:line 1\r\nline 2\r\n]",
 					indexState(CONTENT));
 			config.setString("core", null, "autocrlf", "true");
 			config.save();
 			writeTrashFile("file.txt", "line 1\r\nline 1.5\r\nline 2\r\n");
-			writeTrashFile("file2.txt", "new\r\nfile\r\n");
+			testFile = writeTrashFile("file2.txt", "new\r\nfile\r\n");
+			if (executable) {
+				FS.DETECTED.setExecute(testFile, true);
+			}
 			git.add().addFilepattern("file.txt").addFilepattern("file2.txt")
 					.call();
 			git.commit().setMessage("Second").call();
 			assertEquals(
-					"[file.txt, mode:100644, content:line 1\r\nline 1.5\r\nline 2\r\n]"
-							+ "[file2.txt, mode:100644, content:new\nfile\n]",
+					"[file.txt, mode:" + mode
+							+ ", content:line 1\r\nline 1.5\r\nline 2\r\n]"
+							+ "[file2.txt, mode:" + mode
+							+ ", content:new\nfile\n]",
 					indexState(CONTENT));
 			writeTrashFile("file2.txt", "new\r\nfile\r\ncontent\r\n");
 			git.add().addFilepattern("file2.txt").call();
 			git.commit().setMessage("Third").call();
 			assertEquals(
-					"[file.txt, mode:100644, content:line 1\r\nline 1.5\r\nline 2\r\n]"
-							+ "[file2.txt, mode:100644, content:new\nfile\ncontent\n]",
+					"[file.txt, mode:" + mode
+							+ ", content:line 1\r\nline 1.5\r\nline 2\r\n]"
+							+ "[file2.txt, mode:" + mode
+							+ ", content:new\nfile\ncontent\n]",
 					indexState(CONTENT));
 		}
 	}
 
 	@Test
+	public void commitWithAutoCrlfAndNonNormalizedIndex() throws Exception {
+		nonNormalizedIndexTest(false);
+	}
+
+	@Test
+	public void commitExecutableWithAutoCrlfAndNonNormalizedIndex()
+			throws Exception {
+		assumeTrue(FS.DETECTED.supportsExecute());
+		nonNormalizedIndexTest(true);
+	}
+
+	@Test
 	public void testDeletionConflictWithAutoCrlf() throws Exception {
 		try (Git git = new Git(db)) {
 			// Commit a file with CR/LF into the index
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StatusCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StatusCommandTest.java
index e0d9cbc..7e0de82 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StatusCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StatusCommandTest.java
@@ -12,6 +12,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import java.io.File;
 import java.io.IOException;
@@ -21,6 +22,8 @@
 import org.eclipse.jgit.errors.NoWorkTreeException;
 import org.eclipse.jgit.junit.RepositoryTestCase;
 import org.eclipse.jgit.lib.Sets;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
 import org.junit.Test;
 
 public class StatusCommandTest extends RepositoryTestCase {
@@ -135,4 +138,26 @@
 			assertEquals(Sets.of("a", "D/b", "D/D/d"), stat.getModified());
 		}
 	}
+
+	@Test
+	public void testExecutableWithNonNormalizedIndex() throws Exception {
+		assumeTrue(FS.DETECTED.supportsExecute());
+		try (Git git = new Git(db)) {
+			// Commit a file with CR/LF into the index
+			FileBasedConfig config = db.getConfig();
+			config.setString("core", null, "autocrlf", "false");
+			config.save();
+			File testFile = writeTrashFile("file.txt", "line 1\r\nline 2\r\n");
+			FS.DETECTED.setExecute(testFile, true);
+			git.add().addFilepattern("file.txt").call();
+			git.commit().setMessage("Initial").call();
+			assertEquals(
+					"[file.txt, mode:100755, content:line 1\r\nline 2\r\n]",
+					indexState(CONTENT));
+			config.setString("core", null, "autocrlf", "true");
+			config.save();
+			Status status = git.status().call();
+			assertTrue("Expected no differences", status.isClean());
+		}
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java
index 254878a..33ed360 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java
@@ -259,6 +259,38 @@
 	}
 
 	@Test
+	public void testHookPathWithBlank() throws Exception {
+		assumeSupportedPlatform();
+
+		File file = writeHookFile("../../a directory/" + PreCommitHook.NAME,
+				"#!/bin/sh\necho \"test $1 $2\"\nread INPUT\necho $INPUT\n"
+						+ "echo $GIT_DIR\necho $GIT_WORK_TREE\necho 1>&2 \"stderr\"");
+		StoredConfig cfg = db.getConfig();
+		cfg.load();
+		cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
+				ConfigConstants.CONFIG_KEY_HOOKS_PATH,
+				file.getParentFile().getAbsolutePath());
+		cfg.save();
+		try (ByteArrayOutputStream out = new ByteArrayOutputStream();
+				ByteArrayOutputStream err = new ByteArrayOutputStream()) {
+			ProcessResult res = FS.DETECTED.runHookIfPresent(db,
+					PreCommitHook.NAME, new String[] { "arg1", "arg2" },
+					new PrintStream(out), new PrintStream(err), "stdin");
+
+			assertEquals("unexpected hook output",
+					"test arg1 arg2\nstdin\n"
+							+ db.getDirectory().getAbsolutePath() + '\n'
+							+ db.getWorkTree().getAbsolutePath() + '\n',
+					out.toString("UTF-8"));
+			assertEquals("unexpected output on stderr stream", "stderr\n",
+					err.toString("UTF-8"));
+			assertEquals("unexpected exit code", 0, res.getExitCode());
+			assertEquals("unexpected process status", ProcessResult.Status.OK,
+					res.getStatus());
+		}
+	}
+
+	@Test
 	public void testFailedPreCommitHookBlockCommit() throws Exception {
 		assumeSupportedPlatform();
 
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 2d406bd..994af26 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
@@ -1467,7 +1467,7 @@
 		}
 		// Read blob from index and check for CR/LF-delimited text.
 		DirCacheEntry entry = dirCache.getDirCacheEntry();
-		if (FileMode.REGULAR_FILE.equals(entry.getFileMode())) {
+		if ((entry.getRawMode() & FileMode.TYPE_MASK) == FileMode.TYPE_FILE) {
 			ObjectId blobId = entry.getObjectId();
 			if (entry.getStage() > 0
 					&& entry.getStage() != DirCacheEntry.STAGE_2) {
@@ -1484,7 +1484,10 @@
 						break;
 					}
 					if (entry.getStage() == DirCacheEntry.STAGE_2) {
-						blobId = entry.getObjectId();
+						if ((entry.getRawMode()
+								& FileMode.TYPE_MASK) == FileMode.TYPE_FILE) {
+							blobId = entry.getObjectId();
+						}
 						break;
 					}
 				}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
index 216bf2c..988953b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
@@ -1747,7 +1747,7 @@
 			return new ProcessResult(Status.NOT_PRESENT);
 		}
 		String cmd = hookFile.getAbsolutePath();
-		ProcessBuilder hookProcess = runInShell(cmd, args);
+		ProcessBuilder hookProcess = runInShell(shellQuote(cmd), args);
 		hookProcess.directory(runDirectory.getAbsoluteFile());
 		Map<String, String> environment = hookProcess.environment();
 		environment.put(Constants.GIT_DIR_KEY,
@@ -1770,6 +1770,21 @@
 		}
 	}
 
+	/**
+	 * Quote a string (such as a file system path obtained from a Java
+	 * {@link File} or {@link Path} object) such that it can be passed as first
+	 * argument to {@link #runInShell(String, String[])}.
+	 * <p>
+	 * This default implementation returns the string unchanged.
+	 * </p>
+	 *
+	 * @param cmd
+	 *            the String to quote
+	 * @return the quoted string
+	 */
+	String shellQuote(String cmd) {
+		return cmd;
+	}
 
 	/**
 	 * Tries to find a hook matching the given one in the given repository.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java
index a082200..c9d2770 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java
@@ -228,7 +228,7 @@
 		List<String> argv = new ArrayList<>(4 + args.length);
 		argv.add("sh"); //$NON-NLS-1$
 		argv.add("-c"); //$NON-NLS-1$
-		argv.add("$0 \"$@\""); //$NON-NLS-1$
+		argv.add(cmd + " \"$@\""); //$NON-NLS-1$
 		argv.add(cmd);
 		argv.addAll(Arrays.asList(args));
 		ProcessBuilder proc = new ProcessBuilder();
@@ -236,6 +236,11 @@
 		return proc;
 	}
 
+	@Override
+	String shellQuote(String cmd) {
+		return QuotedString.BOURNE.quote(cmd);
+	}
+
 	/** {@inheritDoc} */
 	@Override
 	public ProcessResult runHookIfPresent(Repository repository, String hookName,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java
index 82b2818..d53bff7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java
@@ -116,14 +116,19 @@
 		List<String> argv = new ArrayList<>(4 + args.length);
 		argv.add("sh.exe"); //$NON-NLS-1$
 		argv.add("-c"); //$NON-NLS-1$
-		argv.add("$0 \"$@\""); //$NON-NLS-1$
-		argv.add(cmd.replace(File.separatorChar, '/'));
+		argv.add(cmd + " \"$@\""); //$NON-NLS-1$
+		argv.add(cmd);
 		argv.addAll(Arrays.asList(args));
 		ProcessBuilder proc = new ProcessBuilder();
 		proc.command(argv);
 		return proc;
 	}
 
+	@Override
+	String shellQuote(String cmd) {
+		return QuotedString.BOURNE.quote(cmd.replace(File.separatorChar, '/'));
+	}
+
 	/** {@inheritDoc} */
 	@Override
 	public String relativize(String base, String other) {