Merge "ReceivePack: Use error message if set"
diff --git a/Documentation/config-options.md b/Documentation/config-options.md
new file mode 100644
index 0000000..94ef5b9
--- /dev/null
+++ b/Documentation/config-options.md
@@ -0,0 +1,57 @@
+# JGit configuration options
+
+## Legend
+
+| git option | description |
+|------------|-------------|
+| ✅ | option defined by native git |
+| ⃞ | jgit custom option not supported by native git |
+
+## __core__ options
+
+|  option | default | git option | description |
+|---------|---------|------------|-------------|
+| `core.bigFileThreshold` | `52428800` (50 MiB) | ✅ | Maximum file size that will be delta compressed. Files larger than this size are stored deflated, without attempting delta compression. |
+| `core.compression` | `-1` (default compression) | ✅ | An integer -1..9, indicating a default compression level. -1 is the zlib default. 0 means no compression, and 1..9 are various speed/size tradeoffs, 9 being slowest.|
+
+## __gc__ options
+
+|  option | default | git option | description |
+|---------|---------|------------|-------------|
+| `gc.aggressiveDepth` | 50 | ✅ | The depth parameter used in the delta compression algorithm used by aggressive garbage collection. |
+| `gc.aggressiveWindow` | 250 | ✅ | The window size parameter used in the delta compression algorithm used by aggressive garbage collection. |
+| `gc.auto` | `6700` | ✅ | Number of loose objects until auto gc combines all loose objects into a pack and consolidates all existing packs into one. Setting to 0 disables automatic packing of loose objects. |
+| `gc.autoDetach` | `true` |  ✅ | Make auto gc return immediately and run in background. |
+| `gc.autoPackLimit` | `50` |  ✅ | Number of packs until auto gc consolidates existing packs (except those marked with a .keep file) into a single pack. Setting `gc.autoPackLimit` to 0 disables automatic consolidation of packs. |
+| `gc.logExpiry` | `1.day.ago` | ✅ | If the file `gc.log` exists, then auto gc will print its content and exit successfully instead of running unless that file is more than `gc.logExpiry` old. |
+| `gc.pruneExpire` | `2.weeks.ago` | ✅ | Grace period after which unreachable objects will be pruned. |
+| `gc.prunePackExpire` | `1.hour.ago` |  ⃞ | Grace period after which packfiles only containing unreachable objects will be pruned. |
+
+## __pack__ options
+
+|  option | default | git option | description |
+|---------|---------|------------|-------------|
+| `pack.bitmapContiguousCommitCount` | `100` | ⃞ | Count of most recent commits for which to build bitmaps. |
+| `pack.bitmapDistantCommitSpan` | `5000` | ⃞ | Span of commits when building bitmaps for distant history. |
+| `pack.bitmapExcessiveBranchCount` | `100` | ⃞ | The count of branches deemed "excessive". If the count of branches in a repository exceeds this number and bitmaps are enabled, "inactive" branches will have fewer bitmaps than "active" branches. |
+| `pack.bitmapInactiveBranchAgeInDays` | `90` | ⃞ | Age in days that marks a branch as "inactive" for bitmap creation. |
+| `pack.bitmapRecentCommitCount` | `20000`  | ⃞ | Count at which to switch from `bitmapRecentCommitSpan` to `bitmapDistantCommitSpan`. |
+| `pack.bitmapRecentCommitSpan` | `100` | ⃞ | Span of commits when building bitmaps for recent history. |
+| `pack.buildBitmaps` | `true` | ⃞ synonym for `repack.writeBitmaps` | Whether index writer is allowed to build bitmaps for indexes. |
+| `pack.compression` | `core.compression` | ✅ | Compression level applied to objects in the pack. |
+| `pack.cutDeltaChains` | `false` | ⃞ | Whether existing delta chains should be cut at {@link #getMaxDeltaDepth() |
+| `pack.deltaCacheLimit` | `100` | ✅ | Maximum size in bytes of a delta to cache. |
+| `pack.deltaCacheSize` | `52428800` (50 MiB) | ✅ | Size of the in-memory delta cache. |
+| `pack.deltaCompression` | `true` | ⃞ | Whether the writer will create new deltas on the fly. `true` if the pack writer will create a new delta when either `pack.reuseDeltas` is false, or no suitable delta is available for reuse. |
+| `pack.depth` | `50` | ✅ | Maximum depth of delta chain set up for the pack writer. |
+| `pack.indexVersion` | `2` | ✅ | Pack index file format version. |
+| `pack.minSizePreventRacyPack` | `104857600` (100 MiB) | ⃞ | Minimum packfile size for which we wait before opening a newly written pack to prevent its lastModified timestamp could be racy if `pack.waitPreventRacyPack` is `true`. |
+| `pack.preserveOldPacks` | `false` | ⃞ | Whether to preserve old packs in a preserved directory. |
+| `prunePreserved`, only via API of PackConfig | `false` | ⃞ | Whether to remove preserved pack files in a preserved directory. |
+| `pack.reuseDeltas` | `true` |⃞ | Whether to reuse deltas existing in repository. |
+| `pack.reuseObjects` | `true` | ⃞ | Whether to reuse existing objects representation in repository. |
+| `pack.singlePack` | `false` | ⃞ | Whether all of `refs/*` should be packed in a single pack. |
+| `pack.threads` | `0` (auto-detect number of processors) | ✅ | Number of threads to use for delta compression. |
+| `pack.waitPreventRacyPack` | `false` | ⃞ | Whether we wait before opening a newly written pack to prevent its lastModified timestamp could be racy. |
+| `pack.window` | `10` | ✅ | Number of objects to try when looking for a delta base per thread searching for deltas. |
+| `pack.windowMemory` | `0` (unlimited) | ✅ | Maximum number of bytes to put into the delta search window. |
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/FileUtilsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilsTest.java
index f9ec5d8..2b1fb2e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilsTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilsTest.java
@@ -79,6 +79,15 @@
 	}
 
 	@Test
