Merge "PackBitmapIndexV1: support parallel loading of reverse index"
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitFilter.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitFilter.java
index 1769832..0957cc8 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitFilter.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitFilter.java
@@ -125,6 +125,7 @@ public void setUploadPackFactory(UploadPackFactory<HttpServletRequest> f) {
 	 *
 	 * @param h
 	 *            A custom error handler for git-upload-pack.
+	 * @since 5.6
 	 */
 	public void setUploadPackErrorHandler(UploadPackErrorHandler h) {
 		assertNotInitialized();
@@ -163,6 +164,7 @@ public void setReceivePackFactory(ReceivePackFactory<HttpServletRequest> f) {
 	 *
 	 * @param h
 	 *            A custom error handler for git-receive-pack.
+	 * @since 5.7
 	 */
 	public void setReceivePackErrorHandler(ReceivePackErrorHandler h) {
 		assertNotInitialized();
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackErrorHandler.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackErrorHandler.java
index ee66cb1..a7ca4d0 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackErrorHandler.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackErrorHandler.java
@@ -31,7 +31,7 @@
  * If a custom handler is not specified, JGit will use the default error
  * handler.
  *
- * @since 5.6
+ * @since 5.7
  */
 public interface ReceivePackErrorHandler {
 	/**
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java
index 664615e..6bfe706 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java
@@ -80,6 +80,7 @@ public String toString() {
 	 * @param userGitConfig
 	 *            set another user-level git config
 	 * @return the old user-level git config
+	 * @since 5.1.9
 	 */
 	public FileBasedConfig setUserGitConfig(FileBasedConfig userGitConfig) {
 		FileBasedConfig old = this.userGitConfig;
@@ -92,6 +93,7 @@ public FileBasedConfig setUserGitConfig(FileBasedConfig userGitConfig) {
 	 *
 	 * @param jgitConfig
 	 *            set the jgit configuration
+	 * @since 5.5
 	 */
 	public void setJGitConfig(FileBasedConfig jgitConfig) {
 		this.jgitConfig = jgitConfig;
@@ -103,6 +105,7 @@ public void setJGitConfig(FileBasedConfig jgitConfig) {
 	 * @param systemGitConfig
 	 *            the new system-level git config
 	 * @return the old system-level config
+	 * @since 5.1.9
 	 */
 	public FileBasedConfig setSystemGitConfig(FileBasedConfig systemGitConfig) {
 		FileBasedConfig old = this.systemGitConfig;
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java
index 3db3ba9..adcc10c 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java
@@ -60,6 +60,7 @@ public static class RepeatedTestException extends RuntimeException {
 		 *
 		 * @param message
 		 *            the error message
+		 * @since 5.1.9
 		 */
 		public RepeatedTestException(String message) {
 			super(message);
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java
index 5622108..04988f6 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java
@@ -340,6 +340,7 @@ public static String slashify(String str) {
 	 *         greater than then the lastmodification time of lastfile.
 	 * @throws InterruptedException
 	 * @throws IOException
+	 * @since 5.1.9
 	 */
 	public static Instant fsTick(File lastFile)
 			throws InterruptedException,
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/SeparateClassloaderTestRunner.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/SeparateClassloaderTestRunner.java
index 4a4dc92..c8c56b2 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/SeparateClassloaderTestRunner.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/SeparateClassloaderTestRunner.java
@@ -21,6 +21,8 @@
  * This class is used when it's required to load jgit classes in separate
  * classloader for each test class. It can be needed to isolate static field
  * initialization between separate tests.
+ *
+ * @since 5.5
  */
 public class SeparateClassloaderTestRunner extends BlockJUnit4ClassRunner {
 
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
index 0232156..54e4a09 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
@@ -339,6 +339,7 @@ public RevObject get(RevTree tree, String path)
 	 *            zero or more IDs of the commit's parents.
 	 * @return the ID of the new commit.
 	 * @throws Exception
+	 * @since 5.5
 	 */
 	public ObjectId unparsedCommit(ObjectId... parents) throws Exception {
 		return unparsedCommit(1, tree(), parents);
@@ -431,6 +432,7 @@ public RevCommit commit(final int secDelta, final RevTree tree,
 	 *            zero or more IDs of the commit's parents.
 	 * @return the ID of the new commit.
 	 * @throws Exception
+	 * @since 5.5
 	 */
 	public ObjectId unparsedCommit(final int secDelta, final RevTree tree,
 			final ObjectId... parents) throws Exception {
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/time/TimeUtil.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/time/TimeUtil.java
index 2ebb8e2..f93ac57 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/time/TimeUtil.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/time/TimeUtil.java
@@ -20,6 +20,8 @@
 
 /**
  * Utility methods for handling timestamps
+ *
+ * @since 5.1.9
  */
 public class TimeUtil {
 	/**
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target
index 737cada..6d96033 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.17" sequenceNumber="1637746569">
+<target name="jgit-4.17" sequenceNumber="1638648728">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="jakarta.servlet-api" version="4.0.0"/>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target
index 55cadffa..cbe26bb 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.18" sequenceNumber="1637746569">
+<target name="jgit-4.18" sequenceNumber="1638648728">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="jakarta.servlet-api" version="4.0.0"/>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.target
index 43fe4c8..ceff305 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.target
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.19-staging" sequenceNumber="1637746569">
+<target name="jgit-4.19-staging" sequenceNumber="1638648728">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="jakarta.servlet-api" version="4.0.0"/>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20.target
index aefa341..e2fed06 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20.target
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.20" sequenceNumber="1637746569">
+<target name="jgit-4.20" sequenceNumber="1638648728">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="jakarta.servlet-api" version="4.0.0"/>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.21.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.21.target
index 1b6cf26..4bd5546 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.21.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.21.target
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.21" sequenceNumber="1637746569">
+<target name="jgit-4.21" sequenceNumber="1638648728">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="jakarta.servlet-api" version="4.0.0"/>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.22.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.22.target
index 7fd0032..98f44a8 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.22.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.22.target
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.22" sequenceNumber="1637746993">
+<target name="jgit-4.22" sequenceNumber="1638648736">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="jakarta.servlet-api" version="4.0.0"/>
diff --git a/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF
index 3e0a4ea..3d6e909 100644
--- a/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF
@@ -12,6 +12,7 @@
  org.eclipse.jgit.api.errors;version="[6.1.0,6.2.0)",
  org.eclipse.jgit.diff;version="[6.1.0,6.2.0)",
  org.eclipse.jgit.dircache;version="[6.1.0,6.2.0)",
+ org.eclipse.jgit.internal.diffmergetool;version="6.1.0",
  org.eclipse.jgit.internal.storage.file;version="6.1.0",
  org.eclipse.jgit.junit;version="[6.1.0,6.2.0)",
  org.eclipse.jgit.lib;version="[6.1.0,6.2.0)",
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DiffToolTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DiffToolTest.java
index 2ce50c7..e7bf484 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DiffToolTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DiffToolTest.java
@@ -12,10 +12,12 @@
 import static org.junit.Assert.assertEquals;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.diff.DiffEntry;
+import org.eclipse.jgit.internal.diffmergetool.CommandLineDiffTool;
 import org.eclipse.jgit.lib.CLIRepositoryTestCase;
 import org.eclipse.jgit.pgm.opt.CmdLineParser;
 import org.eclipse.jgit.pgm.opt.SubcommandHandler;
@@ -135,16 +137,24 @@ expectedOutput, runAndCaptureUsingInitRaw("difftool",
 
 	@Test
 	public void testToolHelp() throws Exception {
-		String[] expectedOutput = {
-				"git difftool --tool=<tool> may be set to one of the following:",
+		CommandLineDiffTool[] defaultTools = CommandLineDiffTool.values();
+		List<String> expectedOutput = new ArrayList<>();
+		expectedOutput.add("git difftool --tool=<tool> may be set to one of the following:");
+		for (CommandLineDiffTool defaultTool : defaultTools) {
+			String toolName = defaultTool.name();
+			expectedOutput.add(toolName);
+		}
+		String[] userDefinedToolsHelp = {
 				"user-defined:",
 				"The following tools are valid, but not currently available:",
 				"Some of the tools listed above only work in a windowed",
-				"environment. If run in a terminal-only session, they will fail.", };
+				"environment. If run in a terminal-only session, they will fail.",
+		};
+		expectedOutput.addAll(Arrays.asList(userDefinedToolsHelp));
 
 		String option = "--tool-help";
 		assertArrayOfLinesEquals("Incorrect output for option: " + option,
-				expectedOutput, runAndCaptureUsingInitRaw("difftool", option));
+				expectedOutput.toArray(new String[0]), runAndCaptureUsingInitRaw("difftool", option));
 	}
 
 	private RevCommit createUnstagedChanges() throws Exception {
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTool.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTool.java
index 9fc26c9..d26842c 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTool.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTool.java
@@ -199,20 +199,20 @@ private boolean isLaunchCompare(int fileIndex, int fileCount,
 	}
 
 	private void showToolHelp() throws IOException {
-		String availableToolNames = new String();
+		StringBuilder availableToolNames = new StringBuilder();
 		for (String name : diffTools.getAvailableTools().keySet()) {
-			availableToolNames += String.format("\t\t{0}\n", name); //$NON-NLS-1$
+			availableToolNames.append(String.format("\t\t%s\n", name)); //$NON-NLS-1$
 		}
-		String notAvailableToolNames = new String();
+		StringBuilder notAvailableToolNames = new StringBuilder();
 		for (String name : diffTools.getNotAvailableTools().keySet()) {
-			notAvailableToolNames += String.format("\t\t{0}\n", name); //$NON-NLS-1$
+			notAvailableToolNames.append(String.format("\t\t%s\n", name)); //$NON-NLS-1$
 		}
-		String userToolNames = new String();
+		StringBuilder userToolNames = new StringBuilder();
 		Map<String, ExternalDiffTool> userTools = diffTools
 				.getUserDefinedTools();
 		for (String name : userTools.keySet()) {
-			availableToolNames += String.format("\t\t{0}.cmd {1}\n", //$NON-NLS-1$
-					name, userTools.get(name).getCommand());
+			userToolNames.append(String.format("\t\t%s.cmd %s\n", //$NON-NLS-1$
+					name, userTools.get(name).getCommand()));
 		}
 		outw.println(MessageFormat.format(
 				CLIText.get().diffToolHelpSetToFollowing, availableToolNames,
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalDiffToolTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalDiffToolTest.java
index f07d9d1..b141a86 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalDiffToolTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalDiffToolTest.java
@@ -9,9 +9,18 @@
  */
 package org.eclipse.jgit.internal.diffmergetool;
 
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DIFFTOOL_SECTION;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_CMD;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_GUITOOL;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PATH;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PROMPT;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_TRUST_EXIT_CODE;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 
 import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Map;
 import java.util.Set;
 
 import org.eclipse.jgit.lib.internal.BooleanTriState;
@@ -29,27 +38,64 @@ public void testToolNames() {
 		Set<String> actualToolNames = manager.getToolNames();
 		Set<String> expectedToolNames = Collections.emptySet();
 		assertEquals("Incorrect set of external diff tool names",
-				expectedToolNames,
-				actualToolNames);
+				expectedToolNames, actualToolNames);
 	}
 
 	@Test
 	public void testAllTools() {
 		DiffTools manager = new DiffTools(db);
 		Set<String> actualToolNames = manager.getAvailableTools().keySet();
-		Set<String> expectedToolNames = Collections.emptySet();
-		assertEquals("Incorrect set of available external diff tools",
-				expectedToolNames,
+		Set<String> expectedToolNames = new LinkedHashSet<>();
+		CommandLineDiffTool[] defaultTools = CommandLineDiffTool.values();
+		for (CommandLineDiffTool defaultTool : defaultTools) {
+			String toolName = defaultTool.name();
+			expectedToolNames.add(toolName);
+		}
+		assertEquals("Incorrect set of external diff tools", expectedToolNames,
 				actualToolNames);
 	}
 
 	@Test
+	public void testOverridePredefinedToolPath() {
+		String toolName = CommandLineDiffTool.guiffy.name();
+		String customToolPath = "/usr/bin/echo";
+
+		FileBasedConfig config = db.getConfig();
+		config.setString(CONFIG_DIFFTOOL_SECTION, toolName, CONFIG_KEY_CMD,
+				"echo");
+		config.setString(CONFIG_DIFFTOOL_SECTION, toolName, CONFIG_KEY_PATH,
+				customToolPath);
+
+		DiffTools manager = new DiffTools(db);
+		Map<String, ExternalDiffTool> tools = manager.getUserDefinedTools();
+		ExternalDiffTool diffTool = tools.get(toolName);
+		assertNotNull("Expected tool \"" + toolName + "\" to be user defined",
+				diffTool);
+
+		String toolPath = diffTool.getPath();
+		assertEquals("Expected external diff tool to have an overriden path",
+				customToolPath, toolPath);
+	}
+
+	@Test
 	public void testUserDefinedTools() {
+		FileBasedConfig config = db.getConfig();
+		String customToolname = "customTool";
+		config.setString(CONFIG_DIFFTOOL_SECTION, customToolname,
+				CONFIG_KEY_CMD, "echo");
+		config.setString(CONFIG_DIFFTOOL_SECTION, customToolname,
+				CONFIG_KEY_PATH, "/usr/bin/echo");
+		config.setString(CONFIG_DIFFTOOL_SECTION, customToolname,
+				CONFIG_KEY_PROMPT, "--no-prompt");
+		config.setString(CONFIG_DIFFTOOL_SECTION, customToolname,
+				CONFIG_KEY_GUITOOL, "--no-gui");
+		config.setString(CONFIG_DIFFTOOL_SECTION, customToolname,
+				CONFIG_KEY_TRUST_EXIT_CODE, "--no-trust-exit-code");
 		DiffTools manager = new DiffTools(db);
 		Set<String> actualToolNames = manager.getUserDefinedTools().keySet();
-		Set<String> expectedToolNames = Collections.emptySet();
-		assertEquals("Incorrect set of user defined external diff tools",
-				expectedToolNames,
+		Set<String> expectedToolNames = new LinkedHashSet<>();
+		expectedToolNames.add(customToolname);
+		assertEquals("Incorrect set of external diff tools", expectedToolNames,
 				actualToolNames);
 	}
 
@@ -59,8 +105,7 @@ public void testNotAvailableTools() {
 		Set<String> actualToolNames = manager.getNotAvailableTools().keySet();
 		Set<String> expectedToolNames = Collections.emptySet();
 		assertEquals("Incorrect set of not available external diff tools",
-				expectedToolNames,
-				actualToolNames);
+				expectedToolNames, actualToolNames);
 	}
 
 	@Test
@@ -80,8 +125,7 @@ public void testCompare() {
 		int compareResult = manager.compare(newPath, oldPath, newId, oldId,
 				toolName, prompt, gui, trustExitCode);
 		assertEquals("Incorrect compare result for external diff tool",
-				expectedCompareResult,
-				compareResult);
+				expectedCompareResult, compareResult);
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/revwalk/BitmappedObjectReachabilityTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/revwalk/BitmappedObjectReachabilityTest.java
index f2f7405..d574428 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/revwalk/BitmappedObjectReachabilityTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/revwalk/BitmappedObjectReachabilityTest.java
@@ -23,7 +23,7 @@ ObjectReachabilityChecker getChecker(
 		// GC generates the bitmaps
 		GC gc = new GC(repository.getRepository());
 		gc.setAuto(false);
-		gc.gc();
+		gc.gc().get();
 
 		return new BitmappedObjectReachabilityChecker(
 				repository.getRevWalk().toObjectWalkWithSameObjects());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/revwalk/BitmappedReachabilityCheckerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/revwalk/BitmappedReachabilityCheckerTest.java
index 5833c7a..253246e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/revwalk/BitmappedReachabilityCheckerTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/revwalk/BitmappedReachabilityCheckerTest.java
@@ -25,7 +25,7 @@ protected ReachabilityChecker getChecker(
 		// GC generates the bitmaps
 		GC gc = new GC(repo.getRepository());
 		gc.setAuto(false);
-		gc.gc();
+		gc.gc().get();
 
 		// This is null when the test didn't create any branch
 		assertNotNull("Probably the test didn't define any ref",
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java
index 8dc1ddb..6cad8b6 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java
@@ -54,7 +54,7 @@ public void testPackRepoWithNoRefs(boolean aggressive) throws Exception {
 		assertEquals(4, stats.numberOfLooseObjects);
 		assertEquals(0, stats.numberOfPackedObjects);
 		configureGc(gc, aggressive);
-		gc.gc();
+		gc.gc().get();
 		stats = gc.getStatistics();
 		assertEquals(4, stats.numberOfLooseObjects);
 		assertEquals(0, stats.numberOfPackedObjects);
@@ -72,7 +72,7 @@ public void testPack2Commits(boolean aggressive) throws Exception {
 		assertEquals(8, stats.numberOfLooseObjects);
 		assertEquals(0, stats.numberOfPackedObjects);
 		configureGc(gc, aggressive);
-		gc.gc();
+		gc.gc().get();
 		stats = gc.getStatistics();
 		assertEquals(0, stats.numberOfLooseObjects);
 		assertEquals(8, stats.numberOfPackedObjects);
@@ -93,7 +93,7 @@ public void testPack2Commits_noPackFolder(boolean aggressive) throws Exception {
 		assertEquals(8, stats.numberOfLooseObjects);
 		assertEquals(0, stats.numberOfPackedObjects);
 		configureGc(gc, aggressive);
-		gc.gc();
+		gc.gc().get();
 		stats = gc.getStatistics();
 		assertEquals(0, stats.numberOfLooseObjects);
 		assertEquals(8, stats.numberOfPackedObjects);
@@ -112,7 +112,7 @@ public void testPackAllObjectsInOnePack(boolean aggressive)
 		assertEquals(4, stats.numberOfLooseObjects);
 		assertEquals(0, stats.numberOfPackedObjects);
 		configureGc(gc, aggressive);
-		gc.gc();
+		gc.gc().get();
 		stats = gc.getStatistics();
 		assertEquals(0, stats.numberOfLooseObjects);
 		assertEquals(4, stats.numberOfPackedObjects);
@@ -120,7 +120,7 @@ public void testPackAllObjectsInOnePack(boolean aggressive)
 		assertEquals(1, stats.numberOfBitmaps);
 
 		// Do the gc again and check that it hasn't changed anything
-		gc.gc();
+		gc.gc().get();
 		stats = gc.getStatistics();
 		assertEquals(0, stats.numberOfLooseObjects);
 		assertEquals(4, stats.numberOfPackedObjects);
@@ -140,7 +140,7 @@ public void testPackCommitsAndLooseOne(boolean aggressive)
 		assertEquals(8, stats.numberOfLooseObjects);
 		assertEquals(0, stats.numberOfPackedObjects);
 		configureGc(gc, aggressive);
-		gc.gc();
+		gc.gc().get();
 		stats = gc.getStatistics();
 		assertEquals(0, stats.numberOfLooseObjects);
 		assertEquals(8, stats.numberOfPackedObjects);
@@ -168,7 +168,7 @@ public void testNotPackTwice(boolean aggressive) throws Exception {
 		gc.setExpireAgeMillis(0);
 		fsTick();
 		configureGc(gc, aggressive);
-		gc.gc();
+		gc.gc().get();
 		stats = gc.getStatistics();
 		assertEquals(0, stats.numberOfLooseObjects);
 
@@ -187,7 +187,7 @@ public void testDonePruneTooYoungPacks() throws Exception {
 		bb2.commit().message("M").add("M", "M").create();
 
 		gc.setExpireAgeMillis(0);
-		gc.gc();
+		gc.gc().get();
 		stats = gc.getStatistics();
 		assertEquals(0, stats.numberOfLooseObjects);
 		assertEquals(4, stats.numberOfPackedObjects);
@@ -207,7 +207,7 @@ public void testDonePruneTooYoungPacks() throws Exception {
 		// The old packfile is too young to be deleted. We should end up with
 		// two pack files
 		gc.setExpire(new Date(oldPackfile.lastModified() - 1));
-		gc.gc();
+		gc.gc().get();
 		stats = gc.getStatistics();
 		assertEquals(0, stats.numberOfLooseObjects);
 		// if objects exist in multiple packFiles then they are counted multiple
@@ -218,7 +218,7 @@ public void testDonePruneTooYoungPacks() throws Exception {
 		// repack again but now without a grace period for loose objects. Since
 		// we don't have loose objects anymore this shouldn't change anything
 		gc.setExpireAgeMillis(0);
-		gc.gc();
+		gc.gc().get();
 		stats = gc.getStatistics();
 		assertEquals(0, stats.numberOfLooseObjects);
 		// if objects exist in multiple packFiles then they are counted multiple
@@ -233,7 +233,7 @@ public void testDonePruneTooYoungPacks() throws Exception {
 		// we want to keep newly-loosened objects though
 		gc.setExpireAgeMillis(-1);
 
-		gc.gc();
+		gc.gc().get();
 		stats = gc.getStatistics();
 		assertEquals(1, stats.numberOfLooseObjects);
 		// if objects exist in multiple packFiles then they are counted multiple
@@ -252,7 +252,7 @@ public void testImmediatePruning() throws Exception {
 		bb2.commit().message("M").add("M", "M").create();
 
 		gc.setExpireAgeMillis(0);
-		gc.gc();
+		gc.gc().get();
 		stats = gc.getStatistics();
 
 		fsTick();
@@ -273,7 +273,7 @@ public void testImmediatePruning() throws Exception {
 		//And we don't want to keep packs full of dead objects
 		gc.setPackExpireAgeMillis(0);
 
-		gc.gc();
+		gc.gc().get();
 		stats = gc.getStatistics();
 		assertEquals(0, stats.numberOfLooseObjects);
 		assertEquals(6, stats.numberOfPackedObjects);
@@ -284,7 +284,7 @@ public void testImmediatePruning() throws Exception {
 	public void testPreserveAndPruneOldPacks() throws Exception {
 		testPreserveOldPacks();
 		configureGc(gc, false).setPrunePreserved(true);
-		gc.gc();
+		gc.gc().get();
 
 		assertFalse(repo.getObjectDatabase().getPreservedDirectory().exists());
 	}
@@ -295,7 +295,7 @@ private void testPreserveOldPacks() throws Exception {
 
 		// pack loose object into packfile
 		gc.setExpireAgeMillis(0);
-		gc.gc();
+		gc.gc().get();
 		PackFile oldPackfile = tr.getRepository().getObjectDatabase().getPacks()
 				.iterator().next().getPackFile();
 		assertTrue(oldPackfile.exists());
@@ -308,7 +308,7 @@ private void testPreserveOldPacks() throws Exception {
 		// preserved directory
 		gc.setPackExpireAgeMillis(0);
 		configureGc(gc, false).setPreserveOldPacks(true);
-		gc.gc();
+		gc.gc().get();
 
 		File preservedPackFile = oldPackfile.createPreservedForDirectory(
 				repo.getObjectDatabase().getPreservedDirectory());
@@ -330,7 +330,7 @@ public void testPruneAndRestoreOldPacks() throws Exception {
 		configureGc(gc, false);
 		gc.setExpireAgeMillis(0);
 		gc.setPackExpireAgeMillis(0);
-		gc.gc();
+		gc.gc().get();
 		stats = gc.getStatistics();
 		assertEquals(0, stats.numberOfLooseObjects);
 		assertEquals(4, stats.numberOfPackedObjects);
@@ -347,7 +347,7 @@ public void testPruneAndRestoreOldPacks() throws Exception {
 
 		// Repack with only orphaned commit, so packfile will be pruned
 		configureGc(gc, false).setPreserveOldPacks(true);
-		gc.gc();
+		gc.gc().get();
 		stats = gc.getStatistics();
 		assertEquals(0, stats.numberOfLooseObjects);
 		assertEquals(0, stats.numberOfPackedObjects);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java
index 5cac1e3..2c5f1a8 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java
@@ -110,7 +110,7 @@ public void repackAndGetStats() throws Exception {
 		test.commit().add("a", "a").create();
 		GC gc1 = new GC(tr.getRepository());
 		gc1.setPackExpireAgeMillis(0);
-		gc1.gc();
+		gc1.gc().get();
 		test.commit().add("b", "b").create();
 
 		// Create a new Repository instance and trigger a gc
@@ -120,7 +120,7 @@ public void repackAndGetStats() throws Exception {
 				tr.getRepository().getDirectory());
 		GC gc2 = new GC(r2);
 		gc2.setPackExpireAgeMillis(0);
-		gc2.gc();
+		gc2.gc().get();
 
 		new GC(tr.getRepository()).getStatistics();
 	}
@@ -133,7 +133,7 @@ public void repackAndUploadPack() throws Exception {
 
 		GC gc1 = new GC(tr.getRepository());
 		gc1.setPackExpireAgeMillis(0);
-		gc1.gc();
+		gc1.gc().get();
 
 		RevCommit b = test.commit().add("b", "b").create();
 
@@ -141,7 +141,7 @@ public void repackAndUploadPack() throws Exception {
 				tr.getRepository().getDirectory());
 		GC gc2 = new GC(r2);
 		gc2.setPackExpireAgeMillis(0);
-		gc2.gc();
+		gc2.gc().get();
 
 		// Simulate parts of an UploadPack. This is the situation on
 		// server side (e.g. gerrit) when clients are
@@ -172,7 +172,7 @@ public void repackAndCheckBitmapUsage() throws Exception {
 		FileRepository repository = tr.getRepository();
 		GC gc1 = new GC(repository);
 		gc1.setPackExpireAgeMillis(0);
-		gc1.gc();
+		gc1.gc().get();
 		String oldPackName = getSinglePack(repository).getPackName();
 		RevCommit b = test.commit().add("b", "b").create();
 
@@ -180,7 +180,7 @@ public void repackAndCheckBitmapUsage() throws Exception {
 		FileRepository repository2 = new FileRepository(repository.getDirectory());
 		GC gc2 = new GC(repository2);
 		gc2.setPackExpireAgeMillis(0);
-		gc2.gc();
+		gc2.gc().get();
 		String newPackName = getSinglePack(repository2).getPackName();
 		// make sure gc() has caused creation of a new packfile
 		assertNotEquals(oldPackName, newPackName);
@@ -210,7 +210,7 @@ public void testInterruptGc() throws Exception {
 			long start = System.currentTimeMillis();
 			System.out.println("starting gc");
 			latch.countDown();
-			Collection<Pack> r = gc.gc();
+			Collection<Pack> r = gc.gc().get();
 			System.out.println(
 					"gc took " + (System.currentTimeMillis() - start) + " ms");
 			return r;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcDeleteEmptyRefsFoldersTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcDeleteEmptyRefsFoldersTest.java
index 564f8ab..39aafc8 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcDeleteEmptyRefsFoldersTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcDeleteEmptyRefsFoldersTest.java
@@ -48,7 +48,7 @@ public void emptyRefFoldersAreDeleted() throws Exception {
 		setLastModifiedTime(fileTime, heads, REF_FOLDER_02);
 		assertTrue(refDir01.toFile().exists());
 		assertTrue(refDir02.toFile().exists());
-		gc.gc();
+		gc.gc().get();
 
 		assertFalse(refDir01.toFile().exists());
 		assertFalse(refDir01.getParent().toFile().exists());
@@ -68,7 +68,7 @@ public void emptyRefFoldersSkipFiles() throws Exception {
 		setLastModifiedTime(fileTime, heads, REF_FOLDER_02);
 		assertTrue(refDir01.toFile().exists());
 		assertTrue(refDir02.toFile().exists());
-		gc.gc();
+		gc.gc().get();
 		assertTrue(Files.exists(refFile));
 	}
 
@@ -88,7 +88,7 @@ public void emptyRefFoldersAreKeptIfTheyAreTooRecent()
 		Path refDir02 = Files.createDirectories(heads.resolve(REF_FOLDER_02));
 		assertTrue(refDir01.toFile().exists());
 		assertTrue(refDir02.toFile().exists());
-		gc.gc();
+		gc.gc().get();
 
 		assertTrue(refDir01.toFile().exists());
 		assertTrue(refDir02.toFile().exists());
@@ -104,7 +104,7 @@ public void nonEmptyRefsFoldersAreKept() throws Exception {
 		assertTrue(refDir02.toFile().exists());
 		assertTrue(ref01.toFile().exists());
 		assertTrue(ref02.toFile().exists());
-		gc.gc();
+		gc.gc().get();
 		assertTrue(refDir01.toFile().exists());
 		assertTrue(refDir02.toFile().exists());
 		assertTrue(ref01.toFile().exists());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcDirCacheSavesObjectsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcDirCacheSavesObjectsTest.java
index a4dff26..a51b0c2 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcDirCacheSavesObjectsTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcDirCacheSavesObjectsTest.java
@@ -25,7 +25,7 @@ public void testDirCacheSavesObjects() throws Exception {
 		stats = gc.getStatistics();
 		assertEquals(9, stats.numberOfLooseObjects);
 		assertEquals(0, stats.numberOfPackedObjects);
-		gc.gc();
+		gc.gc().get();
 		stats = gc.getStatistics();
 		assertEquals(1, stats.numberOfLooseObjects);
 		assertEquals(8, stats.numberOfPackedObjects);
@@ -43,7 +43,7 @@ public void testDirCacheSavesObjectsWithPruneNow() throws Exception {
 		assertEquals(0, stats.numberOfPackedObjects);
 		gc.setExpireAgeMillis(0);
 		fsTick();
-		gc.gc();
+		gc.gc().get();
 		stats = gc.getStatistics();
 		assertEquals(0, stats.numberOfLooseObjects);
 		assertEquals(8, stats.numberOfPackedObjects);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcKeepFilesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcKeepFilesTest.java
index 5fcdd37..840c098 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcKeepFilesTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcKeepFilesTest.java
@@ -30,7 +30,7 @@ public void testKeepFiles() throws Exception {
 		assertEquals(4, stats.numberOfLooseObjects);
 		assertEquals(0, stats.numberOfPackedObjects);
 		assertEquals(0, stats.numberOfPackFiles);
-		gc.gc();
+		gc.gc().get();
 		stats = gc.getStatistics();
 		assertEquals(0, stats.numberOfLooseObjects);
 		assertEquals(4, stats.numberOfPackedObjects);
@@ -48,7 +48,7 @@ public void testKeepFiles() throws Exception {
 		assertEquals(4, stats.numberOfLooseObjects);
 		assertEquals(4, stats.numberOfPackedObjects);
 		assertEquals(1, stats.numberOfPackFiles);
-		gc.gc();
+		gc.gc().get();
 		stats = gc.getStatistics();
 		assertEquals(0, stats.numberOfLooseObjects);
 		assertEquals(8, stats.numberOfPackedObjects);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcOrphanFilesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcOrphanFilesTest.java
index c5c316d..620aedf 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcOrphanFilesTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcOrphanFilesTest.java
@@ -50,7 +50,7 @@ public void bitmapAndIdxDeletedButPackNot() throws Exception {
 		createFileInPackFolder(BITMAP_File_1);
 		createFileInPackFolder(IDX_File_2);
 		createFileInPackFolder(PACK_File_3);
-		gc.gc();
+		gc.gc().get();
 		assertFalse(new File(packDir, BITMAP_File_1).exists());
 		assertFalse(new File(packDir, IDX_File_2).exists());
 		assertTrue(new File(packDir, PACK_File_3).exists());
@@ -62,7 +62,7 @@ public void bitmapDeletedButIdxAndPackNot() throws Exception {
 		createFileInPackFolder(IDX_File_2);
 		createFileInPackFolder(PACK_File_2);
 		createFileInPackFolder(PACK_File_3);
-		gc.gc();
+		gc.gc().get();
 		assertFalse(new File(packDir, BITMAP_File_1).exists());
 		assertTrue(new File(packDir, IDX_File_2).exists());
 		assertTrue(new File(packDir, PACK_File_2).exists());
@@ -72,7 +72,7 @@ public void bitmapDeletedButIdxAndPackNot() throws Exception {
 	@Test
 	public void malformedIdxNotDeleted() throws Exception {
 		createFileInPackFolder(IDX_File_malformed);
-		gc.gc();
+		gc.gc().get();
 		assertTrue(new File(packDir, IDX_File_malformed).exists());
 	}
 
@@ -84,7 +84,7 @@ public void keepPreventsDeletionOfIndexFilesForMissingPackFile()
 		createFileInPackFolder(BITMAP_File_2);
 		createFileInPackFolder(KEEP_File_2);
 		createFileInPackFolder(PACK_File_3);
-		gc.gc();
+		gc.gc().get();
 		assertFalse(new File(packDir, BITMAP_File_1).exists());
 		assertTrue(new File(packDir, BITMAP_File_2).exists());
 		assertTrue(new File(packDir, IDX_File_2).exists());
@@ -102,6 +102,6 @@ private void createFileInPackFolder(String fileName) throws IOException {
 	@Test
 	public void noSuchPackFolder() throws Exception {
 		assertTrue(packDir.delete());
-		gc.gc();
+		gc.gc().get();
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPruneNonReferencedTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPruneNonReferencedTest.java
index 7386621..ca0f684 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPruneNonReferencedTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPruneNonReferencedTest.java
@@ -80,7 +80,7 @@ public void testPackCommitsAndLooseOneWithPruneNow() throws Exception {
 		assertEquals(0, stats.numberOfPackedObjects);
 		gc.setExpireAgeMillis(0);
 		fsTick();
-		gc.gc();
+		gc.gc().get();
 		stats = gc.getStatistics();
 		assertNoEmptyFanoutDirectories();
 		assertEquals(0, stats.numberOfLooseObjects);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java
index 0901d86..e6c1ee5 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java
@@ -59,7 +59,7 @@ public void testPackRepoWithCorruptReflog() throws Exception {
 				.create();
 		// make sure HEAD exists
 		Git.wrap(repo).checkout().setName("refs/heads/master").call();
-		gc.gc();
+		gc.gc().get();
 	}
 
 	@Test
@@ -78,7 +78,7 @@ public void testPackCommitsAndLooseOneNoReflog() throws Exception {
 		FileUtils.delete(
 				new File(repo.getDirectory(), "logs/refs/heads/master"),
 				FileUtils.RETRY | FileUtils.SKIP_MISSING);
-		gc.gc();
+		gc.gc().get();
 
 		stats = gc.getStatistics();
 		assertEquals(4, stats.numberOfLooseObjects);
@@ -104,7 +104,7 @@ public void testPackCommitsAndLooseOneWithPruneNowNoReflog()
 				new File(repo.getDirectory(), "logs/refs/heads/master"),
 				FileUtils.RETRY | FileUtils.SKIP_MISSING);
 		gc.setExpireAgeMillis(0);
-		gc.gc();
+		gc.gc().get();
 
 		stats = gc.getStatistics();
 		assertEquals(0, stats.numberOfLooseObjects);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTemporaryFilesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTemporaryFilesTest.java
index 16bde19..1a7dd5e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTemporaryFilesTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTemporaryFilesTest.java
@@ -50,7 +50,7 @@ public void oldTempPacksAndIdxAreDeleted() throws Exception {
 				- 24 * 60 * 62 * 1000;
 		tempIndex.setLastModified(_24HoursBefore);
 		tempPack.setLastModified(_24HoursBefore);
-		gc.gc();
+		gc.gc().get();
 		assertFalse(tempIndex.exists());
 		assertFalse(tempPack.exists());
 	}
@@ -66,7 +66,7 @@ public void recentTempPacksAndIdxAreNotDeleted() throws Exception {
 		assertTrue(tempIndex.createNewFile());
 		assertTrue(tempIndex.exists());
 		assertTrue(tempPack.exists());
-		gc.gc();
+		gc.gc().get();
 		assertTrue(tempIndex.exists());
 		assertTrue(tempPack.exists());
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java
index 316e336..1a3b378 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java
@@ -126,7 +126,7 @@ public void testScanningForPackfiles() throws Exception {
 			// setup a repo which has at least one pack file and trigger
 			// scanning of the packs directory
 			ObjectId id = commitFile("file.txt", "test", "master").getId();
-			gc.gc();
+			gc.gc().get();
 			assertFalse(receivingDB.getObjectDatabase().has(unknownID));
 			assertTrue(receivingDB.getObjectDatabase().hasPackedObject(id));
 
@@ -155,7 +155,7 @@ public void testScanningForPackfiles() throws Exception {
 
 			// trigger a gc. This will create packfiles which have likely the
 			// same mtime than the packfolder
-			gc.gc();
+			gc.gc().get();
 
 			// To deal with racy-git situations JGit's Filesnapshot class will
 			// report a file/folder potentially dirty if
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileSnapshotTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileSnapshotTest.java
index 7c32ce7..3de015b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileSnapshotTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileSnapshotTest.java
@@ -25,8 +25,6 @@
 import java.nio.file.Paths;
 import java.nio.file.StandardCopyOption;
 import java.nio.file.StandardOpenOption;
-//import java.nio.file.attribute.BasicFileAttributes;
-import java.text.ParseException;
 import java.time.Instant;
 import java.util.Collection;
 import java.util.Iterator;
@@ -281,8 +279,7 @@ private Path copyPack(Path base, String srcSuffix, String dstSuffix)
 	}
 
 	private Pack repackAndCheck(int compressionLevel, String oldName,
-			Long oldLength, AnyObjectId oldChkSum)
-			throws IOException, ParseException {
+			Long oldLength, AnyObjectId oldChkSum) throws Exception {
 		Pack p = getSinglePack(gc(compressionLevel));
 		File pf = p.getPackFile();
 		// The following two assumptions should not cause the test to fail. If
@@ -305,8 +302,7 @@ private Pack getSinglePack(Collection<Pack> packs) {
 		return p;
 	}
 
-	private Collection<Pack> gc(int compressionLevel)
-			throws IOException, ParseException {
+	private Collection<Pack> gc(int compressionLevel) throws Exception {
 		GC gc = new GC(db);
 		PackConfig pc = new PackConfig(db.getConfig());
 		pc.setCompressionLevel(compressionLevel);
@@ -322,7 +318,7 @@ private Collection<Pack> gc(int compressionLevel)
 		gc.setPackConfig(pc);
 		gc.setExpireAgeMillis(0);
 		gc.setPackExpireAgeMillis(0);
-		return gc.gc();
+		return gc.gc().get();
 	}
 
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java
index 71aca9d..1ff2264 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java
@@ -28,7 +28,6 @@
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.text.ParseException;
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -232,15 +231,13 @@ public void testIgnoreNonExistingObjects() throws IOException {
 	 * Use a repo with bitmap indexes because then PackWriter will use
 	 * PackWriterBitmapWalker which had problems with this situation.
 	 *
-	 * @throws IOException
-	 * @throws ParseException
+	 * @throws Exception
 	 */
 	@Test
-	public void testIgnoreNonExistingObjectsWithBitmaps() throws IOException,
-			ParseException {
+	public void testIgnoreNonExistingObjectsWithBitmaps() throws Exception {
 		final ObjectId nonExisting = ObjectId
 				.fromString("0000000000000000000000000000000000000001");
-		new GC(db).gc();
+		new GC(db).gc().get();
 		createVerifyOpenPack(NONE, haves(nonExisting), false, true, true);
 		// shouldn't throw anything
 	}
@@ -732,11 +729,11 @@ private FileRepository setUpRepoWithMultiplePackfiles() throws Exception {
 			gc.setPackExpireAgeMillis(Long.MAX_VALUE);
 			gc.setExpireAgeMillis(Long.MAX_VALUE);
 			// Creates packfile P1 (containing C1, T1)
-			gc.gc();
+			gc.gc().get();
 			// Creates 1 object (C2 commit)
 			git.commit().setMessage("Second commit").call();
 			// Creates packfile P2 (containing C1, T1, C2)
-			gc.gc();
+			gc.gc().get();
 			// Create 1 object (C3 commit)
 			git.commit().setMessage("Third commit").call();
 		}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/GcCommitSelectionTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/GcCommitSelectionTest.java
index cc826c3..bb56c84 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/GcCommitSelectionTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/GcCommitSelectionTest.java
@@ -74,7 +74,7 @@ private void testBitmapSpansNoMerges(boolean withTags) throws Exception {
 
 			gc.setPackExpireAgeMillis(0); // immediately delete old packs
 			gc.setExpireAgeMillis(0);
-			gc.gc();
+			gc.gc().get();
 			assertEquals(currentCommits * 3, // commit/tree/object
 					gc.getStatistics().numberOfPackedObjects);
 			assertEquals(currentCommits + " commits: ", expectedBitmapCount,
@@ -138,7 +138,7 @@ public void testBitmapSpansWithMerges() throws Exception {
 
 			gc.setPackExpireAgeMillis(0); // immediately delete old packs
 			gc.setExpireAgeMillis(0);
-			gc.gc();
+			gc.gc().get();
 			assertEquals(currentCommits + " commits: ", expectedBitmapCount,
 					gc.getStatistics().numberOfBitmaps);
 		}
@@ -179,7 +179,7 @@ public void testBitmapsForExcessiveBranches() throws Exception {
 		// Excessive branch history pruning, one old branch.
 		gc.setPackExpireAgeMillis(0); // immediately delete old packs
 		gc.setExpireAgeMillis(0);
-		gc.gc();
+		gc.gc().get();
 		assertEquals(
 				commitsForSparseBranch + commitsForFullBranch
 						+ commitsForShallowBranches,
diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF
index 90e5ccb..91e905b 100644
--- a/org.eclipse.jgit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit/META-INF/MANIFEST.MF
@@ -72,6 +72,7 @@
    org.eclipse.jgit.http.test",
  org.eclipse.jgit.internal.diffmergetool;version="6.1.0";
   x-friends:="org.eclipse.jgit.test,
+   org.eclipse.jgit.pgm.test,
    org.eclipse.jgit.pgm",
  org.eclipse.jgit.internal.fsck;version="6.1.0";
   x-friends:="org.eclipse.jgit.test",
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java
index b5fff7d..584d2bc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java
@@ -14,6 +14,7 @@
 import java.text.ParseException;
 import java.util.Date;
 import java.util.Properties;
+import java.util.concurrent.ExecutionException;
 
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.api.errors.JGitInternalException;
@@ -176,9 +177,10 @@ public Properties call() throws GitAPIException {
 					gc.setExpire(expire);
 
 				try {
-					gc.gc();
+					gc.gc().get();
 					return toProperties(gc.getStatistics());
-				} catch (ParseException e) {
+				} catch (ParseException | InterruptedException
+						| ExecutionException e) {
 					throw new JGitInternalException(JGitText.get().gcFailed, e);
 				}
 			} else if (repo instanceof DfsRepository) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandLineDiffTool.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandLineDiffTool.java
new file mode 100644
index 0000000..509515c
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandLineDiffTool.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2018-2021, Andre Bossert <andre.bossert@siemens.com>
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.diffmergetool;
+
+/**
+ * Pre-defined command line diff tools.
+ *
+ * Adds same diff tools as also pre-defined in C-Git
+ * <p>
+ * see "git-core\mergetools\"
+ * </p>
+ * <p>
+ * see links to command line parameter description for the tools
+ * </p>
+ *
+ * <pre>
+ * araxis
+ * bc
+ * bc3
+ * codecompare
+ * deltawalker
+ * diffmerge
+ * diffuse
+ * ecmerge
+ * emerge
+ * examdiff
+ * guiffy
+ * gvimdiff
+ * gvimdiff2
+ * gvimdiff3
+ * kdiff3
+ * kompare
+ * meld
+ * opendiff
+ * p4merge
+ * tkdiff
+ * vimdiff
+ * vimdiff2
+ * vimdiff3
+ * winmerge
+ * xxdiff
+ * </pre>
+ *
+ */
+@SuppressWarnings("nls")
+public enum CommandLineDiffTool {
+	/**
+	 * See: <a href=
+	 * "https://www.araxis.com/merge/documentation-windows/command-line.en">https://www.araxis.com/merge/documentation-windows/command-line.en</a>
+	 */
+	araxis("compare", "-wait -2 \"$LOCAL\" \"$REMOTE\""),
+	/**
+	 * See: <a href=
+	 * "https://www.scootersoftware.com/v4help/index.html?command_line_reference.html">https://www.scootersoftware.com/v4help/index.html?command_line_reference.html</a>
+	 */
+	bc("bcomp", "\"$LOCAL\" \"$REMOTE\""),
+	/**
+	 * See: <a href=
+	 * "https://www.scootersoftware.com/v4help/index.html?command_line_reference.html">https://www.scootersoftware.com/v4help/index.html?command_line_reference.html</a>
+	 */
+	bc3("bcompare", bc),
+	/**
+	 * See: <a href=
+	 * "https://www.devart.com/codecompare/docs/index.html?comparing_via_command_line.htm">https://www.devart.com/codecompare/docs/index.html?comparing_via_command_line.htm</a>
+	 */
+	codecompare("CodeCompare", "\"$LOCAL\" \"$REMOTE\""),
+	/**
+	 * See: <a href=
+	 * "https://www.deltawalker.com/integrate/command-line">https://www.deltawalker.com/integrate/command-line</a>
+	 */
+	deltawalker("DeltaWalker", "\"$LOCAL\" \"$REMOTE\""),
+	/**
+	 * See: <a href=
+	 * "https://sourcegear.com/diffmerge/webhelp/sec__clargs__diff.html">https://sourcegear.com/diffmerge/webhelp/sec__clargs__diff.html</a>
+	 */
+	diffmerge("diffmerge", "\"$LOCAL\" \"$REMOTE\""),
+	/**
+	 * See: <a href=
+	 * "http://diffuse.sourceforge.net/manual.html#introduction-usage">http://diffuse.sourceforge.net/manual.html#introduction-usage</a>
+	 */
+	diffuse("diffuse", "\"$LOCAL\" \"$REMOTE\""),
+	/**
+	 * See: <a href=
+	 * "http://www.elliecomputing.com/en/OnlineDoc/ecmerge_en/44205167.asp">http://www.elliecomputing.com/en/OnlineDoc/ecmerge_en/44205167.asp</a>
+	 */
+	ecmerge("ecmerge", "--default --mode=diff2 \"$LOCAL\" \"$REMOTE\""),
+	/**
+	 * See: <a href=
+	 * "https://www.gnu.org/software/emacs/manual/html_node/emacs/Overview-of-Emerge.html">https://www.gnu.org/software/emacs/manual/html_node/emacs/Overview-of-Emerge.html</a>
+	 */
+	emerge("emacs", "-f emerge-files-command \"$LOCAL\" \"$REMOTE\""),
+	/**
+	 * See: <a href=
+	 * "https://www.prestosoft.com/ps.asp?page=htmlhelp/edp/command_line_options">https://www.prestosoft.com/ps.asp?page=htmlhelp/edp/command_line_options</a>
+	 */
+	examdiff("ExamDiff", "\"$LOCAL\" \"$REMOTE\" -nh"),
+	/**
+	 * See: <a href=
+	 * "https://www.guiffy.com/help/GuiffyHelp/GuiffyCmd.html">https://www.guiffy.com/help/GuiffyHelp/GuiffyCmd.html</a>
+	 */
+	guiffy("guiffy", "\"$LOCAL\" \"$REMOTE\""),
+	/**
+	 * See: <a href=
+	 * "http://vimdoc.sourceforge.net/htmldoc/diff.html">http://vimdoc.sourceforge.net/htmldoc/diff.html</a>
+	 */
+	gvimdiff("gviewdiff", "\"$LOCAL\" \"$REMOTE\""),
+	/**
+	 * See: <a href=
+	 * "http://vimdoc.sourceforge.net/htmldoc/diff.html">http://vimdoc.sourceforge.net/htmldoc/diff.html</a>
+	 */
+	gvimdiff2(gvimdiff),
+	/**
+	 * See: <a href=
+	 * "http://vimdoc.sourceforge.net/htmldoc/diff.html">http://vimdoc.sourceforge.net/htmldoc/diff.html</a>
+	 */
+	gvimdiff3(gvimdiff),
+	/**
+	 * See: <a href=
+	 * "http://kdiff3.sourceforge.net/doc/documentation.html">http://kdiff3.sourceforge.net/doc/documentation.html</a>
+	 */
+	kdiff3("kdiff3",
+			"--L1 \"$MERGED (A)\" --L2 \"$MERGED (B)\" \"$LOCAL\" \"$REMOTE\""),
+	/**
+	 * See: <a href=
+	 * "https://docs.kde.org/trunk5/en/kdesdk/kompare/commandline-options.html">https://docs.kde.org/trunk5/en/kdesdk/kompare/commandline-options.html</a>
+	 */
+	kompare("kompare", "\"$LOCAL\" \"$REMOTE\""),
+	/**
+	 * See: <a href=
+	 * "ttp://meldmerge.org/help/file-mode.html">http://meldmerge.org/help/file-mode.html</a>
+	 */
+	meld("meld", "\"$LOCAL\" \"$REMOTE\""),
+	/**
+	 * See: <a href=
+	 * "http://www.manpagez.com/man/1/opendiff/">http://www.manpagez.com/man/1/opendiff/</a>
+	 * <p>
+	 * Hint: check the ' | cat' for the call
+	 * </p>
+	 */
+	opendiff("opendiff", "\"$LOCAL\" \"$REMOTE\""),
+	/**
+	 * See: <a href=
+	 * "https://www.perforce.com/manuals/v15.1/cmdref/p4_merge.html">https://www.perforce.com/manuals/v15.1/cmdref/p4_merge.html</a>
+	 */
+	p4merge("p4merge", "\"$LOCAL\" \"$REMOTE\""),
+	/**
+	 * See: <a href=
+	 * "http://linux.math.tifr.res.in/manuals/man/tkdiff.html">http://linux.math.tifr.res.in/manuals/man/tkdiff.html</a>
+	 */
+	tkdiff("tkdiff", "\"$LOCAL\" \"$REMOTE\""),
+	/**
+	 * See: <a href=
+	 * "http://vimdoc.sourceforge.net/htmldoc/diff.html">http://vimdoc.sourceforge.net/htmldoc/diff.html</a>
+	 */
+	vimdiff("viewdiff", gvimdiff),
+	/**
+	 * See: <a href=
+	 * "http://vimdoc.sourceforge.net/htmldoc/diff.html">http://vimdoc.sourceforge.net/htmldoc/diff.html</a>
+	 */
+	vimdiff2(vimdiff),
+	/**
+	 * See: <a href=
+	 * "http://vimdoc.sourceforge.net/htmldoc/diff.html">http://vimdoc.sourceforge.net/htmldoc/diff.html</a>
+	 */
+	vimdiff3(vimdiff),
+	/**
+	 * See: <a href=
+	 * "http://manual.winmerge.org/Command_line.html">http://manual.winmerge.org/Command_line.html</a>
+	 * <p>
+	 * Hint: check how 'mergetool_find_win32_cmd "WinMergeU.exe" "WinMerge"'
+	 * works
+	 * </p>
+	 */
+	winmerge("WinMergeU", "-u -e \"$LOCAL\" \"$REMOTE\""),
+	/**
+	 * See: <a href=
+	 * "http://furius.ca/xxdiff/doc/xxdiff-doc.html">http://furius.ca/xxdiff/doc/xxdiff-doc.html</a>
+	 */
+	xxdiff("xxdiff",
+			"-R 'Accel.Search: \"Ctrl+F\"' -R 'Accel.SearchForward: \"Ctrl+G\"' \"$LOCAL\" \"$REMOTE\"");
+
+	CommandLineDiffTool(String path, String parameters) {
+		this.path = path;
+		this.parameters = parameters;
+	}
+
+	CommandLineDiffTool(CommandLineDiffTool from) {
+		this(from.getPath(), from.getParameters());
+	}
+
+	CommandLineDiffTool(String path, CommandLineDiffTool from) {
+		this(path, from.getParameters());
+	}
+
+	private final String path;
+
+	private final String parameters;
+
+	/**
+	 * @return path
+	 */
+	public String getPath() {
+		return path;
+	}
+
+	/**
+	 * @return parameters as one string
+	 */
+	public String getParameters() {
+		return parameters;
+	}
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffToolConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffToolConfig.java
new file mode 100644
index 0000000..551f634
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffToolConfig.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2018-2021, Andre Bossert <andre.bossert@siemens.com>
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.diffmergetool;
+
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DIFFTOOL_SECTION;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DIFF_SECTION;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_CMD;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_GUITOOL;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PATH;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PROMPT;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_TOOL;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_TRUST_EXIT_CODE;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Config.SectionParser;
+import org.eclipse.jgit.lib.internal.BooleanTriState;
+
+/**
+ * Keeps track of difftool related configuration options.
+ */
+public class DiffToolConfig {
+
+	/** Key for {@link Config#get(SectionParser)}. */
+	public static final Config.SectionParser<DiffToolConfig> KEY = DiffToolConfig::new;
+
+	private final String toolName;
+
+	private final String guiToolName;
+
+	private final boolean prompt;
+
+	private final BooleanTriState trustExitCode;
+
+	private final Map<String, ExternalDiffTool> tools;
+
+	private DiffToolConfig(Config rc) {
+		toolName = rc.getString(CONFIG_DIFF_SECTION, null, CONFIG_KEY_TOOL);
+		guiToolName = rc.getString(CONFIG_DIFF_SECTION, null,
+				CONFIG_KEY_GUITOOL);
+		prompt = rc.getBoolean(CONFIG_DIFFTOOL_SECTION, CONFIG_KEY_PROMPT,
+				true);
+		String trustStr = rc.getString(CONFIG_DIFFTOOL_SECTION, null,
+				CONFIG_KEY_TRUST_EXIT_CODE);
+		if (trustStr != null) {
+			trustExitCode = Boolean.parseBoolean(trustStr)
+					? BooleanTriState.TRUE
+					: BooleanTriState.FALSE;
+		} else {
+			trustExitCode = BooleanTriState.UNSET;
+		}
+		tools = new HashMap<>();
+		Set<String> subsections = rc.getSubsections(CONFIG_DIFFTOOL_SECTION);
+		for (String name : subsections) {
+			String cmd = rc.getString(CONFIG_DIFFTOOL_SECTION, name,
+					CONFIG_KEY_CMD);
+			String path = rc.getString(CONFIG_DIFFTOOL_SECTION, name,
+					CONFIG_KEY_PATH);
+			if ((cmd != null) || (path != null)) {
+				tools.put(name, new UserDefinedDiffTool(name, path, cmd));
+			}
+		}
+	}
+
+	/**
+	 * @return the default diff tool name (diff.tool)
+	 */
+	public String getDefaultToolName() {
+		return toolName;
+	}
+
+	/**
+	 * @return the default GUI diff tool name (diff.guitool)
+	 */
+	public String getDefaultGuiToolName() {
+		return guiToolName;
+	}
+
+	/**
+	 * @return the diff tool "prompt" option (difftool.prompt)
+	 */
+	public boolean isPrompt() {
+		return prompt;
+	}
+
+	/**
+	 * @return the diff tool "trust exit code" option (difftool.trustExitCode)
+	 */
+	public boolean isTrustExitCode() {
+		return trustExitCode == BooleanTriState.TRUE;
+	}
+
+	/**
+	 * @return the tools map
+	 */
+	public Map<String, ExternalDiffTool> getTools() {
+		return tools;
+	}
+
+	/**
+	 * @return the tool names
+	 */
+	public Set<String> getToolNames() {
+		return tools.keySet();
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java
index cb0640d..39729a4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java
@@ -23,6 +23,8 @@
  */
 public class DiffTools {
 
+	private final DiffToolConfig config;
+
 	private Map<String, ExternalDiffTool> predefinedTools;
 
 	private Map<String, ExternalDiffTool> userDefinedTools;
@@ -31,9 +33,10 @@ public class DiffTools {
 	 * Creates the external diff-tools manager for given repository.
 	 *
 	 * @param repo
-	 *            the repository database
+	 *            the repository
 	 */
 	public DiffTools(Repository repo) {
+		config = repo.getConfig().get(DiffToolConfig.KEY);
 		setupPredefinedTools();
 		setupUserDefinedTools();
 	}
@@ -69,7 +72,7 @@ public int compare(String newPath, String oldPath, String newId,
 	 * @return the tool names
 	 */
 	public Set<String> getToolNames() {
-		return Collections.emptySet();
+		return config.getToolNames();
 	}
 
 	/**
@@ -112,10 +115,29 @@ public boolean isInteractive() {
 
 	private void setupPredefinedTools() {
 		predefinedTools = new TreeMap<>();
+		for (CommandLineDiffTool tool : CommandLineDiffTool.values()) {
+			predefinedTools.put(tool.name(), new PreDefinedDiffTool(tool));
+		}
 	}
 
 	private void setupUserDefinedTools() {
 		userDefinedTools = new TreeMap<>();
+		Map<String, ExternalDiffTool> userTools = config.getTools();
+		for (String name : userTools.keySet()) {
+			ExternalDiffTool userTool = userTools.get(name);
+			// if difftool.<name>.cmd is defined we have user defined tool
+			if (userTool.getCommand() != null) {
+				userDefinedTools.put(name, userTool);
+			} else if (userTool.getPath() != null) {
+				// if difftool.<name>.path is defined we just overload the path
+				// of predefined tool
+				PreDefinedDiffTool predefTool = (PreDefinedDiffTool) predefinedTools
+						.get(name);
+				if (predefTool != null) {
+					predefTool.setPath(userTool.getPath());
+				}
+			}
+		}
 	}
 
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedDiffTool.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedDiffTool.java
new file mode 100644
index 0000000..1c69fb4
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedDiffTool.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2018-2021, Andre Bossert <andre.bossert@siemens.com>
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.diffmergetool;
+
+/**
+ * The pre-defined diff tool.
+ */
+public class PreDefinedDiffTool extends UserDefinedDiffTool {
+
+	/**
+	 * Create a pre-defined diff tool
+	 *
+	 * @param name
+	 *            the name
+	 * @param path
+	 *            the path
+	 * @param parameters
+	 *            the tool parameters as one string that is used together with
+	 *            path as command
+	 */
+	public PreDefinedDiffTool(String name, String path, String parameters) {
+		super(name, path, parameters);
+	}
+
+	/**
+	 * Creates the pre-defined diff tool
+	 *
+	 * @param tool
+	 *            the command line diff tool
+	 *
+	 */
+	public PreDefinedDiffTool(CommandLineDiffTool tool) {
+		this(tool.name(), tool.getPath(), tool.getParameters());
+	}
+
+	/**
+	 * @param path
+	 */
+	@Override
+	public void setPath(String path) {
+		// handling of spaces in path
+		if (path.contains(" ")) { //$NON-NLS-1$
+			// add quotes before if needed
+			if (!path.startsWith("\"")) { //$NON-NLS-1$
+				path = "\"" + path; //$NON-NLS-1$
+			}
+			// add quotes after if needed
+			if (!path.endsWith("\"")) { //$NON-NLS-1$
+				path = path + "\""; //$NON-NLS-1$
+			}
+		}
+		super.setPath(path);
+	}
+
+	/**
+	 * {@inheritDoc}
+	 *
+	 * @return the concatenated path and command of the pre-defined diff tool
+	 */
+	@Override
+	public String getCommand() {
+		return getPath() + " " + super.getCommand(); //$NON-NLS-1$
+	}
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/UserDefinedDiffTool.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/UserDefinedDiffTool.java
new file mode 100644
index 0000000..012296e
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/UserDefinedDiffTool.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2018-2021, Andre Bossert <andre.bossert@siemens.com>
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.diffmergetool;
+
+/**
+ * The user-defined diff tool.
+ */
+public class UserDefinedDiffTool implements ExternalDiffTool {
+
+	/**
+	 * the diff tool name
+	 */
+	private final String name;
+
+	/**
+	 * the diff tool path
+	 */
+	private String path;
+
+	/**
+	 * the diff tool command
+	 */
+	private final String cmd;
+
+	/**
+	 * Creates the diff tool
+	 *
+	 * @param name
+	 *            the name
+	 * @param path
+	 *            the path
+	 * @param cmd
+	 *            the command
+	 */
+	public UserDefinedDiffTool(String name, String path, String cmd) {
+		this.name = name;
+		this.path = path;
+		this.cmd = cmd;
+	}
+
+	/**
+	 * @return the diff tool name
+	 */
+	@Override
+	public String getName() {
+		return name;
+	}
+
+	/**
+	 * The path of the diff tool.
+	 *
+	 * <p>
+	 * The path to a pre-defined external diff tool can be overridden by
+	 * specifying {@code difftool.<tool>.path} in a configuration file.
+	 * </p>
+	 * <p>
+	 * For a user defined diff tool (that does not override a pre-defined diff
+	 * tool), the path is ignored when invoking the tool.
+	 * </p>
+	 *
+	 * @return the diff tool path
+	 *
+	 * @see <a href=
+	 *      "https://git-scm.com/docs/git-difftool">https://git-scm.com/docs/git-difftool</a>
+	 */
+	@Override
+	public String getPath() {
+		return path;
+	}
+
+	/**
+	 * The command of the diff tool.
+	 *
+	 * <p>
+	 * A pre-defined external diff tool can be overridden using the tools name
+	 * in a configuration file. The overwritten tool is then a user defined tool
+	 * and the command of the diff tool is specified with
+	 * {@code difftool.<tool>.cmd}. This command must work without prepending
+	 * the value of {@link #getPath()} and can sometimes include tool
+	 * parameters.
+	 * </p>
+	 *
+	 * @return the diff tool command
+	 *
+	 * @see <a href=
+	 *      "https://git-scm.com/docs/git-difftool">https://git-scm.com/docs/git-difftool</a>
+	 */
+	@Override
+	public String getCommand() {
+		return cmd;
+	}
+
+	/**
+	 * Overrides the path for the given tool. Equivalent to setting
+	 * {@code difftool.<tool>.path}.
+	 *
+	 * @param path
+	 *            the new diff tool path
+	 *
+	 * @see <a href=
+	 *      "https://git-scm.com/docs/git-difftool">https://git-scm.com/docs/git-difftool</a>
+	 */
+	public void setPath(String path) {
+		this.path = path;
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java
index 8c48cd9..5ab973f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java
@@ -187,6 +187,12 @@ public static FileSnapshot save(Instant modified) {
 	private FileStoreAttributes fileStoreAttributeCache;
 
 	/**
+	 * if {@code true} read filesystem time resolution from configuration file
+	 * otherwise use fallback resolution
+	 */
+	private boolean useConfig;
+
+	/**
 	 * Object that uniquely identifies the given file, or {@code
 	 * null} if a file key is not available
 	 */
@@ -222,9 +228,7 @@ protected FileSnapshot(File file) {
 	protected FileSnapshot(File file, boolean useConfig) {
 		this.file = file;
 		this.lastRead = Instant.now();
-		this.fileStoreAttributeCache = useConfig
-				? FS.getFileStoreAttributes(file.toPath())
-				: FALLBACK_FILESTORE_ATTRIBUTES;
+		this.useConfig = useConfig;
 		BasicFileAttributes fileAttributes = null;
 		try {
 			fileAttributes = FS.DETECTED.fileAttributes(file);
@@ -379,7 +383,7 @@ public void setClean(FileSnapshot other) {
 	 *             if sleep was interrupted
 	 */
 	public void waitUntilNotRacy() throws InterruptedException {
-		long timestampResolution = fileStoreAttributeCache
+		long timestampResolution = fileStoreAttributeCache()
 				.getFsTimestampResolution().toNanos();
 		while (isRacyClean(Instant.now())) {
 			TimeUnit.NANOSECONDS.sleep(timestampResolution);
@@ -416,6 +420,15 @@ public boolean equals(Object obj) {
 		return equals(other);
 	}
 
+	/**
+	 * Check if the file exists
+	 *
+	 * @return true if the file exists
+	 */
+	public boolean fileExists() {
+		return !MISSING_FILEKEY.equals(this.fileKey);
+	}
+
 	/** {@inheritDoc} */
 	@Override
 	public int hashCode() {
@@ -500,10 +513,10 @@ private boolean isRacyClean(Instant read) {
 	}
 
 	private long getEffectiveRacyThreshold() {
-		long timestampResolution = fileStoreAttributeCache
+		long timestampResolution = fileStoreAttributeCache()
 				.getFsTimestampResolution().toNanos();
-		long minRacyInterval = fileStoreAttributeCache.getMinimalRacyInterval()
-				.toNanos();
+		long minRacyInterval = fileStoreAttributeCache()
+				.getMinimalRacyInterval().toNanos();
 		long max = Math.max(timestampResolution, minRacyInterval);
 		// safety margin: factor 2.5 below 100ms otherwise 1.25
 		return max < 100_000_000L ? max * 5 / 2 : max * 5 / 4;
@@ -563,4 +576,13 @@ private boolean isSizeChanged(long currSize) {
 		}
 		return changed;
 	}
+
+	private FileStoreAttributes fileStoreAttributeCache() {
+		if (fileStoreAttributeCache == null) {
+			fileStoreAttributeCache = useConfig
+					? FS.getFileStoreAttributes(file.toPath().getParent())
+					: FALLBACK_FILESTORE_ATTRIBUTES;
+		}
+		return fileStoreAttributeCache;
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
index 93c6201..ca4f7a2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
@@ -12,8 +12,8 @@
 
 import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX;
 import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
-import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
 import static org.eclipse.jgit.internal.storage.pack.PackExt.KEEP;
+import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -46,8 +46,9 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.TreeMap;
-import java.util.concurrent.Callable;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutorService;
+import java.util.function.Supplier;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -214,19 +215,18 @@ public GC(FileRepository repo) {
 	 *             If the configuration parameter "gc.pruneexpire" couldn't be
 	 *             parsed
 	 */
-	// TODO(ms): change signature and return Future<Collection<Pack>>
-	@SuppressWarnings("FutureReturnValueIgnored")
-	public Collection<Pack> gc() throws IOException, ParseException {
+	public CompletableFuture<Collection<Pack>> gc()
+			throws IOException, ParseException {
 		if (!background) {
-			return doGc();
+			return CompletableFuture.completedFuture(doGc());
 		}
 		final GcLog gcLog = new GcLog(repo);
 		if (!gcLog.lock()) {
 			// there is already a background gc running
-			return Collections.emptyList();
+			return CompletableFuture.completedFuture(Collections.emptyList());
 		}
 
-		Callable<Collection<Pack>> gcTask = () -> {
+		Supplier<Collection<Pack>> gcTask = () -> {
 			try {
 				Collection<Pack> newPacks = doGc();
 				if (automatic && tooManyLooseObjects()) {
@@ -251,9 +251,7 @@ public Collection<Pack> gc() throws IOException, ParseException {
 			}
 			return Collections.emptyList();
 		};
-		// TODO(ms): change signature and return the Future
-		executor().submit(gcTask);
-		return Collections.emptyList();
+		return CompletableFuture.supplyAsync(gcTask, executor());
 	}
 
 	private ExecutorService executor() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java
index 1c23ff2..55b2646 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java
@@ -153,7 +153,7 @@ public long getPackSize() {
 		String p = pack.getAbsolutePath();
 		String i = p.substring(0, p.length() - ".pack".length()) + ".idx"; //$NON-NLS-1$ //$NON-NLS-2$
 		File idx = new File(i);
-		if (idx.exists() && idx.isFile())
+		if (idx.isFile())
 			size += idx.length();
 		return size;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
index 17a9100..2167262 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
@@ -1092,10 +1092,14 @@ LooseRef scanRef(LooseRef ref, String name) throws IOException {
 		final int limit = 4096;
 		final byte[] buf;
 		FileSnapshot otherSnapshot = FileSnapshot.save(path);
+		if (!otherSnapshot.fileExists()) {
+			return null;
+		}
+
 		try {
 			buf = IO.readSome(path, limit);
 		} catch (FileNotFoundException noFile) {
-			if (path.exists() && path.isFile()) {
+			if (path.isFile()) {
 				throw noFile;
 			}
 			return null; // doesn't exist or no file; not a reference.
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 24eebc6..4b21e4b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
@@ -1,7 +1,8 @@
 /*
  * Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com>
  * Copyright (C) 2010, Chris Aniszczyk <caniszczyk@gmail.com>
- * Copyright (C) 2012, 2020, Robin Rosenberg and others
+ * Copyright (C) 2012-2013, Robin Rosenberg
+ * Copyright (C) 2018-2021, Andre Bossert <andre.bossert@siemens.com> and others
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -29,6 +30,48 @@ public final class ConfigConstants {
 	/** The "diff" section */
 	public static final String CONFIG_DIFF_SECTION = "diff";
 
+	/**
+	 * The "tool" key within "diff" section
+	 *
+	 * @since 6.1
+	 */
+	public static final String CONFIG_KEY_TOOL = "tool";
+
+	/**
+	 * The "guitool" key within "diff" section
+	 *
+	 * @since 6.1
+	 */
+	public static final String CONFIG_KEY_GUITOOL = "guitool";
+
+	/**
+	 * The "difftool" section
+	 *
+	 * @since 6.1
+	 */
+	public static final String CONFIG_DIFFTOOL_SECTION = "difftool";
+
+	/**
+	 * The "prompt" key within "difftool" section
+	 *
+	 * @since 6.1
+	 */
+	public static final String CONFIG_KEY_PROMPT = "prompt";
+
+	/**
+	 * The "trustExitCode" key within "difftool" section
+	 *
+	 * @since 6.1
+	 */
+	public static final String CONFIG_KEY_TRUST_EXIT_CODE = "trustExitCode";
+
+	/**
+	 * The "cmd" key within "difftool.*." section
+	 *
+	 * @since 6.1
+	 */
+	public static final String CONFIG_KEY_CMD = "cmd";
+
 	/** The "dfs" section */
 	public static final String CONFIG_DFS_SECTION = "dfs";
 
diff --git a/tools/maven-central/deploy.rb b/tools/maven-central/deploy.rb
index 834fa94..ece1337 100755
--- a/tools/maven-central/deploy.rb
+++ b/tools/maven-central/deploy.rb
@@ -61,6 +61,7 @@
              group + '.lfs.server',
              group + '.pgm',
              group + '.ssh.apache',
+             group + '.ssh.apache.agent',
              group + '.ssh.jsch',
              group + '.ui']
 
diff --git a/tools/maven-central/download.rb b/tools/maven-central/download.rb
index bc48c82..b3c4e3d 100755
--- a/tools/maven-central/download.rb
+++ b/tools/maven-central/download.rb
@@ -21,6 +21,7 @@
              group + '.lfs.server',
              group + '.pgm',
              group + '.ssh.apache',
+             group + '.ssh.apache.agent',
              group + '.ssh.jsch',
              group + '.ui']