+	public void testDeleteReadOnlyFile() throws IOException {
+		File f = new File(trash, "f");
+		FileUtils.createNewFile(f);
+		assertTrue(f.setReadOnly());
+		FileUtils.delete(f);
+		assertFalse(f.exists());
+	}
+
+	@Test
 	public void testDeleteRecursive() throws IOException {
 		File f1 = new File(trash, "test/test/a");
 		FileUtils.mkdirs(f1.getParentFile());
@@ -339,6 +348,34 @@
 	}
 
 	@Test
+	public void testDeleteNonRecursiveTreeNotOk() throws IOException {
+		File t = new File(trash, "t");
+		FileUtils.mkdir(t);
+		File f = new File(t, "f");
+		FileUtils.createNewFile(f);
+		try {
+			FileUtils.delete(t, FileUtils.EMPTY_DIRECTORIES_ONLY);
+			fail("expected failure to delete f");
+		} catch (IOException e) {
+			assertTrue(e.getMessage().endsWith(t.getAbsolutePath()));
+		}
+		assertTrue(f.exists());
+		assertTrue(t.exists());
+	}
+
+	@Test
+	public void testDeleteNonRecursiveTreeIgnoreError() throws IOException {
+		File t = new File(trash, "t");
+		FileUtils.mkdir(t);
+		File f = new File(t, "f");
+		FileUtils.createNewFile(f);
+		FileUtils.delete(t,
+				FileUtils.EMPTY_DIRECTORIES_ONLY | FileUtils.IGNORE_ERRORS);
+		assertTrue(f.exists());
+		assertTrue(t.exists());
+	}
+
+	@Test
 	public void testRenameOverNonExistingFile() throws IOException {
 		File d = new File(trash, "d");
 		FileUtils.mkdirs(d);
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/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
index e607edc..eef822f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
@@ -542,4 +542,124 @@
 	 * @since 5.1.13
 	 */
 	public static final String CONFIG_JMX_SECTION = "jmx";
+
+	/**
+	 * The "pack.bigfilethreshold" key
+	 * @since 5.8
+	 */
+	public static final String CONFIG_KEY_BIGFILE_THRESHOLD = "bigfilethreshold";
+
+	/**
+	 * The "pack.bitmapContiguousCommitCount" key
+	 * @since 5.8
+	 */
+	public static final String CONFIG_KEY_BITMAP_CONTIGUOUS_COMMIT_COUNT = "bitmapcontiguouscommitcount";
+
+	/**
+	 * The "pack.bitmapDistantCommitSpan" key
+	 * @since 5.8
+	 */
+	public static final String CONFIG_KEY_BITMAP_DISTANT_COMMIT_SPAN = "bitmapdistantcommitspan";
+
+	/**
+	 * The "pack.bitmapExcessiveBranchCount" key
+	 * @since 5.8
+	 */
+	public static final String CONFIG_KEY_BITMAP_EXCESSIVE_BRANCH_COUNT = "bitmapexcessivebranchcount";
+
+	/**
+	 * The "pack.bitmapInactiveBranchAgeInDays" key
+	 * @since 5.8
+	 */
+	public static final String CONFIG_KEY_BITMAP_INACTIVE_BRANCH_AGE_INDAYS = "bitmapinactivebranchageindays";
+
+	/**
+	 * The "pack.bitmapRecentCommitSpan" key
+	 * @since 5.8
+	 */
+	public static final String CONFIG_KEY_BITMAP_RECENT_COMMIT_COUNT = "bitmaprecentcommitspan";
+
+	/**
+	 * The "pack.buildBitmaps" key
+	 * @since 5.8
+	 */
+	public static final String CONFIG_KEY_BUILD_BITMAPS = "buildbitmaps";
+
+	/**
+	 * The "pack.cutDeltaChains" key
+	 * @since 5.8
+	 */
+	public static final String CONFIG_KEY_CUT_DELTACHAINS = "cutdeltachains";
+
+	/**
+	 * The "pack.deltaCacheLimit" key
+	 * @since 5.8
+	 */
+	public static final String CONFIG_KEY_DELTA_CACHE_LIMIT = "deltacachelimit";
+
+	/**
+	 * The "pack.deltaCacheSize" key
+	 * @since 5.8
+	 */
+	public static final String CONFIG_KEY_DELTA_CACHE_SIZE = "deltacachesize";
+
+	/**
+	 * The "pack.deltaCompression" key
+	 * @since 5.8
+	 */
+	public static final String CONFIG_KEY_DELTA_COMPRESSION = "deltacompression";
+
+	/**
+	 * The "pack.depth" key
+	 * @since 5.8
+	 */
+	public static final String CONFIG_KEY_DEPTH = "depth";
+
+	/**
+	 * The "pack.minSizePreventRacyPack" key
+	 * @since 5.8
+	 */
+	public static final String CONFIG_KEY_MIN_SIZE_PREVENT_RACYPACK = "minsizepreventracypack";
+
+	/**
+	 * The "pack.reuseDeltas" key
+	 * @since 5.8
+	 */
+	public static final String CONFIG_KEY_REUSE_DELTAS = "reusedeltas";
+
+	/**
+	 * The "pack.reuseObjects" key
+	 * @since 5.8
+	 */
+	public static final String CONFIG_KEY_REUSE_OBJECTS = "reuseobjects";
+
+	/**
+	 * The "pack.singlePack" key
+	 * @since 5.8
+	 */
+	public static final String CONFIG_KEY_SINGLE_PACK = "singlepack";
+
+	/**
+	 * The "pack.threads" key
+	 * @since 5.8
+	 */
+	public static final String CONFIG_KEY_THREADS = "threads";
+
+	/**
+	 * The "pack.waitPreventRacyPack" key
+	 * @since 5.8
+	 */
+	public static final String CONFIG_KEY_WAIT_PREVENT_RACYPACK = "waitpreventracypack";
+
+	/**
+	 * The "pack.window" key
+	 * @since 5.8
+	 */
+	public static final String CONFIG_KEY_WINDOW = "window";
+
+	/**
+	 * The "pack.windowMemory" key
+	 * @since 5.8
+	 */
+	public static final String CONFIG_KEY_WINDOW_MEMORY = "windowmemory";
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepositoryBuilder.java
index 553d875..d476a0d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepositoryBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepositoryBuilder.java
@@ -32,7 +32,7 @@
  * <pre>
  * new FileRepositoryBuilder() //
  * 		.setGitDir(gitDirArgument) // --git-dir if supplied, no-op if null
- * 		.readEnviroment() // scan environment GIT_* variables
+ * 		.readEnvironment() // scan environment GIT_* variables
  * 		.findGitDir() // scan up the file system tree
  * 		.build()
  * </pre>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java
index 259f011..f76dd27 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java
@@ -11,6 +11,31 @@
 
 package org.eclipse.jgit.storage.pack;
 
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_CORE_SECTION;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BIGFILE_THRESHOLD;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BITMAP_CONTIGUOUS_COMMIT_COUNT;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BITMAP_DISTANT_COMMIT_SPAN;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BITMAP_EXCESSIVE_BRANCH_COUNT;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BITMAP_INACTIVE_BRANCH_AGE_INDAYS;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BITMAP_RECENT_COMMIT_COUNT;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BUILD_BITMAPS;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_COMPRESSION;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_CUT_DELTACHAINS;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_DELTA_CACHE_LIMIT;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_DELTA_CACHE_SIZE;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_DELTA_COMPRESSION;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_DEPTH;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_INDEXVERSION;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_MIN_SIZE_PREVENT_RACYPACK;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_REUSE_DELTAS;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_REUSE_OBJECTS;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_SINGLE_PACK;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_THREADS;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_WAIT_PREVENT_RACYPACK;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_WINDOW;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_WINDOW_MEMORY;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_PACK_SECTION;
+
 import java.util.concurrent.Executor;
 import java.util.zip.Deflater;
 
@@ -1101,52 +1126,63 @@
 	 *            configuration to read properties from.
 	 */
 	public void fromConfig(Config rc) {
-		setMaxDeltaDepth(rc.getInt("pack", "depth", getMaxDeltaDepth())); //$NON-NLS-1$ //$NON-NLS-2$
-		setDeltaSearchWindowSize(rc.getInt(
-				"pack", "window", getDeltaSearchWindowSize())); //$NON-NLS-1$ //$NON-NLS-2$
-		setDeltaSearchMemoryLimit(rc.getLong(
-				"pack", "windowmemory", getDeltaSearchMemoryLimit())); //$NON-NLS-1$ //$NON-NLS-2$
-		setDeltaCacheSize(rc.getLong(
-				"pack", "deltacachesize", getDeltaCacheSize())); //$NON-NLS-1$ //$NON-NLS-2$
-		setDeltaCacheLimit(rc.getInt(
-				"pack", "deltacachelimit", getDeltaCacheLimit())); //$NON-NLS-1$ //$NON-NLS-2$
-		setCompressionLevel(rc.getInt("pack", "compression", //$NON-NLS-1$ //$NON-NLS-2$
-				rc.getInt("core", "compression", getCompressionLevel()))); //$NON-NLS-1$ //$NON-NLS-2$
-		setIndexVersion(rc.getInt("pack", "indexversion", getIndexVersion())); //$NON-NLS-1$ //$NON-NLS-2$
-		setBigFileThreshold(rc.getInt(
-				"core", "bigfilethreshold", getBigFileThreshold())); //$NON-NLS-1$ //$NON-NLS-2$
-		setThreads(rc.getInt("pack", "threads", getThreads())); //$NON-NLS-1$ //$NON-NLS-2$
+		setMaxDeltaDepth(rc.getInt(CONFIG_PACK_SECTION, CONFIG_KEY_DEPTH,
+				getMaxDeltaDepth()));
+		setDeltaSearchWindowSize(rc.getInt(CONFIG_PACK_SECTION,
+				CONFIG_KEY_WINDOW, getDeltaSearchWindowSize()));
+		setDeltaSearchMemoryLimit(rc.getLong(CONFIG_PACK_SECTION,
+				CONFIG_KEY_WINDOW_MEMORY, getDeltaSearchMemoryLimit()));
+		setDeltaCacheSize(rc.getLong(CONFIG_PACK_SECTION,
+				CONFIG_KEY_DELTA_CACHE_SIZE, getDeltaCacheSize()));
+		setDeltaCacheLimit(rc.getInt(CONFIG_PACK_SECTION,
+				CONFIG_KEY_DELTA_CACHE_LIMIT, getDeltaCacheLimit()));
+		setCompressionLevel(rc.getInt(CONFIG_PACK_SECTION,
+				CONFIG_KEY_COMPRESSION, rc.getInt(CONFIG_CORE_SECTION,
+						CONFIG_KEY_COMPRESSION, getCompressionLevel())));
+		setIndexVersion(rc.getInt(CONFIG_PACK_SECTION,
+				CONFIG_KEY_INDEXVERSION,
+				getIndexVersion()));
+		setBigFileThreshold(rc.getInt(CONFIG_CORE_SECTION,
+				CONFIG_KEY_BIGFILE_THRESHOLD, getBigFileThreshold()));
+		setThreads(rc.getInt(CONFIG_PACK_SECTION, CONFIG_KEY_THREADS,
+				getThreads()));
 
 		// These variables aren't standardized
-		//
-		setReuseDeltas(rc.getBoolean("pack", "reusedeltas", isReuseDeltas())); //$NON-NLS-1$ //$NON-NLS-2$
-		setReuseObjects(
-				rc.getBoolean("pack", "reuseobjects", isReuseObjects())); //$NON-NLS-1$ //$NON-NLS-2$
-		setDeltaCompress(
-				rc.getBoolean("pack", "deltacompression", isDeltaCompress())); //$NON-NLS-1$ //$NON-NLS-2$
-		setCutDeltaChains(
-				rc.getBoolean("pack", "cutdeltachains", getCutDeltaChains())); //$NON-NLS-1$ //$NON-NLS-2$
-		setSinglePack(
-				rc.getBoolean("pack", "singlepack", getSinglePack())); //$NON-NLS-1$ //$NON-NLS-2$
-		setBuildBitmaps(
-				rc.getBoolean("pack", "buildbitmaps", isBuildBitmaps())); //$NON-NLS-1$ //$NON-NLS-2$
-		setBitmapContiguousCommitCount(
-				rc.getInt("pack", "bitmapcontiguouscommitcount", //$NON-NLS-1$ //$NON-NLS-2$
-						getBitmapContiguousCommitCount()));
-		setBitmapRecentCommitCount(rc.getInt("pack", "bitmaprecentcommitcount", //$NON-NLS-1$ //$NON-NLS-2$
+		setReuseDeltas(rc.getBoolean(CONFIG_PACK_SECTION,
+				CONFIG_KEY_REUSE_DELTAS, isReuseDeltas()));
+		setReuseObjects(rc.getBoolean(CONFIG_PACK_SECTION,
+				CONFIG_KEY_REUSE_OBJECTS, isReuseObjects()));
+		setDeltaCompress(rc.getBoolean(CONFIG_PACK_SECTION,
+				CONFIG_KEY_DELTA_COMPRESSION, isDeltaCompress()));
+		setCutDeltaChains(rc.getBoolean(CONFIG_PACK_SECTION,
+				CONFIG_KEY_CUT_DELTACHAINS, getCutDeltaChains()));
+		setSinglePack(rc.getBoolean(CONFIG_PACK_SECTION,
+				CONFIG_KEY_SINGLE_PACK,
+				getSinglePack()));
+		setBuildBitmaps(rc.getBoolean(CONFIG_PACK_SECTION,
+				CONFIG_KEY_BUILD_BITMAPS, isBuildBitmaps()));
+		setBitmapContiguousCommitCount(rc.getInt(CONFIG_PACK_SECTION,
+				CONFIG_KEY_BITMAP_CONTIGUOUS_COMMIT_COUNT,
+				getBitmapContiguousCommitCount()));
+		setBitmapRecentCommitCount(rc.getInt(CONFIG_PACK_SECTION,
+				CONFIG_KEY_BITMAP_RECENT_COMMIT_COUNT,
 				getBitmapRecentCommitCount()));
-		setBitmapRecentCommitSpan(rc.getInt("pack", "bitmaprecentcommitspan", //$NON-NLS-1$ //$NON-NLS-2$
+		setBitmapRecentCommitSpan(rc.getInt(CONFIG_PACK_SECTION,
+				CONFIG_KEY_BITMAP_RECENT_COMMIT_COUNT,
 				getBitmapRecentCommitSpan()));
-		setBitmapDistantCommitSpan(rc.getInt("pack", "bitmapdistantcommitspan", //$NON-NLS-1$ //$NON-NLS-2$
+		setBitmapDistantCommitSpan(rc.getInt(CONFIG_PACK_SECTION,
+				CONFIG_KEY_BITMAP_DISTANT_COMMIT_SPAN,
 				getBitmapDistantCommitSpan()));
-		setBitmapExcessiveBranchCount(rc.getInt("pack", //$NON-NLS-1$
-				"bitmapexcessivebranchcount", getBitmapExcessiveBranchCount())); //$NON-NLS-1$
-		setBitmapInactiveBranchAgeInDays(
-				rc.getInt("pack", "bitmapinactivebranchageindays", //$NON-NLS-1$ //$NON-NLS-2$
-						getBitmapInactiveBranchAgeInDays()));
-		setWaitPreventRacyPack(rc.getBoolean("pack", "waitpreventracypack", //$NON-NLS-1$ //$NON-NLS-2$
-				isWaitPreventRacyPack()));
-		setMinSizePreventRacyPack(rc.getLong("pack", "minsizepreventracypack", //$NON-NLS-1$//$NON-NLS-2$
+		setBitmapExcessiveBranchCount(rc.getInt(CONFIG_PACK_SECTION,
+				CONFIG_KEY_BITMAP_EXCESSIVE_BRANCH_COUNT,
+				getBitmapExcessiveBranchCount()));
+		setBitmapInactiveBranchAgeInDays(rc.getInt(CONFIG_PACK_SECTION,
+				CONFIG_KEY_BITMAP_INACTIVE_BRANCH_AGE_INDAYS,
+				getBitmapInactiveBranchAgeInDays()));
+		setWaitPreventRacyPack(rc.getBoolean(CONFIG_PACK_SECTION,
+				CONFIG_KEY_WAIT_PREVENT_RACYPACK, isWaitPreventRacyPack()));
+		setMinSizePreventRacyPack(rc.getLong(CONFIG_PACK_SECTION,
+				CONFIG_KEY_MIN_SIZE_PREVENT_RACYPACK,
 				getMinSizePreventRacyPack()));
 	}
 
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) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
index 4831fbb..c43956e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
@@ -20,6 +20,7 @@
 import java.nio.channels.FileChannel;
 import java.nio.file.AtomicMoveNotSupportedException;
 import java.nio.file.CopyOption;
+import java.nio.file.DirectoryNotEmptyException;
 import java.nio.file.Files;
 import java.nio.file.InvalidPathException;
 import java.nio.file.LinkOption;
@@ -180,21 +181,31 @@
 		}
 
 		if (delete) {
-			Throwable t = null;
+			IOException t = null;
 			Path p = f.toPath();
-			try {
-				Files.delete(p);
-				return;
-			} catch (FileNotFoundException e) {
-				if ((options & (SKIP_MISSING | IGNORE_ERRORS)) == 0) {
-					throw new IOException(MessageFormat.format(
-							JGitText.get().deleteFileFailed,
-							f.getAbsolutePath()), e);
+			boolean tryAgain;
+			do {
+				tryAgain = false;
+				try {
+					Files.delete(p);
+					return;
+				} catch (NoSuchFileException | FileNotFoundException e) {
+					handleDeleteException(f, e, options,
+							SKIP_MISSING | IGNORE_ERRORS);
+					return;
+				} catch (DirectoryNotEmptyException e) {
+					handleDeleteException(f, e, options, IGNORE_ERRORS);
+					return;
+				} catch (IOException e) {
+					if (!f.canWrite()) {
+						tryAgain = f.setWritable(true);
+					}
+					if (!tryAgain) {
+						t = e;
+					}
 				}
-				return;
-			} catch (IOException e) {
-				t = e;
-			}
+			} while (tryAgain);
+
 			if ((options & RETRY) != 0) {
 				for (int i = 1; i < 10; i++) {
 					try {
@@ -210,11 +221,15 @@
 					}
 				}
 			}
-			if ((options & IGNORE_ERRORS) == 0) {
-				throw new IOException(MessageFormat.format(
-						JGitText.get().deleteFileFailed, f.getAbsolutePath()),
-						t);
-			}
+			handleDeleteException(f, t, options, IGNORE_ERRORS);
+		}
+	}
+
+	private static void handleDeleteException(File f, IOException e,
+			int allOptions, int checkOptions) throws IOException {
+		if (e != null && (allOptions & checkOptions) == 0) {
+			throw new IOException(MessageFormat.format(
+					JGitText.get().deleteFileFailed, f.getAbsolutePath()), e);
 		}
 	}
 
diff --git a/pom.xml b/pom.xml
index 6764644..070a1c5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -895,7 +895,7 @@
               <dependency>
                 <groupId>org.eclipse.jdt</groupId>
                 <artifactId>ecj</artifactId>
-                <version>3.20.0</version>
+                <version>3.21.0</version>
               </dependency>
             </dependencies>
           </plugin>