Merge branch 'stable-6.0' into stable-6.1

* stable-6.0:
  Checkout: better directory handling

Change-Id: Ide9fb318b5fe413d73d73a19943c6dacdcf07f3d
diff --git a/.bazelrc b/.bazelrc
index e24be88..8c32661 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -3,8 +3,33 @@
 build --experimental_strict_action_env
 build --action_env=PATH
 build --disk_cache=~/.gerritcodereview/bazel-cache/cas
-build --java_toolchain //tools:error_prone_warnings_toolchain
+
+# Builds using remote_jdk11, executes using remote_jdk11 or local_jdk
+build --java_language_version=11
+build --java_runtime_version=remotejdk_11
+build --tool_java_language_version=11
+build --tool_java_runtime_version=remotejdk_11
+
+# Builds and executes on RBE using remotejdk_11
+build:remote --java_language_version=11
+build:remote --java_runtime_version=remotejdk_11
+build:remote --tool_java_language_version=11
+build:remote --tool_java_runtime_version=remotejdk_11
+
+# Builds using remote_jdk17, executes using remote_jdk11 or local_jdk
+build:java17 --java_language_version=17
+build:java17 --java_runtime_version=remotejdk_17
+build:java17 --tool_java_language_version=17
+build:java17 --tool_java_runtime_version=remotejdk_17
+
+# Builds and executes on RBE using remotejdk_17
+build:remote17 --java_language_version=17
+build:remote17 --java_runtime_version=remotejdk_17
+build:remote17 --tool_java_language_version=17
+build:remote17 --tool_java_runtime_version=remotejdk_17
 
 test --build_tests_only
 test --test_output=errors
 
+import %workspace%/tools/remote-bazelrc
+
diff --git a/.bazelversion b/.bazelversion
index fcdb2e1..0062ac9 100644
--- a/.bazelversion
+++ b/.bazelversion
@@ -1 +1 @@
-4.0.0
+5.0.0
diff --git a/.mailmap b/.mailmap
index f0bc990..f0af49b 100644
--- a/.mailmap
+++ b/.mailmap
@@ -12,6 +12,7 @@
 Saša Živkov <sasa.zivkov@sap.com>                           Sasa Zivkov <sasa.zivkov@sap.com>
 Saša Živkov <sasa.zivkov@sap.com>                           Saša Živkov <zivkov@gmail.com>
 Saša Živkov <sasa.zivkov@sap.com>                           Sasa Zivkov <zivkov@gmail.com>
+Sebastian Schuberth <sschuberth@gmail.com>                  Sebastian Schuberth <sebastian.schuberth@bosch.io>
 Shawn Pearce <spearce@spearce.org>                          Shawn O. Pearce <sop@google.com>
 Shawn Pearce <spearce@spearce.org>                          Shawn Pearce <sop@google.com>
 Shawn Pearce <spearce@spearce.org>                          Shawn O. Pearce <spearce@spearce.org>
diff --git a/DEPENDENCIES b/DEPENDENCIES
index ebbe480..93fa850 100644
--- a/DEPENDENCIES
+++ b/DEPENDENCIES
@@ -1,69 +1,68 @@
 maven/mavencentral/args4j/args4j/2.33, MIT, approved, CQ11068
-maven/mavencentral/com.google.code.gson/gson/2.8.7, Apache-2.0, approved, CQ23496
-maven/mavencentral/com.googlecode.javaewah/JavaEWAH/1.1.12, Apache-2.0, approved, CQ11658
+maven/mavencentral/com.google.code.gson/gson/2.8.9, Apache-2.0, approved, CQ23496
+maven/mavencentral/com.googlecode.javaewah/JavaEWAH/1.1.13, Apache-2.0, approved, CQ11658
 maven/mavencentral/com.jcraft/jsch/0.1.55, BSD-3-Clause, approved, CQ19435
 maven/mavencentral/com.jcraft/jzlib/1.1.1, BSD-2-Clause, approved, CQ6218
 maven/mavencentral/commons-codec/commons-codec/1.11, Apache-2.0 AND BSD-3-Clause, approved, CQ15971
 maven/mavencentral/commons-logging/commons-logging/1.2, Apache-2.0, approved, CQ10162
-maven/mavencentral/javax.servlet/javax.servlet-api/3.1.0, Apache-2.0 AND (CDDL-1.1 OR GPL-2.0 WITH Classpath-exception-2.0), approved, CQ7248
-maven/mavencentral/junit/junit/4.13, , approved, CQ22796
-maven/mavencentral/log4j/log4j/1.2.15, Apache-2.0, approved, CQ7837
+maven/mavencentral/javax.servlet/javax.servlet-api/4.0.0, , approved, CQ16125
+maven/mavencentral/junit/junit/4.13.2, EPL-2.0, approved, CQ23636
 maven/mavencentral/net.bytebuddy/byte-buddy-agent/1.9.0, Apache-2.0, approved, clearlydefined
 maven/mavencentral/net.bytebuddy/byte-buddy/1.9.0, Apache-2.0, approved, clearlydefined
 maven/mavencentral/net.i2p.crypto/eddsa/0.3.0, CC0-1.0, approved, CQ22537
+maven/mavencentral/net.java.dev.jna/jna-platform/5.8.0, Apache-2.0 OR LGPL-2.1-or-later, approved, CQ23218
+maven/mavencentral/net.java.dev.jna/jna/5.8.0, Apache-2.0 OR LGPL-2.1-or-later, approved, CQ23217
 maven/mavencentral/net.sf.jopt-simple/jopt-simple/4.6, MIT, approved, clearlydefined
-maven/mavencentral/org.apache.ant/ant-launcher/1.10.10, Apache-2.0 AND W3C AND LicenseRef-Public-Domain, approved, CQ15560
-maven/mavencentral/org.apache.ant/ant/1.10.10, Apache-2.0 AND W3C AND LicenseRef-Public-Domain, approved, CQ15560
-maven/mavencentral/org.apache.commons/commons-compress/1.20, Apache-2.0 AND BSD-3-Clause AND LicenseRef-Public-Domain, approved, CQ21771
+maven/mavencentral/org.apache.ant/ant-launcher/1.10.12, Apache-2.0 AND W3C AND LicenseRef-Public-Domain, approved, CQ15560
+maven/mavencentral/org.apache.ant/ant/1.10.12, Apache-2.0 AND W3C AND LicenseRef-Public-Domain, approved, CQ15560
+maven/mavencentral/org.apache.commons/commons-compress/1.21, Apache-2.0 AND BSD-3-Clause AND bzip2-1.0.6 AND LicenseRef-Public-Domain, approved, CQ23710
 maven/mavencentral/org.apache.commons/commons-math3/3.2, Apache-2.0, approved, clearlydefined
 maven/mavencentral/org.apache.httpcomponents/httpclient/4.5.13, Apache-2.0 AND LicenseRef-Public-Domain, approved, CQ23527
 maven/mavencentral/org.apache.httpcomponents/httpcore/4.4.14, Apache-2.0, approved, CQ23528
-maven/mavencentral/org.apache.sshd/sshd-common/2.7.0, Apache-2.0 and ISC, approved, CQ23469
-maven/mavencentral/org.apache.sshd/sshd-core/2.7.0, Apache-2.0, approved, CQ23469
-maven/mavencentral/org.apache.sshd/sshd-osgi/2.7.0, Apache-2.0 and ISC, approved, CQ23469
-maven/mavencentral/org.apache.sshd/sshd-sftp/2.7.0, Apache-2.0, approved, CQ23470
+maven/mavencentral/org.apache.sshd/sshd-osgi/2.8.0, Apache-2.0, approved, CQ23892
+maven/mavencentral/org.apache.sshd/sshd-sftp/2.8.0, Apache-2.0, approved, CQ23893
 maven/mavencentral/org.assertj/assertj-core/3.20.2, Apache-2.0, approved, clearlydefined
-maven/mavencentral/org.bouncycastle/bcpg-jdk15on/1.69, MIT and Apache-2.0, approved, CQ23472
-maven/mavencentral/org.bouncycastle/bcpkix-jdk15on/1.69, MIT, approved, CQ23473
-maven/mavencentral/org.bouncycastle/bcprov-jdk15on/1.69, MIT, approved, CQ23471
-maven/mavencentral/org.bouncycastle/bcutil-jdk15on/1.69, MIT, approved, CQ23474
-maven/mavencentral/org.eclipse.jetty/jetty-http/9.4.43.v20210629, , approved, eclipse
-maven/mavencentral/org.eclipse.jetty/jetty-io/9.4.43.v20210629, , approved, eclipse
-maven/mavencentral/org.eclipse.jetty/jetty-security/9.4.43.v20210629, , approved, eclipse
-maven/mavencentral/org.eclipse.jetty/jetty-server/9.4.43.v20210629, , approved, eclipse
-maven/mavencentral/org.eclipse.jetty/jetty-servlet/9.4.43.v20210629, , approved, eclipse
-maven/mavencentral/org.eclipse.jetty/jetty-util-ajax/9.4.43.v20210629, , approved, eclipse
-maven/mavencentral/org.eclipse.jetty/jetty-util/9.4.43.v20210629, , approved, eclipse
-maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ant.test/5.13.0-SNAPSHOT, , approved, eclipse
-maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ant/5.13.0-SNAPSHOT, , approved, eclipse
-maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.archive/5.13.0-SNAPSHOT, , approved, eclipse
-maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.gpg.bc/5.13.0-SNAPSHOT, , approved, eclipse
-maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.http.apache/5.13.0-SNAPSHOT, , approved, eclipse
-maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.http.server/5.13.0-SNAPSHOT, , approved, eclipse
-maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.http.test/5.13.0-SNAPSHOT, , approved, eclipse
-maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.junit.http/5.13.0-SNAPSHOT, , approved, eclipse
-maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.junit.ssh/5.13.0-SNAPSHOT, , approved, eclipse
-maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.junit/5.13.0-SNAPSHOT, , approved, eclipse
-maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.lfs.server.test/5.13.0-SNAPSHOT, , approved, eclipse
-maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.lfs.server/5.13.0-SNAPSHOT, , approved, eclipse
-maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.lfs.test/5.13.0-SNAPSHOT, , approved, eclipse
-maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.lfs/5.13.0-SNAPSHOT, , approved, eclipse
-maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.pgm.test/5.13.0-SNAPSHOT, , approved, eclipse
-maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.pgm/5.13.0-SNAPSHOT, , approved, eclipse
-maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ssh.apache.test/5.13.0-SNAPSHOT, , approved, eclipse
-maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ssh.apache/5.13.0-SNAPSHOT, , approved, eclipse
-maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ssh.jsch/5.13.0-SNAPSHOT, , approved, eclipse
-maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.test/5.13.0-SNAPSHOT, , approved, eclipse
-maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ui/5.13.0-SNAPSHOT, , approved, eclipse
-maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit/5.13.0-SNAPSHOT, , approved, eclipse
-maven/mavencentral/org.hamcrest/hamcrest-core/1.3, BSD-2-Clause, approved, CQ7063
-maven/mavencentral/org.hamcrest/hamcrest/2.2, BSD-2-Clause, approved, clearlydefined
-maven/mavencentral/org.mockito/mockito-core/2.23.0, MIT, approved, CQ17976
+maven/mavencentral/org.bouncycastle/bcpg-jdk15on/1.70, Apache-2.0, approved, #1713
+maven/mavencentral/org.bouncycastle/bcpkix-jdk15on/1.70, MIT, approved, clearlydefined
+maven/mavencentral/org.bouncycastle/bcprov-jdk15on/1.70, MIT, approved, #1712
+maven/mavencentral/org.bouncycastle/bcutil-jdk15on/1.70, MIT, approved, clearlydefined
+maven/mavencentral/org.eclipse.jetty.toolchain/jetty-servlet-api/4.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty
+maven/mavencentral/org.eclipse.jetty/jetty-http/10.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty
+maven/mavencentral/org.eclipse.jetty/jetty-io/10.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty
+maven/mavencentral/org.eclipse.jetty/jetty-security/10.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty
+maven/mavencentral/org.eclipse.jetty/jetty-server/10.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty
+maven/mavencentral/org.eclipse.jetty/jetty-servlet/10.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty
+maven/mavencentral/org.eclipse.jetty/jetty-util/10.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty
+maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ant.test/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
+maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ant/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
+maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.archive/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
+maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.gpg.bc/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
+maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.http.apache/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
+maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.http.server/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
+maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.http.test/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
+maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.junit.http/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
+maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.junit.ssh/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
+maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.junit/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
+maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.lfs.server.test/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
+maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.lfs.server/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
+maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.lfs.test/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
+maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.lfs/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
+maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.pgm.test/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
+maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.pgm/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
+maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ssh.apache.agent/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
+maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ssh.apache.test/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
+maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ssh.apache/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
+maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ssh.jsch/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
+maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.test/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
+maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ui/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
+maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
+maven/mavencentral/org.hamcrest/hamcrest-core/1.3, BSD-2-Clause, approved, CQ11429
+maven/mavencentral/org.mockito/mockito-core/2.23.0, Apache-2.0 AND MIT, approved, #958
 maven/mavencentral/org.objenesis/objenesis/2.6, Apache-2.0, approved, CQ15478
-maven/mavencentral/org.openjdk.jmh/jmh-core/1.32, GPL-2.0, approved, CQ23499
-maven/mavencentral/org.openjdk.jmh/jmh-generator-annprocess/1.32, GPL-2.0, approved, CQ23500
-maven/mavencentral/org.osgi/org.osgi.core/4.3.1, Apache-2.0, approved, CQ10111
-maven/mavencentral/org.slf4j/jcl-over-slf4j/1.7.30, Apache-2.0, approved, CQ12843
+maven/mavencentral/org.openjdk.jmh/jmh-core/1.32, GPL-2.0-only with Classpath-exception-2.0, approved, #959
+maven/mavencentral/org.openjdk.jmh/jmh-generator-annprocess/1.32, GPL-2.0-only with Classpath-exception-2.0, approved, #962
+maven/mavencentral/org.osgi/org.osgi.core/6.0.0, Apache-2.0, approved, #1794
+maven/mavencentral/org.slf4j/jcl-over-slf4j/1.7.32, Apache-2.0, approved, CQ12843
 maven/mavencentral/org.slf4j/slf4j-api/1.7.30, MIT, approved, CQ13368
-maven/mavencentral/org.slf4j/slf4j-log4j12/1.7.30, MIT, approved, CQ7665
+maven/mavencentral/org.slf4j/slf4j-simple/1.7.30, MIT, approved, CQ7952
 maven/mavencentral/org.tukaani/xz/1.9, LicenseRef-Public-Domain, approved, CQ23498
diff --git a/Documentation/config-options.md b/Documentation/config-options.md
index 7221cce..815677c 100644
--- a/Documentation/config-options.md
+++ b/Documentation/config-options.md
@@ -46,7 +46,8 @@
 | `core.streamFileThreshold` | `50 MiB` | &#x20DE; | The size threshold beyond which objects must be streamed. |
 | `core.supportsAtomicFileCreation` | `true` | &#x20DE; | Whether the filesystem supports atomic file creation. |
 | `core.symlinks` | Auto detect if filesystem supports symlinks| &#x2705; | If false, symbolic links are checked out as small plain files that contain the link text. |
-| `core.trustFolderStat` | `true` | &#x20DE; | Whether to trust the pack folder's modification time. If `false` JGit will always scan the `.git/objects/pack` folder to check for new pack files. This can help to workaround caching issues on NFS, but reduces performance. If set to `true` it uses the `lastmodified` attribute of the folder and assumes that no new pack files can be in this folder if its modification time has not changed. |
+| `core.trustFolderStat` | `true` | &#x20DE; | Whether to trust the pack folder's, packed-refs file's and loose-objects folder's file attributes (Java equivalent of stat command on *nix). When looking for pack files, if `false` JGit will always scan the `.git/objects/pack` folder and if set to `true` it assumes that pack files are unchanged if the file attributes of the pack folder are unchanged. When getting the list of packed refs, if `false` JGit will always read the packed-refs file and if set to `true` it uses the file attributes of the packed-refs file and will only read it if a file attribute has changed. When looking for loose objects, if `false` and if a loose object is not found, JGit will open and close a stream to `.git/objects` folder (which can refresh its directory listing, at least on some NFS clients) and retry looking for that loose object. Setting this option to `false` can help to workaround caching issues on NFS, but reduces performance. |
+| `core.trustPackedRefsStat` | `unset` | &#x20DE; | Whether to trust the file attributes (Java equivalent of stat command on *nix) of the packed-refs file. If `never` JGit will ignore the file attributes of the packed-refs file and always read it. If `always` JGit will trust the file attributes of the packed-refs file and will only read it if a file attribute has changed. `after_open` behaves the same as `always`, except that the packed-refs file is opened and closed before its file attributes are considered. An open/close of the packed-refs file is known to refresh its file attributes, at least on some NFS clients. If `unset`, JGit will use the behavior described in `trustFolderStat`. |
 | `core.worktree` | Root directory of the working tree if it is not the parent directory of the `.git` directory | &#x2705; | The path to the root of the working tree. |
 
 ## __gc__ options
diff --git a/WORKSPACE b/WORKSPACE
index 51a815e..040617e 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -1,23 +1,6 @@
 workspace(name = "jgit")
 
 load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
-
-http_archive(
-    name = "bazel_skylib",
-    sha256 = "2ea8a5ed2b448baf4a6855d3ce049c4c452a6470b1efd1504fdb7c1c134d220a",
-    strip_prefix = "bazel-skylib-0.8.0",
-    urls = ["https://github.com/bazelbuild/bazel-skylib/archive/0.8.0.tar.gz"],
-)
-
-# Check Bazel version when invoked by Bazel directly
-load("//tools:bazelisk_version.bzl", "bazelisk_version")
-
-bazelisk_version(name = "bazelisk_version")
-
-load("@bazelisk_version//:check.bzl", "check_bazel_version")
-
-check_bazel_version()
-
 load("//tools:bazlets.bzl", "load_bazlets")
 
 load_bazlets(commit = "f30a992da9fc855dce819875afb59f9dd6f860cd")
@@ -28,32 +11,18 @@
 )
 
 http_archive(
-    name = "openjdk15_linux_archive",
-    build_file_content = """
-java_runtime(name = 'runtime', srcs =  glob(['**']), visibility = ['//visibility:public'])
-exports_files(["WORKSPACE"], visibility = ["//visibility:public"])
-""",
-    sha256 = "0a38f1138c15a4f243b75eb82f8ef40855afcc402e3c2a6de97ce8235011b1ad",
-    strip_prefix = "zulu15.27.17-ca-jdk15.0.0-linux_x64",
+    name = "rbe_jdk11",
+    sha256 = "766796de71916118e528b9f4334c29c9c9b4e926227bf3264dee555e6a4306c8",
+    strip_prefix = "rbe_autoconfig-2.0.0",
     urls = [
-        "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu15.27.17-ca-jdk15.0.0-linux_x64.tar.gz",
-        "https://cdn.azul.com/zulu/bin/zulu15.27.17-ca-jdk15.0.0-linux_x64.tar.gz",
+        "https://gerrit-bazel.storage.googleapis.com/rbe_autoconfig/v2.0.0.tar.gz",
+        "https://github.com/davido/rbe_autoconfig/archive/v2.0.0.tar.gz",
     ],
 )
 
-http_archive(
-    name = "openjdk15_darwin_archive",
-    build_file_content = """
-java_runtime(name = 'runtime', srcs =  glob(['**']), visibility = ['//visibility:public'])
-exports_files(["WORKSPACE"], visibility = ["//visibility:public"])
-""",
-    sha256 = "f80b2e0512d9d8a92be24497334c974bfecc8c898fc215ce0e76594f00437482",
-    strip_prefix = "zulu15.27.17-ca-jdk15.0.0-macosx_x64",
-    urls = [
-        "https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu15.27.17-ca-jdk15.0.0-macosx_x64.tar.gz",
-        "https://cdn.azul.com/zulu/bin/zulu15.27.17-ca-jdk15.0.0-macosx_x64.tar.gz",
-    ],
-)
+register_toolchains("//tools:error_prone_warnings_toolchain_java11_definition")
+
+register_toolchains("//tools:error_prone_warnings_toolchain_java17_definition")
 
 JMH_VERS = "1.32"
 
@@ -123,14 +92,14 @@
 
 maven_jar(
     name = "sshd-osgi",
-    artifact = "org.apache.sshd:sshd-osgi:2.7.0",
-    sha1 = "a101aad0f79ad424498098f7e91c39d3d92177c1",
+    artifact = "org.apache.sshd:sshd-osgi:2.8.0",
+    sha1 = "b2a59b73c045f40d5722b9160d4f909a646d86c9",
 )
 
 maven_jar(
     name = "sshd-sftp",
-    artifact = "org.apache.sshd:sshd-sftp:2.7.0",
-    sha1 = "0c9eff7145e20b338c1dd6aca36ba93ed7c0147c",
+    artifact = "org.apache.sshd:sshd-sftp:2.8.0",
+    sha1 = "d3cd9bc8d335b3ed1a86d2965deb4d202de27442",
 )
 
 maven_jar(
@@ -239,8 +208,8 @@
 
 maven_jar(
     name = "gson",
-    artifact = "com.google.code.gson:gson:2.8.8",
-    sha1 = "431fc3cbc0ff81abdbfde070062741089c3ba874",
+    artifact = "com.google.code.gson:gson:2.8.9",
+    sha1 = "8a432c1d6825781e21a02db2e2c33c5fde2833b9",
 )
 
 JETTY_VER = "10.0.6"
@@ -294,32 +263,32 @@
     src_sha1 = "f35f5525a5d30dc1237b85457d758d578e3ce8d0",
 )
 
-BOUNCYCASTLE_VER = "1.69"
+BOUNCYCASTLE_VER = "1.70"
 
 maven_jar(
     name = "bcpg",
     artifact = "org.bouncycastle:bcpg-jdk15on:" + BOUNCYCASTLE_VER,
-    sha1 = "d99a08c3f651b26e8eb668e941b0bbd2c09ece08",
-    src_sha1 = "de1fc261b44a8eb60583413a31ffc98ce3dce38b",
+    sha1 = "062f72ec06f31a6c31a3f3355fce0384b21126d7",
+    src_sha1 = "9dee73ad926752ee3b421a7dc4531287166ccf36",
 )
 
 maven_jar(
     name = "bcprov",
     artifact = "org.bouncycastle:bcprov-jdk15on:" + BOUNCYCASTLE_VER,
-    sha1 = "91e1628251cf3ca90093ce9d0fe67e5b7dab3850",
-    src_sha1 = "67dc6476845f6b29cb520b5df61db65ae56718e4",
+    sha1 = "4636a0d01f74acaf28082fb62b317f1080118371",
+    src_sha1 = "6245e15dd47e5fc33cff275df61662e0a8e5958f",
 )
 
 maven_jar(
     name = "bcutil",
     artifact = "org.bouncycastle:bcutil-jdk15on:" + BOUNCYCASTLE_VER,
-    sha1 = "c3edf93d346e97f64f041e448e7455c39c7eff64",
-    src_sha1 = "deeb3fbbf373e05e2a20941f9a8ce90e9aeab3d2",
+    sha1 = "54280e7195a7430d7911ded93fc01e07300b9526",
+    src_sha1 = "4af4a6c92b8ea07885b27d8536b81b855497f4eb",
 )
 
 maven_jar(
     name = "bcpkix",
     artifact = "org.bouncycastle:bcpkix-jdk15on:" + BOUNCYCASTLE_VER,
-    sha1 = "45c36fb72fafb0b688c6af795e6cc803f6f79ee5",
-    src_sha1 = "8bc5214401459bd91eea816b516079da38374e90",
+    sha1 = "f81e5af49571a9d5a109a88f239a73ce87055417",
+    src_sha1 = "42f9de53a91b20bc06e88482c57fd97e5a84250d",
 )
diff --git a/lib/BUILD b/lib/BUILD
index 94427f6..6be9e57 100644
--- a/lib/BUILD
+++ b/lib/BUILD
@@ -97,7 +97,7 @@
 java_library(
     name = "jna",
     visibility = [
-         "//org.eclipse.jgit.ssh.apache.agent:__pkg__",
+        "//org.eclipse.jgit.ssh.apache.agent:__pkg__",
     ],
     exports = ["@jna//jar"],
 )
@@ -105,7 +105,7 @@
 java_library(
     name = "jna-platform",
     visibility = [
-         "//org.eclipse.jgit.ssh.apache.agent:__pkg__",
+        "//org.eclipse.jgit.ssh.apache.agent:__pkg__",
     ],
     exports = ["@jna-platform//jar"],
 )
diff --git a/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF
index 9433658..0997570 100644
--- a/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF
@@ -5,13 +5,13 @@
 Automatic-Module-Name: org.eclipse.jgit.ant.test
 Bundle-SymbolicName: org.eclipse.jgit.ant.test
 Bundle-Vendor: %Bundle-Vendor
-Bundle-Version: 6.0.1.qualifier
+Bundle-Version: 6.1.1.qualifier
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-11
 Import-Package: org.apache.tools.ant,
- org.eclipse.jgit.ant.tasks;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.junit;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lib;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.util;version="[6.0.1,6.1.0)",
+ org.eclipse.jgit.ant.tasks;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.junit;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lib;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.util;version="[6.1.1,6.2.0)",
  org.hamcrest.core;version="[1.1.0,3.0.0)",
  org.junit;version="[4.13,5.0.0)"
diff --git a/org.eclipse.jgit.ant.test/pom.xml b/org.eclipse.jgit.ant.test/pom.xml
index 6ad6415..ba473ef 100644
--- a/org.eclipse.jgit.ant.test/pom.xml
+++ b/org.eclipse.jgit.ant.test/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.ant.test</artifactId>
diff --git a/org.eclipse.jgit.ant/META-INF/MANIFEST.MF b/org.eclipse.jgit.ant/META-INF/MANIFEST.MF
index 10bfbd2..6003a31 100644
--- a/org.eclipse.jgit.ant/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ant/META-INF/MANIFEST.MF
@@ -3,13 +3,13 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.ant
 Bundle-SymbolicName: org.eclipse.jgit.ant
-Bundle-Version: 6.0.1.qualifier
+Bundle-Version: 6.1.1.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-11
 Import-Package: org.apache.tools.ant,
-  org.eclipse.jgit.storage.file;version="[6.0.1,6.1.0)"
+  org.eclipse.jgit.storage.file;version="[6.1.1,6.2.0)"
 Bundle-Localization: plugin
 Bundle-Vendor: %Bundle-Vendor
-Export-Package: org.eclipse.jgit.ant;version="6.0.1",
- org.eclipse.jgit.ant.tasks;version="6.0.1";
+Export-Package: org.eclipse.jgit.ant;version="6.1.1",
+ org.eclipse.jgit.ant.tasks;version="6.1.1";
   uses:="org.apache.tools.ant,
    org.apache.tools.ant.types"
diff --git a/org.eclipse.jgit.ant/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.ant/META-INF/SOURCE-MANIFEST.MF
index c3ebfb3..d0dc5c8 100644
--- a/org.eclipse.jgit.ant/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.ant/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.ant - Sources
 Bundle-SymbolicName: org.eclipse.jgit.ant.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 6.0.1.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.ant;version="6.0.1.qualifier";roots="."
+Bundle-Version: 6.1.1.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.ant;version="6.1.1.qualifier";roots="."
diff --git a/org.eclipse.jgit.ant/pom.xml b/org.eclipse.jgit.ant/pom.xml
index 4448f2e..a7f258b 100644
--- a/org.eclipse.jgit.ant/pom.xml
+++ b/org.eclipse.jgit.ant/pom.xml
@@ -15,7 +15,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.ant</artifactId>
diff --git a/org.eclipse.jgit.archive/META-INF/MANIFEST.MF b/org.eclipse.jgit.archive/META-INF/MANIFEST.MF
index 62fe5a5..3fc8df1 100644
--- a/org.eclipse.jgit.archive/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.archive/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.archive
 Bundle-SymbolicName: org.eclipse.jgit.archive
-Bundle-Version: 6.0.1.qualifier
+Bundle-Version: 6.1.1.qualifier
 Bundle-Vendor: %Bundle-Vendor
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-11
@@ -13,17 +13,17 @@
  org.apache.commons.compress.compressors.bzip2;version="[1.4,2.0)",
  org.apache.commons.compress.compressors.gzip;version="[1.4,2.0)",
  org.apache.commons.compress.compressors.xz;version="[1.4,2.0)",
- org.eclipse.jgit.api;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lib;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.nls;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.revwalk;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.util;version="[6.0.1,6.1.0)",
+ org.eclipse.jgit.api;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lib;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.nls;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.revwalk;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.util;version="[6.1.1,6.2.0)",
  org.osgi.framework;version="[1.3.0,2.0.0)"
 Bundle-ActivationPolicy: lazy
 Bundle-Activator: org.eclipse.jgit.archive.FormatActivator
-Export-Package: org.eclipse.jgit.archive;version="6.0.1";
+Export-Package: org.eclipse.jgit.archive;version="6.1.1";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.api,
    org.apache.commons.compress.archivers,
    org.osgi.framework",
- org.eclipse.jgit.archive.internal;version="6.0.1";x-internal:=true
+ org.eclipse.jgit.archive.internal;version="6.1.1";x-internal:=true
diff --git a/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF
index deab2fa..8c7fd07 100644
--- a/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.archive - Sources
 Bundle-SymbolicName: org.eclipse.jgit.archive.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 6.0.1.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.archive;version="6.0.1.qualifier";roots="."
+Bundle-Version: 6.1.1.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.archive;version="6.1.1.qualifier";roots="."
diff --git a/org.eclipse.jgit.archive/pom.xml b/org.eclipse.jgit.archive/pom.xml
index 45f11da..12f3e68 100644
--- a/org.eclipse.jgit.archive/pom.xml
+++ b/org.eclipse.jgit.archive/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.archive</artifactId>
diff --git a/org.eclipse.jgit.benchmarks/pom.xml b/org.eclipse.jgit.benchmarks/pom.xml
index 55c80c6..ac1d6bd 100644
--- a/org.eclipse.jgit.benchmarks/pom.xml
+++ b/org.eclipse.jgit.benchmarks/pom.xml
@@ -14,7 +14,7 @@
   <modelVersion>4.0.0</modelVersion>
 
   <groupId>org.eclipse.jgit</groupId>
-  <version>6.0.1-SNAPSHOT</version>
+  <version>6.1.1-SNAPSHOT</version>
   <artifactId>org.eclipse.jgit.benchmarks</artifactId>
   <packaging>jar</packaging>
 
diff --git a/org.eclipse.jgit.coverage/pom.xml b/org.eclipse.jgit.coverage/pom.xml
index 63ead27..abc37ec 100644
--- a/org.eclipse.jgit.coverage/pom.xml
+++ b/org.eclipse.jgit.coverage/pom.xml
@@ -14,7 +14,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
 
@@ -27,88 +27,88 @@
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit</artifactId>
-      <version>6.0.1-SNAPSHOT</version>
+      <version>6.1.1-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.ant</artifactId>
-      <version>6.0.1-SNAPSHOT</version>
+      <version>6.1.1-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.archive</artifactId>
-      <version>6.0.1-SNAPSHOT</version>
+      <version>6.1.1-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.http.apache</artifactId>
-      <version>6.0.1-SNAPSHOT</version>
+      <version>6.1.1-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.http.server</artifactId>
-      <version>6.0.1-SNAPSHOT</version>
+      <version>6.1.1-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.lfs</artifactId>
-      <version>6.0.1-SNAPSHOT</version>
+      <version>6.1.1-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.lfs.server</artifactId>
-      <version>6.0.1-SNAPSHOT</version>
+      <version>6.1.1-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.pgm</artifactId>
-      <version>6.0.1-SNAPSHOT</version>
+      <version>6.1.1-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.ui</artifactId>
-      <version>6.0.1-SNAPSHOT</version>
+      <version>6.1.1-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.ssh.apache</artifactId>
-      <version>6.0.1-SNAPSHOT</version>
+      <version>6.1.1-SNAPSHOT</version>
     </dependency>
 
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.test</artifactId>
-      <version>6.0.1-SNAPSHOT</version>
+      <version>6.1.1-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.ant.test</artifactId>
-      <version>6.0.1-SNAPSHOT</version>
+      <version>6.1.1-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.http.test</artifactId>
-      <version>6.0.1-SNAPSHOT</version>
+      <version>6.1.1-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.pgm.test</artifactId>
-      <version>6.0.1-SNAPSHOT</version>
+      <version>6.1.1-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.lfs.test</artifactId>
-      <version>6.0.1-SNAPSHOT</version>
+      <version>6.1.1-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.lfs.server.test</artifactId>
-      <version>6.0.1-SNAPSHOT</version>
+      <version>6.1.1-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.ssh.apache.test</artifactId>
-      <version>6.0.1-SNAPSHOT</version>
+      <version>6.1.1-SNAPSHOT</version>
     </dependency>
   </dependencies>
 
diff --git a/org.eclipse.jgit.gpg.bc.test/BUILD b/org.eclipse.jgit.gpg.bc.test/BUILD
index 35b125f..9e5813c 100644
--- a/org.eclipse.jgit.gpg.bc.test/BUILD
+++ b/org.eclipse.jgit.gpg.bc.test/BUILD
@@ -10,8 +10,8 @@
 junit_tests(
     name = "bc",
     srcs = glob(["tst/**/*.java"]),
-    resource_jars = [":tst_rsrc"],
     tags = ["bc"],
+    runtime_deps = [":tst_rsrc"],
     deps = [
         "//lib:bcpg",
         "//lib:bcprov",
diff --git a/org.eclipse.jgit.gpg.bc.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.gpg.bc.test/META-INF/MANIFEST.MF
index d703432..bb9b393 100644
--- a/org.eclipse.jgit.gpg.bc.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.gpg.bc.test/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.gpg.bc.test
 Bundle-SymbolicName: org.eclipse.jgit.gpg.bc.test
-Bundle-Version: 6.0.1.qualifier
+Bundle-Version: 6.1.1.qualifier
 Bundle-Vendor: %Bundle-Vendor
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-11
@@ -12,9 +12,9 @@
  org.bouncycastle.openpgp.operator;version="[1.65.0,2.0.0)",
  org.bouncycastle.openpgp.operator.jcajce;version="[1.65.0,2.0.0)",
  org.bouncycastle.util.encoders;version="[1.65.0,2.0.0)",
- org.eclipse.jgit.gpg.bc.internal;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.gpg.bc.internal.keys;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.util.sha1;version="[6.0.1,6.1.0)",
+ org.eclipse.jgit.gpg.bc.internal;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.gpg.bc.internal.keys;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.util.sha1;version="[6.1.1,6.2.0)",
  org.hamcrest;version="[1.1.0,3.0.0)",
  org.junit;version="[4.13,5.0.0)",
  org.junit.runner;version="[4.13,5.0.0)",
diff --git a/org.eclipse.jgit.gpg.bc.test/pom.xml b/org.eclipse.jgit.gpg.bc.test/pom.xml
index ad955b0..9568c68 100644
--- a/org.eclipse.jgit.gpg.bc.test/pom.xml
+++ b/org.eclipse.jgit.gpg.bc.test/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.gpg.bc.test</artifactId>
diff --git a/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF b/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF
index 74e3305..534568d 100644
--- a/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF
@@ -3,10 +3,10 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.gpg.bc
 Bundle-SymbolicName: org.eclipse.jgit.gpg.bc;singleton:=true
-Fragment-Host: org.eclipse.jgit;bundle-version="[6.0.1,6.1.0)"
+Fragment-Host: org.eclipse.jgit;bundle-version="[6.1.1,6.2.0)"
 Bundle-Vendor: %Bundle-Vendor
 Bundle-Localization: plugin
-Bundle-Version: 6.0.1.qualifier
+Bundle-Version: 6.1.1.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-11
 Import-Package: org.bouncycastle.asn1;version="[1.69.0,2.0.0)",
  org.bouncycastle.asn1.cryptlib;version="[1.69.0,2.0.0)",
@@ -29,9 +29,9 @@
  org.bouncycastle.util;version="[1.69.0,2.0.0)",
  org.bouncycastle.util.encoders;version="[1.69.0,2.0.0)",
  org.bouncycastle.util.io;version="[1.69.0,2.0.0)",
- org.eclipse.jgit.annotations;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.api.errors;version="[6.0.1,6.1.0)",
+ org.eclipse.jgit.annotations;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.api.errors;version="[6.1.1,6.2.0)",
  org.slf4j;version="[1.7.0,2.0.0)"
-Export-Package: org.eclipse.jgit.gpg.bc;version="6.0.1",
- org.eclipse.jgit.gpg.bc.internal;version="6.0.1";x-friends:="org.eclipse.jgit.gpg.bc.test",
- org.eclipse.jgit.gpg.bc.internal.keys;version="6.0.1";x-friends:="org.eclipse.jgit.gpg.bc.test"
+Export-Package: org.eclipse.jgit.gpg.bc;version="6.1.1",
+ org.eclipse.jgit.gpg.bc.internal;version="6.1.1";x-friends:="org.eclipse.jgit.gpg.bc.test",
+ org.eclipse.jgit.gpg.bc.internal.keys;version="6.1.1";x-friends:="org.eclipse.jgit.gpg.bc.test"
diff --git a/org.eclipse.jgit.gpg.bc/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.gpg.bc/META-INF/SOURCE-MANIFEST.MF
index de4962b..20e2d01 100644
--- a/org.eclipse.jgit.gpg.bc/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.gpg.bc/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.gpg.bc - Sources
 Bundle-SymbolicName: org.eclipse.jgit.gpg.bc.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 6.0.1.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.gpg.bc;version="6.0.1.qualifier";roots="."
+Bundle-Version: 6.1.1.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.gpg.bc;version="6.1.1.qualifier";roots="."
diff --git a/org.eclipse.jgit.gpg.bc/pom.xml b/org.eclipse.jgit.gpg.bc/pom.xml
index 4bf82c6..138f4e8 100644
--- a/org.eclipse.jgit.gpg.bc/pom.xml
+++ b/org.eclipse.jgit.gpg.bc/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.gpg.bc</artifactId>
diff --git a/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF
index c51a87e..afcaa78 100644
--- a/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.http.apache
 Bundle-SymbolicName: org.eclipse.jgit.http.apache
-Bundle-Version: 6.0.1.qualifier
+Bundle-Version: 6.1.1.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-11
 Bundle-Localization: plugin
 Bundle-Vendor: %Bundle-Vendor
@@ -25,11 +25,11 @@
  org.apache.http.impl.conn;version="[4.4.0,5.0.0)",
  org.apache.http.params;version="[4.3.0,5.0.0)",
  org.apache.http.ssl;version="[4.3.0,5.0.0)",
- org.eclipse.jgit.annotations;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.nls;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport.http;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.util;version="[6.0.1,6.1.0)"
-Export-Package: org.eclipse.jgit.transport.http.apache;version="6.0.1";
+ org.eclipse.jgit.annotations;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.nls;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport.http;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.util;version="[6.1.1,6.2.0)"
+Export-Package: org.eclipse.jgit.transport.http.apache;version="6.1.1";
   uses:="org.apache.http.client,
    org.eclipse.jgit.transport.http,
    org.apache.http.entity,
diff --git a/org.eclipse.jgit.http.apache/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.http.apache/META-INF/SOURCE-MANIFEST.MF
index 9d4f5f2..7b8e149 100644
--- a/org.eclipse.jgit.http.apache/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.http.apache/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.http.apache - Sources
 Bundle-SymbolicName: org.eclipse.jgit.http.apache.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 6.0.1.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.http.apache;version="6.0.1.qualifier";roots="."
+Bundle-Version: 6.1.1.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.http.apache;version="6.1.1.qualifier";roots="."
diff --git a/org.eclipse.jgit.http.apache/pom.xml b/org.eclipse.jgit.http.apache/pom.xml
index 6e5f260..bd35589 100644
--- a/org.eclipse.jgit.http.apache/pom.xml
+++ b/org.eclipse.jgit.http.apache/pom.xml
@@ -15,7 +15,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.http.apache</artifactId>
diff --git a/org.eclipse.jgit.http.server/.settings/.api_filters b/org.eclipse.jgit.http.server/.settings/.api_filters
new file mode 100644
index 0000000..2c32c98
--- /dev/null
+++ b/org.eclipse.jgit.http.server/.settings/.api_filters
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<component id="org.eclipse.jgit.http.server" version="2">
+    <resource path="src/org/eclipse/jgit/http/server/UploadPackErrorHandler.java" type="org.eclipse.jgit.http.server.UploadPackErrorHandler">
+        <filter id="1210056707">
+            <message_arguments>
+                <message_argument value="6.1.1"/>
+                <message_argument value="statusCodeForThrowable(Throwable)"/>
+            </message_arguments>
+        </filter>
+    </resource>
+</component>
diff --git a/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF
index 510c439..4bf66a2 100644
--- a/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF
@@ -3,13 +3,13 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.http.server
 Bundle-SymbolicName: org.eclipse.jgit.http.server
-Bundle-Version: 6.0.1.qualifier
+Bundle-Version: 6.1.1.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %Bundle-Vendor
-Export-Package: org.eclipse.jgit.http.server;version="6.0.1",
- org.eclipse.jgit.http.server.glue;version="6.0.1";
+Export-Package: org.eclipse.jgit.http.server;version="6.1.1",
+ org.eclipse.jgit.http.server.glue;version="6.1.1";
   uses:="javax.servlet,javax.servlet.http",
- org.eclipse.jgit.http.server.resolver;version="6.0.1";
+ org.eclipse.jgit.http.server.resolver;version="6.1.1";
   uses:="org.eclipse.jgit.transport.resolver,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.transport,
@@ -18,14 +18,14 @@
 Bundle-RequiredExecutionEnvironment: JavaSE-11
 Import-Package: javax.servlet;version="[2.5.0,5.0.0)",
  javax.servlet.http;version="[2.5.0,5.0.0)",
- org.eclipse.jgit.annotations;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.errors;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.transport.parser;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lib;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.nls;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.revwalk;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport.resolver;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.util;version="[6.0.1,6.1.0)"
+ org.eclipse.jgit.annotations;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.errors;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.storage.file;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.transport.parser;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lib;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.nls;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.revwalk;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport.resolver;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.util;version="[6.1.1,6.2.0)"
diff --git a/org.eclipse.jgit.http.server/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.http.server/META-INF/SOURCE-MANIFEST.MF
index 7453cb8..2b47055 100644
--- a/org.eclipse.jgit.http.server/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.http.server/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.http.server - Sources
 Bundle-SymbolicName: org.eclipse.jgit.http.server.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 6.0.1.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.http.server;version="6.0.1.qualifier";roots="."
+Bundle-Version: 6.1.1.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.http.server;version="6.1.1.qualifier";roots="."
diff --git a/org.eclipse.jgit.http.server/pom.xml b/org.eclipse.jgit.http.server/pom.xml
index b01c791..6a788e2 100644
--- a/org.eclipse.jgit.http.server/pom.xml
+++ b/org.eclipse.jgit.http.server/pom.xml
@@ -19,7 +19,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.http.server</artifactId>
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitSmartHttpTools.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitSmartHttpTools.java
index 012f9a3..f1155dc 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitSmartHttpTools.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitSmartHttpTools.java
@@ -158,11 +158,11 @@
 		}
 
 		if (isInfoRefs(req)) {
-			sendInfoRefsError(req, res, textForGit);
+			sendInfoRefsError(req, res, textForGit, httpStatus);
 		} else if (isUploadPack(req)) {
-			sendUploadPackError(req, res, textForGit);
+			sendUploadPackError(req, res, textForGit, httpStatus);
 		} else if (isReceivePack(req)) {
-			sendReceivePackError(req, res, textForGit);
+			sendReceivePackError(req, res, textForGit, httpStatus);
 		} else {
 			if (httpStatus < 400)
 				ServletUtils.consumeRequestBody(req);
@@ -171,29 +171,32 @@
 	}
 
 	private static void sendInfoRefsError(HttpServletRequest req,
-			HttpServletResponse res, String textForGit) throws IOException {
+			HttpServletResponse res, String textForGit, int httpStatus)
+			throws IOException {
 		ByteArrayOutputStream buf = new ByteArrayOutputStream(128);
 		PacketLineOut pck = new PacketLineOut(buf);
 		String svc = req.getParameter("service");
 		pck.writeString("# service=" + svc + "\n");
 		pck.end();
 		pck.writeString("ERR " + textForGit);
-		send(req, res, infoRefsResultType(svc), buf.toByteArray());
+		send(req, res, infoRefsResultType(svc), buf.toByteArray(), httpStatus);
 	}
 
 	private static void sendUploadPackError(HttpServletRequest req,
-			HttpServletResponse res, String textForGit) throws IOException {
+			HttpServletResponse res, String textForGit, int httpStatus)
+			throws IOException {
 		// Do not use sideband. Sideband is acceptable only while packfile is
 		// being sent. Other places, like acknowledgement section, do not
 		// support sideband. Use an error packet.
 		ByteArrayOutputStream buf = new ByteArrayOutputStream(128);
 		PacketLineOut pckOut = new PacketLineOut(buf);
 		writePacket(pckOut, textForGit);
-		send(req, res, UPLOAD_PACK_RESULT_TYPE, buf.toByteArray());
+		send(req, res, UPLOAD_PACK_RESULT_TYPE, buf.toByteArray(), httpStatus);
 	}
 
 	private static void sendReceivePackError(HttpServletRequest req,
-			HttpServletResponse res, String textForGit) throws IOException {
+			HttpServletResponse res, String textForGit, int httpStatus)
+			throws IOException {
 		ByteArrayOutputStream buf = new ByteArrayOutputStream(128);
 		PacketLineOut pckOut = new PacketLineOut(buf);
 
@@ -212,7 +215,7 @@
 			writeSideBand(buf, textForGit);
 		else
 			writePacket(pckOut, textForGit);
-		send(req, res, RECEIVE_PACK_RESULT_TYPE, buf.toByteArray());
+		send(req, res, RECEIVE_PACK_RESULT_TYPE, buf.toByteArray(), httpStatus);
 	}
 
 	private static boolean isReceivePackSideBand(HttpServletRequest req) {
@@ -246,9 +249,9 @@
 	}
 
 	private static void send(HttpServletRequest req, HttpServletResponse res,
-			String type, byte[] buf) throws IOException {
+			String type, byte[] buf, int httpStatus) throws IOException {
 		ServletUtils.consumeRequestBody(req);
-		res.setStatus(HttpServletResponse.SC_OK);
+		res.setStatus(httpStatus);
 		res.setContentType(type);
 		res.setContentLength(buf.length);
 		try (OutputStream os = res.getOutputStream()) {
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackErrorHandler.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackErrorHandler.java
index 03be087..2aadbbc 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackErrorHandler.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackErrorHandler.java
@@ -9,13 +9,19 @@
  */
 package org.eclipse.jgit.http.server;
 
+import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
+import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+import static javax.servlet.http.HttpServletResponse.SC_OK;
+
 import java.io.IOException;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.eclipse.jgit.errors.PackProtocolException;
 import org.eclipse.jgit.transport.ServiceMayNotContinueException;
 import org.eclipse.jgit.transport.UploadPack;
+import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
 
 /**
  * Handle git-upload-pack errors.
@@ -35,6 +41,24 @@
  */
 public interface UploadPackErrorHandler {
 	/**
+	 * Maps a thrown git related Exception to an appropriate HTTP status code.
+	 *
+	 * @param error
+	 *            The thrown Exception.
+	 * @return the HTTP status code as an int
+	 * @since 6.1.1
+	 */
+	public static int statusCodeForThrowable(Throwable error) {
+		if (error instanceof ServiceNotEnabledException) {
+			return SC_FORBIDDEN;
+		}
+		if (error instanceof PackProtocolException) {
+			// Internal git errors are not errors from an HTTP standpoint.
+			return SC_OK;
+		}
+		return SC_INTERNAL_SERVER_ERROR;
+	}
+	/**
 	 * @param req
 	 *            The HTTP request
 	 * @param rsp
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java
index c4f845e..b0a07f1 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java
@@ -11,7 +11,6 @@
 package org.eclipse.jgit.http.server;
 
 import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
-import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
 import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
 import static javax.servlet.http.HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE;
 import static org.eclipse.jgit.http.server.GitSmartHttpTools.UPLOAD_PACK;
@@ -22,6 +21,7 @@
 import static org.eclipse.jgit.http.server.ServletUtils.consumeRequestBody;
 import static org.eclipse.jgit.http.server.ServletUtils.getInputStream;
 import static org.eclipse.jgit.http.server.ServletUtils.getRepository;
+import static org.eclipse.jgit.http.server.UploadPackErrorHandler.statusCodeForThrowable;
 import static org.eclipse.jgit.util.HttpSupport.HDR_USER_AGENT;
 
 import java.io.IOException;
@@ -169,6 +169,7 @@
 
 		UploadPackRunnable r = () -> {
 			UploadPack up = (UploadPack) req.getAttribute(ATTRIBUTE_HANDLER);
+			// to be explicitly closed by caller
 			@SuppressWarnings("resource")
 			SmartOutputStream out = new SmartOutputStream(req, rsp, false) {
 				@Override
@@ -217,9 +218,12 @@
 			log(up.getRepository(), e);
 			if (!rsp.isCommitted()) {
 				rsp.reset();
-				String msg = e instanceof PackProtocolException ? e.getMessage()
-						: null;
-				sendError(req, rsp, SC_INTERNAL_SERVER_ERROR, msg);
+				String msg = null;
+				if (e instanceof PackProtocolException
+						|| e instanceof ServiceNotEnabledException) {
+					msg = e.getMessage();
+				}
+				sendError(req, rsp, statusCodeForThrowable(e), msg);
 			}
 		}
 	}
diff --git a/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF
index 3a6517c..4150bdb 100644
--- a/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.http.test
 Bundle-SymbolicName: org.eclipse.jgit.http.test
-Bundle-Version: 6.0.1.qualifier
+Bundle-Version: 6.1.1.qualifier
 Bundle-Vendor: %Bundle-Vendor
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-11
@@ -26,26 +26,26 @@
  org.eclipse.jetty.util.log;version="[10.0.0,11.0.0)",
  org.eclipse.jetty.util.security;version="[10.0.0,11.0.0)",
  org.eclipse.jetty.util.thread;version="[10.0.0,11.0.0)",
- org.eclipse.jgit.api;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.errors;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.http.server;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.http.server.glue;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.http.server.resolver;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.storage.reftable;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.junit;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.junit.http;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lib;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.nls;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.revwalk;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.storage.file;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport.http;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport.http.apache;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport.resolver;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.util;version="[6.0.1,6.1.0)",
+ org.eclipse.jgit.api;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.errors;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.http.server;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.http.server.glue;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.http.server.resolver;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.storage.file;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.storage.reftable;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.junit;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.junit.http;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lib;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.nls;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.revwalk;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.storage.file;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport.http;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport.http.apache;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport.resolver;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.util;version="[6.1.1,6.2.0)",
  org.hamcrest;version="[1.1.0,3.0.0)",
  org.hamcrest.core;version="[1.1.0,3.0.0)",
  org.junit;version="[4.13,5.0.0)",
diff --git a/org.eclipse.jgit.http.test/pom.xml b/org.eclipse.jgit.http.test/pom.xml
index 7694c44..336f43b 100644
--- a/org.eclipse.jgit.http.test/pom.xml
+++ b/org.eclipse.jgit.http.test/pom.xml
@@ -18,7 +18,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.http.test</artifactId>
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java
index db9f2ae..20de256 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java
@@ -97,6 +97,7 @@
 		server.setUp();
 
 		remoteRepository = src.getRepository();
+		addRepoToClose(remoteRepository);
 		remoteURI = toURIish(app, srcName);
 
 		StoredConfig cfg = remoteRepository.getConfig();
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java
index df093c1..3438c52 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java
@@ -23,6 +23,7 @@
 import java.io.OutputStream;
 import java.net.URI;
 import java.net.URL;
+import java.text.MessageFormat;
 import java.util.List;
 
 import javax.servlet.http.HttpServletRequest;
@@ -308,7 +309,10 @@
 				fail("connection opened even though service disabled");
 			} catch (TransportException err) {
 				String exp = smartAuthNoneURI + ": "
-						+ JGitText.get().serviceNotEnabledNoName;
+						+ MessageFormat.format(
+								JGitText.get().serviceNotPermitted,
+								smartAuthNoneURI.toString() + "/",
+								"git-upload-pack");
 				assertEquals(exp, err.getMessage());
 			}
 		}
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/MeasurePackSizeTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/MeasurePackSizeTest.java
index 9ffa19e..000eecd 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/MeasurePackSizeTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/MeasurePackSizeTest.java
@@ -90,6 +90,7 @@
 		server.setUp();
 
 		remoteRepository = src.getRepository();
+		addRepoToClose(remoteRepository);
 		remoteURI = toURIish(app, srcName);
 
 		StoredConfig cfg = remoteRepository.getConfig();
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
index 887e970..b9b10b4 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
@@ -57,7 +57,7 @@
 import org.eclipse.jetty.servlet.ServletHolder;
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.TransportConfigCallback;
-import org.eclipse.jgit.errors.RemoteRepositoryException;
+import org.eclipse.jgit.errors.NoRemoteRepositoryException;
 import org.eclipse.jgit.errors.TransportException;
 import org.eclipse.jgit.errors.UnsupportedCredentialItem;
 import org.eclipse.jgit.http.server.GitServlet;
@@ -496,8 +496,9 @@
 			try {
 				t.openFetch();
 				fail("fetch connection opened");
-			} catch (RemoteRepositoryException notFound) {
-				assertEquals(uri + ": Git repository not found",
+			} catch (NoRemoteRepositoryException notFound) {
+				assertEquals(uri + ": " + uri
+						+ "/info/refs?service=git-upload-pack not found: Not Found",
 						notFound.getMessage());
 			}
 		}
@@ -510,7 +511,7 @@
 		assertEquals(join(uri, "info/refs"), info.getPath());
 		assertEquals(1, info.getParameters().size());
 		assertEquals("git-upload-pack", info.getParameter("service"));
-		assertEquals(200, info.getStatus());
+		assertEquals(404, info.getStatus());
 		assertEquals("application/x-git-upload-pack-advertisement",
 				info.getResponseHeader(HDR_CONTENT_TYPE));
 	}
@@ -538,6 +539,7 @@
 			assertTrue(e.getMessage().contains(
 					"want " + unreachableCommit.name() + " not valid"));
 		}
+		assertLastRequestStatusCode(200);
 	}
 
 	@Test
@@ -560,6 +562,7 @@
 			assertTrue(
 					e.getMessage().contains("want " + A.name() + " not valid"));
 		}
+		assertLastRequestStatusCode(200);
 	}
 
 	@Test
@@ -916,6 +919,7 @@
 		} catch (TransportException e) {
 			assertTrue(e.getMessage().contains("301"));
 		}
+		assertLastRequestStatusCode(301);
 	}
 
 	@Test
@@ -934,6 +938,7 @@
 			assertTrue(
 					e.getMessage().contains("http.followRedirects is false"));
 		}
+		assertLastRequestStatusCode(301);
 	}
 
 	private void assertFetchRequests(List<AccessEvent> requests, int index) {
@@ -1607,6 +1612,7 @@
 			assertTrue(err.getMessage()
 					.contains("want " + id.name() + " not valid"));
 		}
+		assertLastRequestStatusCode(200);
 	}
 
 	@Test
@@ -1650,7 +1656,7 @@
 				fail("Successfully served ref with value " + c.getRef(master));
 			} catch (TransportException err) {
 				assertTrue("Unexpected exception message " + err.getMessage(),
-						err.getMessage().contains("Internal server error"));
+						err.getMessage().contains("Server Error"));
 			}
 		} finally {
 			noRefServer.tearDown();
@@ -1821,6 +1827,11 @@
 				.getResponseHeader(HDR_CONTENT_TYPE));
 	}
 
+	private void assertLastRequestStatusCode(int statusCode) {
+		List<AccessEvent> requests = getRequests();
+		assertEquals(statusCode, requests.get(requests.size() - 1).getStatus());
+	}
+
 	private void enableReceivePack() throws IOException {
 		final StoredConfig cfg = remoteRepository.getConfig();
 		cfg.setBoolean("http", null, "receivepack", true);
diff --git a/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF
index c21f835..d035072 100644
--- a/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.junit.http
 Bundle-SymbolicName: org.eclipse.jgit.junit.http
-Bundle-Version: 6.0.1.qualifier
+Bundle-Version: 6.1.1.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %Bundle-Vendor
 Bundle-ActivationPolicy: lazy
@@ -21,17 +21,17 @@
  org.eclipse.jetty.util.log;version="[10.0.0,11.0.0)",
  org.eclipse.jetty.util.security;version="[10.0.0,11.0.0)",
  org.eclipse.jetty.util.ssl;version="[10.0.0,11.0.0)",
- org.eclipse.jgit.errors;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.http.server;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.junit;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lib;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.revwalk;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport.resolver;version="[6.0.1,6.1.0)",
+ org.eclipse.jgit.errors;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.http.server;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.storage.file;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.junit;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lib;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.revwalk;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport.resolver;version="[6.1.1,6.2.0)",
  org.junit;version="[4.13,5.0.0)",
  org.slf4j.helpers;version="[1.7.0,2.0.0)"
-Export-Package: org.eclipse.jgit.junit.http;version="6.0.1";
+Export-Package: org.eclipse.jgit.junit.http;version="6.1.1";
   uses:="org.eclipse.jgit.transport,
    org.eclipse.jgit.junit,
    javax.servlet.http,
diff --git a/org.eclipse.jgit.junit.http/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.junit.http/META-INF/SOURCE-MANIFEST.MF
index 98fde06..19142c5 100644
--- a/org.eclipse.jgit.junit.http/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.junit.http/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.junit.http - Sources
 Bundle-SymbolicName: org.eclipse.jgit.junit.http.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 6.0.1.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.junit.http;version="6.0.1.qualifier";roots="."
+Bundle-Version: 6.1.1.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.junit.http;version="6.1.1.qualifier";roots="."
diff --git a/org.eclipse.jgit.junit.http/pom.xml b/org.eclipse.jgit.junit.http/pom.xml
index b66991f..d883e03 100644
--- a/org.eclipse.jgit.junit.http/pom.xml
+++ b/org.eclipse.jgit.junit.http/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.junit.http</artifactId>
diff --git a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/HttpTestCase.java b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/HttpTestCase.java
index 3e7c84f..877b918 100644
--- a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/HttpTestCase.java
+++ b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/HttpTestCase.java
@@ -22,6 +22,7 @@
 import java.util.Set;
 
 import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jgit.internal.storage.file.FileRepository;
 import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.AnyObjectId;
@@ -80,7 +81,9 @@
 	 */
 	protected TestRepository<Repository> createTestRepository()
 			throws IOException {
-		return new TestRepository<>(createBareRepository());
+		final FileRepository repository = createBareRepository();
+		addRepoToClose(repository);
+		return new TestRepository<>(repository);
 	}
 
 	/**
diff --git a/org.eclipse.jgit.junit.ssh/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit.ssh/META-INF/MANIFEST.MF
index 8d629ed..60858d8 100644
--- a/org.eclipse.jgit.junit.ssh/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.junit.ssh/META-INF/MANIFEST.MF
@@ -3,46 +3,46 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.junit.ssh
 Bundle-SymbolicName: org.eclipse.jgit.junit.ssh
-Bundle-Version: 6.0.1.qualifier
+Bundle-Version: 6.1.1.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %Bundle-Vendor
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-11
-Import-Package: org.apache.sshd.common;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.config.keys;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.file.virtualfs;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.helpers;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.io;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.kex;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.keyprovider;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.session;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.signature;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.util.buffer;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.util.logging;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.util.security;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.util.threads;version="[2.7.0,2.8.0)",
- org.apache.sshd.core;version="[2.7.0,2.8.0)",
- org.apache.sshd.server;version="[2.7.0,2.8.0)",
- org.apache.sshd.server.auth;version="[2.7.0,2.8.0)",
- org.apache.sshd.server.auth.gss;version="[2.7.0,2.8.0)",
- org.apache.sshd.server.auth.keyboard;version="[2.7.0,2.8.0)",
- org.apache.sshd.server.auth.password;version="[2.7.0,2.8.0)",
- org.apache.sshd.server.command;version="[2.7.0,2.8.0)",
- org.apache.sshd.server.session;version="[2.7.0,2.8.0)",
- org.apache.sshd.server.shell;version="[2.7.0,2.8.0)",
- org.apache.sshd.server.subsystem;version="[2.7.0,2.8.0)",
- org.apache.sshd.sftp;version="[2.7.0,2.8.0)",
- org.apache.sshd.sftp.server;version="[2.7.0,2.8.0)",
- org.eclipse.jgit.annotations;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.api;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.api.errors;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.errors;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.junit;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lib;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.revwalk;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.util;version="[6.0.1,6.1.0)",
+Import-Package: org.apache.sshd.common;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.config.keys;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.file.virtualfs;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.helpers;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.io;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.kex;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.keyprovider;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.session;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.signature;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.util.buffer;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.util.logging;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.util.security;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.util.threads;version="[2.8.0,2.9.0)",
+ org.apache.sshd.core;version="[2.8.0,2.9.0)",
+ org.apache.sshd.server;version="[2.8.0,2.9.0)",
+ org.apache.sshd.server.auth;version="[2.8.0,2.9.0)",
+ org.apache.sshd.server.auth.gss;version="[2.8.0,2.9.0)",
+ org.apache.sshd.server.auth.keyboard;version="[2.8.0,2.9.0)",
+ org.apache.sshd.server.auth.password;version="[2.8.0,2.9.0)",
+ org.apache.sshd.server.command;version="[2.8.0,2.9.0)",
+ org.apache.sshd.server.session;version="[2.8.0,2.9.0)",
+ org.apache.sshd.server.shell;version="[2.8.0,2.9.0)",
+ org.apache.sshd.server.subsystem;version="[2.8.0,2.9.0)",
+ org.apache.sshd.sftp;version="[2.8.0,2.9.0)",
+ org.apache.sshd.sftp.server;version="[2.8.0,2.9.0)",
+ org.eclipse.jgit.annotations;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.api;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.api.errors;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.errors;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.junit;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lib;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.revwalk;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.util;version="[6.1.1,6.2.0)",
  org.junit;version="[4.13,5.0.0)",
  org.junit.experimental.theories;version="[4.13,5.0.0)",
  org.slf4j;version="[1.7.0,2.0.0)"
-Export-Package: org.eclipse.jgit.junit.ssh;version="6.0.1"
+Export-Package: org.eclipse.jgit.junit.ssh;version="6.1.1"
diff --git a/org.eclipse.jgit.junit.ssh/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.junit.ssh/META-INF/SOURCE-MANIFEST.MF
index 7132b5b..41246bc 100644
--- a/org.eclipse.jgit.junit.ssh/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.junit.ssh/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.junit.ssh - Sources
 Bundle-SymbolicName: org.eclipse.jgit.junit.ssh.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 6.0.1.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.junit.ssh;version="6.0.1.qualifier";roots="."
+Bundle-Version: 6.1.1.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.junit.ssh;version="6.1.1.qualifier";roots="."
diff --git a/org.eclipse.jgit.junit.ssh/pom.xml b/org.eclipse.jgit.junit.ssh/pom.xml
index 76bdca1..07d0218 100644
--- a/org.eclipse.jgit.junit.ssh/pom.xml
+++ b/org.eclipse.jgit.junit.ssh/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.junit.ssh</artifactId>
@@ -55,6 +55,16 @@
       <groupId>org.apache.sshd</groupId>
       <artifactId>sshd-sftp</artifactId>
       <version>${apache-sshd-version}</version>
+      <exclusions>
+          <exclusion>
+              <groupId>org.apache.sshd</groupId>
+              <artifactId>sshd-core</artifactId>
+          </exclusion>
+          <exclusion>
+              <groupId>org.apache.sshd</groupId>
+              <artifactId>sshd-common</artifactId>
+          </exclusion>
+      </exclusions>
     </dependency>
 
     <dependency>
diff --git a/org.eclipse.jgit.junit/.settings/.api_filters b/org.eclipse.jgit.junit/.settings/.api_filters
new file mode 100644
index 0000000..a17a72f
--- /dev/null
+++ b/org.eclipse.jgit.junit/.settings/.api_filters
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<component id="org.eclipse.jgit.junit" version="2">
+    <resource path="src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java" type="org.eclipse.jgit.junit.LocalDiskRepositoryTestCase">
+        <filter id="336658481">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.junit.LocalDiskRepositoryTestCase"/>
+                <message_argument value="currentTest"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="6.0.1"/>
+                <message_argument value="currentTest"/>
+            </message_arguments>
+        </filter>
+    </resource>
+</component>
diff --git a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
index 1f021da..7a3b200 100644
--- a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
@@ -3,35 +3,35 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.junit
 Bundle-SymbolicName: org.eclipse.jgit.junit
-Bundle-Version: 6.0.1.qualifier
+Bundle-Version: 6.1.1.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %Bundle-Vendor
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-11
-Import-Package: org.eclipse.jgit.annotations;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.api;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.api.errors;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.dircache;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.errors;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.storage.pack;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lib;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.merge;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.revwalk;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.storage.file;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport;version="6.0.1",
- org.eclipse.jgit.treewalk;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.treewalk.filter;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.util;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.util.io;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.util.time;version="[6.0.1,6.1.0)",
+Import-Package: org.eclipse.jgit.annotations;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.api;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.api.errors;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.dircache;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.errors;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.storage.file;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lib;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.merge;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.revwalk;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.storage.file;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport;version="6.1.1",
+ org.eclipse.jgit.treewalk;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.treewalk.filter;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.util;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.util.io;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.util.time;version="[6.1.1,6.2.0)",
  org.junit;version="[4.13,5.0.0)",
  org.junit.rules;version="[4.13,5.0.0)",
  org.junit.runner;version="[4.13,5.0.0)",
  org.junit.runners;version="[4.13,5.0.0)",
  org.junit.runners.model;version="[4.13,5.0.0)",
  org.slf4j;version="[1.7.0,2.0.0)"
-Export-Package: org.eclipse.jgit.junit;version="6.0.1";
+Export-Package: org.eclipse.jgit.junit;version="6.1.1";
   uses:="org.eclipse.jgit.dircache,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
@@ -44,4 +44,4 @@
    org.junit.runners.model,
    org.junit.runner,
    org.eclipse.jgit.util.time",
- org.eclipse.jgit.junit.time;version="6.0.1";uses:="org.eclipse.jgit.util.time"
+ org.eclipse.jgit.junit.time;version="6.1.1";uses:="org.eclipse.jgit.util.time"
diff --git a/org.eclipse.jgit.junit/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.junit/META-INF/SOURCE-MANIFEST.MF
index 156e59a..25876c7 100644
--- a/org.eclipse.jgit.junit/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.junit/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.junit - Sources
 Bundle-SymbolicName: org.eclipse.jgit.junit.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 6.0.1.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.junit;version="6.0.1.qualifier";roots="."
+Bundle-Version: 6.1.1.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.junit;version="6.1.1.qualifier";roots="."
diff --git a/org.eclipse.jgit.junit/pom.xml b/org.eclipse.jgit.junit/pom.xml
index ef2b946..2c260e2 100644
--- a/org.eclipse.jgit.junit/pom.xml
+++ b/org.eclipse.jgit.junit/pom.xml
@@ -19,7 +19,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.junit</artifactId>
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java
index 494d2f3..59662ce 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java
@@ -45,6 +45,8 @@
 import org.eclipse.jgit.util.SystemReader;
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TestName;
 
 /**
  * JUnit TestCase with specialized support for temporary local repository.
@@ -84,18 +86,35 @@
 	private File tmp;
 
 	/**
+	 * The current test name.
+	 *
+	 * @since 6.0.1
+	 */
+	@Rule
+	public TestName currentTest = new TestName();
+
+	private String getTestName() {
+		String name = currentTest.getMethodName();
+		name = name.replaceAll("[^a-zA-Z0-9]", "_");
+		name = name.replaceAll("__+", "_");
+		if (name.startsWith("_")) {
+			name = name.substring(1);
+		}
+		return name;
+	}
+
+	/**
 	 * Setup test
 	 *
 	 * @throws Exception
 	 */
 	@Before
 	public void setUp() throws Exception {
-		tmp = File.createTempFile("jgit_test_", "_tmp");
+		tmp = File.createTempFile("jgit_" + getTestName() + '_', "_tmp");
 		CleanupThread.deleteOnShutdown(tmp);
 		if (!tmp.delete() || !tmp.mkdir()) {
 			throw new IOException("Cannot create " + tmp);
 		}
-
 		mockSystemReader = new MockSystemReader();
 		SystemReader.setInstance(mockSystemReader);
 
diff --git a/org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF
index b2a82c2..cbb8135 100644
--- a/org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.lfs.server.test
 Bundle-SymbolicName: org.eclipse.jgit.lfs.server.test
-Bundle-Version: 6.0.1.qualifier
+Bundle-Version: 6.1.1.qualifier
 Bundle-Vendor: %Bundle-Vendor
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-11
@@ -26,24 +26,24 @@
  org.eclipse.jetty.util.log;version="[10.0.0,11.0.0)",
  org.eclipse.jetty.util.security;version="[10.0.0,11.0.0)",
  org.eclipse.jetty.util.thread;version="[10.0.0,11.0.0)",
- org.eclipse.jgit.api;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.api.errors;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.junit;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.junit.http;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lfs;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lfs.errors;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lfs.lib;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lfs.server;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lfs.server.fs;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lfs.test;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lib;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.revwalk;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.storage.file;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.treewalk;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.treewalk.filter;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.util;version="[6.0.1,6.1.0)",
+ org.eclipse.jgit.api;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.api.errors;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.storage.file;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.junit;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.junit.http;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lfs;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lfs.errors;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lfs.lib;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lfs.server;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lfs.server.fs;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lfs.test;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lib;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.revwalk;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.storage.file;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.treewalk;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.treewalk.filter;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.util;version="[6.1.1,6.2.0)",
  org.hamcrest.core;version="[1.1.0,3.0.0)",
  org.junit;version="[4.13,5.0.0)",
  org.junit.rules;version="[4.13,5.0.0)",
diff --git a/org.eclipse.jgit.lfs.server.test/pom.xml b/org.eclipse.jgit.lfs.server.test/pom.xml
index ef8751e..1bab620 100644
--- a/org.eclipse.jgit.lfs.server.test/pom.xml
+++ b/org.eclipse.jgit.lfs.server.test/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.lfs.server.test</artifactId>
diff --git a/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/PushTest.java b/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/PushTest.java
index 70c0463..06708b3 100644
--- a/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/PushTest.java
+++ b/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/PushTest.java
@@ -11,6 +11,7 @@
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import java.io.InputStream;
 import java.nio.file.Files;
@@ -29,6 +30,7 @@
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
+import org.eclipse.jgit.transport.RefSpec;
 import org.eclipse.jgit.transport.URIish;
 import org.eclipse.jgit.treewalk.TreeWalk;
 import org.eclipse.jgit.treewalk.filter.PathFilter;
@@ -128,4 +130,18 @@
 				server.getRequests().toString());
 	}
 
+	@Test
+	public void testDeleteBranch() throws Exception {
+		String branch = "new-branch";
+		git.branchCreate().setName(branch).call();
+
+		String destRef = Constants.R_HEADS + branch;
+		git.push().setRefSpecs(new RefSpec().setSource(branch).setDestination(destRef)).call();
+
+		// Should not fail on push.
+		git.branchDelete().setBranchNames(branch).setForce(true).call();
+		git.push().setRefSpecs(new RefSpec().setSource(null).setDestination(destRef)).call();
+
+		assertTrue(server.getRequests().isEmpty());
+	}
 }
diff --git a/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF
index 3a4448b..cd8bde3 100644
--- a/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF
@@ -3,19 +3,19 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.lfs.server
 Bundle-SymbolicName: org.eclipse.jgit.lfs.server
-Bundle-Version: 6.0.1.qualifier
+Bundle-Version: 6.1.1.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %Bundle-Vendor
-Export-Package: org.eclipse.jgit.lfs.server;version="6.0.1";
+Export-Package: org.eclipse.jgit.lfs.server;version="6.1.1";
   uses:="javax.servlet.http,
    org.eclipse.jgit.lfs.lib",
- org.eclipse.jgit.lfs.server.fs;version="6.0.1";
+ org.eclipse.jgit.lfs.server.fs;version="6.1.1";
   uses:="javax.servlet,
    javax.servlet.http,
    org.eclipse.jgit.lfs.server,
    org.eclipse.jgit.lfs.lib",
- org.eclipse.jgit.lfs.server.internal;version="6.0.1";x-internal:=true,
- org.eclipse.jgit.lfs.server.s3;version="6.0.1";
+ org.eclipse.jgit.lfs.server.internal;version="6.1.1";x-internal:=true,
+ org.eclipse.jgit.lfs.server.s3;version="6.1.1";
   uses:="org.eclipse.jgit.lfs.server,
    org.eclipse.jgit.lfs.lib"
 Bundle-RequiredExecutionEnvironment: JavaSE-11
@@ -24,15 +24,15 @@
  javax.servlet.annotation;version="[3.1.0,5.0.0)",
  javax.servlet.http;version="[3.1.0,5.0.0)",
  org.apache.http;version="[4.3.0,5.0.0)",
- org.eclipse.jgit.annotations;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lfs.errors;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lfs.internal;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lfs.lib;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lib;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.nls;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport.http;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport.http.apache;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.util;version="[6.0.1,6.1.0)",
+ org.eclipse.jgit.annotations;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.storage.file;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lfs.errors;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lfs.internal;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lfs.lib;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lib;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.nls;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport.http;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport.http.apache;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.util;version="[6.1.1,6.2.0)",
  org.slf4j;version="[1.7.0,2.0.0)"
diff --git a/org.eclipse.jgit.lfs.server/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.lfs.server/META-INF/SOURCE-MANIFEST.MF
index 5011a22..5a5b23a 100644
--- a/org.eclipse.jgit.lfs.server/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.lfs.server/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.lfs.server - Sources
 Bundle-SymbolicName: org.eclipse.jgit.lfs.server.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 6.0.1.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.lfs.server;version="6.0.1.qualifier";roots="."
+Bundle-Version: 6.1.1.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.lfs.server;version="6.1.1.qualifier";roots="."
diff --git a/org.eclipse.jgit.lfs.server/pom.xml b/org.eclipse.jgit.lfs.server/pom.xml
index c2d6596..ae27153 100644
--- a/org.eclipse.jgit.lfs.server/pom.xml
+++ b/org.eclipse.jgit.lfs.server/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.lfs.server</artifactId>
diff --git a/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF
index b870794..e3c0af8 100644
--- a/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF
@@ -3,24 +3,27 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.lfs.test
 Bundle-SymbolicName: org.eclipse.jgit.lfs.test
-Bundle-Version: 6.0.1.qualifier
+Bundle-Version: 6.1.1.qualifier
 Bundle-Vendor: %Bundle-Vendor
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-11
-Import-Package: org.eclipse.jgit.api;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.attributes;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.junit;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lfs;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lfs.errors;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lfs.lib;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lib;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.revwalk;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.treewalk;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.treewalk.filter;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.util;version="[6.0.1,6.1.0)",
+Import-Package: org.eclipse.jgit.api;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.attributes;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.junit;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lfs;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lfs.errors;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lfs.internal;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lfs.lib;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lib;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.revwalk;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport.http;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.treewalk;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.treewalk.filter;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.util;version="[6.1.1,6.2.0)",
  org.hamcrest.core;version="[1.1.0,3.0.0)",
  org.junit;version="[4.13,5.0.0)",
  org.junit.runner;version="[4.13,5.0.0)",
  org.junit.runners;version="[4.13,5.0.0)"
-Export-Package: org.eclipse.jgit.lfs.test;version="6.0.1";x-friends:="org.eclipse.jgit.lfs.server.test"
+Export-Package: org.eclipse.jgit.lfs.test;version="6.1.1";x-friends:="org.eclipse.jgit.lfs.server.test"
diff --git a/org.eclipse.jgit.lfs.test/pom.xml b/org.eclipse.jgit.lfs.test/pom.xml
index a88ca94..1d782e6 100644
--- a/org.eclipse.jgit.lfs.test/pom.xml
+++ b/org.eclipse.jgit.lfs.test/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.lfs.test</artifactId>
diff --git a/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/LfsConfigGitTest.java b/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/LfsConfigGitTest.java
new file mode 100644
index 0000000..98a0712
--- /dev/null
+++ b/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/LfsConfigGitTest.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2022, Matthias Fromme <mfromme@dspace.de>
+ *
+ * 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.lfs;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.ResetCommand.ResetType;
+import org.eclipse.jgit.attributes.FilterCommand;
+import org.eclipse.jgit.attributes.FilterCommandRegistry;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lfs.internal.LfsConnectionFactory;
+import org.eclipse.jgit.lfs.lib.Constants;
+import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.transport.http.HttpConnection;
+import org.eclipse.jgit.util.HttpSupport;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Test if the lfs config is used in the correct way during checkout.
+ *
+ * Two lfs-files are created, one that comes before .gitattributes and
+ * .lfsconfig in git order (".aaa.txt") and one that comes after ("zzz.txt").
+ *
+ * During checkout/reset it is tested if the correct version of the lfs config
+ * is used.
+ *
+ * TODO: The current behavior seems a little bit strange/unintuitive. Some files
+ * are checked out before and some after the config files. This leads to the
+ * behavior, that during a single command the config changes. Since this seems
+ * to be the same way in native git, the behavior is accepted for now.
+ *
+ */
+public class LfsConfigGitTest extends RepositoryTestCase {
+
+	private static final String SMUDGE_NAME = org.eclipse.jgit.lib.Constants.BUILTIN_FILTER_PREFIX
+			+ Constants.ATTR_FILTER_DRIVER_PREFIX
+			+ org.eclipse.jgit.lib.Constants.ATTR_FILTER_TYPE_SMUDGE;
+
+	private static final String LFS_SERVER_URI1 = "https://lfs.server1/test/uri";
+
+	private static final String EXPECTED_SERVER_URL1 = LFS_SERVER_URI1
+			+ Protocol.OBJECTS_LFS_ENDPOINT;
+
+	private static final String LFS_SERVER_URI2 = "https://lfs.server2/test/uri";
+
+	private static final String EXPECTED_SERVER_URL2 = LFS_SERVER_URI2
+			+ Protocol.OBJECTS_LFS_ENDPOINT;
+
+	private static final String LFS_SERVER_URI3 = "https://lfs.server3/test/uri";
+
+	private static final String EXPECTED_SERVER_URL3 = LFS_SERVER_URI3
+			+ Protocol.OBJECTS_LFS_ENDPOINT;
+
+	private static final String FAKE_LFS_POINTER1 = "version https://git-lfs.github.com/spec/v1\n"
+			+ "oid sha256:6ce9fab52ee9a6c4c097def4e049c6acdeba44c99d26e83ba80adec1473c9b2d\n"
+			+ "size 253952\n";
+
+	private static final String FAKE_LFS_POINTER2 = "version https://git-lfs.github.com/spec/v1\n"
+			+ "oid sha256:a4b711cd989863ae2038758a62672138347abbbae4076a7ad3a545fda7d08f82\n"
+			+ "size 67072\n";
+
+	private static List<String> checkoutURLs = new ArrayList<>();
+
+	static class SmudgeFilterMock extends FilterCommand {
+		public SmudgeFilterMock(Repository db, InputStream in,
+				OutputStream out) throws IOException {
+			super(in, out);
+			HttpConnection lfsServerConn = LfsConnectionFactory.getLfsConnection(db,
+					HttpSupport.METHOD_POST, Protocol.OPERATION_DOWNLOAD);
+			checkoutURLs.add(lfsServerConn.getURL().toString());
+		}
+
+		@Override
+		public int run() throws IOException {
+			// Stupid no impl
+			in.transferTo(out);
+			return -1;
+		}
+	}
+
+	@BeforeClass
+	public static void installLfs() {
+		FilterCommandRegistry.register(SMUDGE_NAME, SmudgeFilterMock::new);
+	}
+
+	@AfterClass
+	public static void removeLfs() {
+		FilterCommandRegistry.unregister(SMUDGE_NAME);
+	}
+
+	private Git git;
+
+	@Override
+	@Before
+	public void setUp() throws Exception {
+		super.setUp();
+		git = new Git(db);
+		// commit something
+		writeTrashFile("Test.txt", "Hello world");
+		git.add().addFilepattern("Test.txt").call();
+		git.commit().setMessage("Initial commit").call();
+		// prepare the config for LFS
+		StoredConfig config = git.getRepository().getConfig();
+		config.setString("filter", "lfs", "smudge", SMUDGE_NAME);
+		config.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
+				ConfigConstants.CONFIG_KEY_AUTOCRLF, "false");
+		config.save();
+
+		fileBefore = null;
+		fileAfter = null;
+		configFile = null;
+		gitAttributesFile = null;
+	}
+
+	File fileBefore;
+
+	File fileAfter;
+
+	File configFile;
+
+	File gitAttributesFile;
+
+	private void createLfsFiles(String lfsPointer) throws Exception {
+		/*
+		 * FileNames ".aaa.txt" and "zzz.txt" seem to be sufficient to get the
+		 * desired checkout order before and after ".lfsconfig", at least in a
+		 * number of manual tries. Since the files to checkout are contained in
+		 * a set (see DirCacheCheckout::doCheckout) the order cannot be
+		 * guaranteed.
+		 */
+
+		//File to be checked out before lfs config
+		String fileNameBefore = ".aaa.txt";
+		fileBefore = writeTrashFile(fileNameBefore, lfsPointer);
+		git.add().addFilepattern(fileNameBefore).call();
+
+		// File to be checked out after lfs config
+		String fileNameAfter = "zzz.txt";
+		fileAfter = writeTrashFile(fileNameAfter, lfsPointer);
+		git.add().addFilepattern(fileNameAfter).call();
+
+		git.commit().setMessage("Commit LFS Pointer files").call();
+	}
+
+
+	private String addLfsConfigFiles(String lfsServerUrl) throws Exception {
+		// Add config files to the repo
+		String lfsConfig1 = createLfsConfig(lfsServerUrl);
+		git.add().addFilepattern(Constants.DOT_LFS_CONFIG).call();
+		// Modify gitattributes on second call, to force checkout too.
+		if (gitAttributesFile == null) {
+			gitAttributesFile = writeTrashFile(".gitattributes",
+				"*.txt filter=lfs");
+		} else {
+			gitAttributesFile = writeTrashFile(".gitattributes",
+					"*.txt filter=lfs\n");
+		}
+
+		git.add().addFilepattern(".gitattributes").call();
+		git.commit().setMessage("Commit config files").call();
+		return lfsConfig1;
+	}
+
+	private String createLfsConfig(String lfsServerUrl) throws IOException {
+		String lfsConfig1 = "[lfs]\n    url = " + lfsServerUrl;
+		configFile = writeTrashFile(Constants.DOT_LFS_CONFIG, lfsConfig1);
+		return lfsConfig1;
+	}
+
+	@Test
+	public void checkoutLfsObjects_reset() throws Exception {
+		createLfsFiles(FAKE_LFS_POINTER1);
+		String lfsConfig1 = addLfsConfigFiles(LFS_SERVER_URI1);
+
+		// Delete files to force action on reset
+		assertTrue(configFile.delete());
+		assertTrue(fileBefore.delete());
+		assertTrue(fileAfter.delete());
+
+		assertTrue(gitAttributesFile.delete());
+
+		// create config file with different url
+		createLfsConfig(LFS_SERVER_URI3);
+
+		checkoutURLs.clear();
+		git.reset().setMode(ResetType.HARD).call();
+
+		checkFile(configFile, lfsConfig1);
+		checkFile(fileBefore, FAKE_LFS_POINTER1);
+		checkFile(fileAfter, FAKE_LFS_POINTER1);
+
+		assertEquals(2, checkoutURLs.size());
+		// TODO: Should may be EXPECTED_SERVR_URL1
+		assertEquals(EXPECTED_SERVER_URL3, checkoutURLs.get(0));
+		assertEquals(EXPECTED_SERVER_URL1, checkoutURLs.get(1));
+	}
+
+	@Test
+	public void checkoutLfsObjects_BranchSwitch() throws Exception {
+		// Create a new branch "URL1" and add config files
+		git.checkout().setCreateBranch(true).setName("URL1").call();
+
+		createLfsFiles(FAKE_LFS_POINTER1);
+		String lfsConfig1 = addLfsConfigFiles(LFS_SERVER_URI1);
+
+		// Create a second new branch "URL2" and add config files
+		git.checkout().setCreateBranch(true).setName("URL2").call();
+
+		createLfsFiles(FAKE_LFS_POINTER2);
+		String lfsConfig2 = addLfsConfigFiles(LFS_SERVER_URI2);
+
+		checkFile(configFile, lfsConfig2);
+		checkFile(fileBefore, FAKE_LFS_POINTER2);
+		checkFile(fileAfter, FAKE_LFS_POINTER2);
+
+		checkoutURLs.clear();
+		git.checkout().setName("URL1").call();
+
+		checkFile(configFile, lfsConfig1);
+		checkFile(fileBefore, FAKE_LFS_POINTER1);
+		checkFile(fileAfter, FAKE_LFS_POINTER1);
+
+		assertEquals(2, checkoutURLs.size());
+		// TODO: Should may be EXPECTED_SERVR_URL1
+		assertEquals(EXPECTED_SERVER_URL2, checkoutURLs.get(0));
+		assertEquals(EXPECTED_SERVER_URL1, checkoutURLs.get(1));
+
+		checkoutURLs.clear();
+		git.checkout().setName("URL2").call();
+
+		checkFile(configFile, lfsConfig2);
+		checkFile(fileBefore, FAKE_LFS_POINTER2);
+		checkFile(fileAfter, FAKE_LFS_POINTER2);
+
+		assertEquals(2, checkoutURLs.size());
+		// TODO: Should may be EXPECTED_SERVR_URL2
+		assertEquals(EXPECTED_SERVER_URL1, checkoutURLs.get(0));
+		assertEquals(EXPECTED_SERVER_URL2, checkoutURLs.get(1));
+	}
+
+	@Test
+	public void checkoutLfsObjects_BranchSwitch_ModifiedLocal()
+			throws Exception {
+
+		// Create a new branch "URL1" and add config files
+		git.checkout().setCreateBranch(true).setName("URL1").call();
+
+		createLfsFiles(FAKE_LFS_POINTER1);
+		addLfsConfigFiles(LFS_SERVER_URI1);
+
+		// Create a second new branch "URL2" and add config files
+		git.checkout().setCreateBranch(true).setName("URL2").call();
+
+		createLfsFiles(FAKE_LFS_POINTER2);
+		addLfsConfigFiles(LFS_SERVER_URI1);
+
+		// create config file with different url
+		assertTrue(configFile.delete());
+		String lfsConfig3 = createLfsConfig(LFS_SERVER_URI3);
+
+		checkFile(configFile, lfsConfig3);
+		checkFile(fileBefore, FAKE_LFS_POINTER2);
+		checkFile(fileAfter, FAKE_LFS_POINTER2);
+
+		checkoutURLs.clear();
+		git.checkout().setName("URL1").call();
+
+		checkFile(fileBefore, FAKE_LFS_POINTER1);
+		checkFile(fileAfter, FAKE_LFS_POINTER1);
+		checkFile(configFile, lfsConfig3);
+
+		assertEquals(2, checkoutURLs.size());
+
+		assertEquals(EXPECTED_SERVER_URL3, checkoutURLs.get(0));
+		assertEquals(EXPECTED_SERVER_URL3, checkoutURLs.get(1));
+
+		checkoutURLs.clear();
+		git.checkout().setName("URL2").call();
+
+		checkFile(fileBefore, FAKE_LFS_POINTER2);
+		checkFile(fileAfter, FAKE_LFS_POINTER2);
+		checkFile(configFile, lfsConfig3);
+
+		assertEquals(2, checkoutURLs.size());
+		assertEquals(EXPECTED_SERVER_URL3, checkoutURLs.get(0));
+		assertEquals(EXPECTED_SERVER_URL3, checkoutURLs.get(1));
+	}
+}
diff --git a/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/LfsGitTest.java b/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/LfsGitTest.java
index 8964310..3e83c8e 100644
--- a/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/LfsGitTest.java
+++ b/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/LfsGitTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021, Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2021, 2022 Thomas Wolf <thomas.wolf@paranor.ch> 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
@@ -68,6 +68,27 @@
 	}
 
 	@Test
+	public void testBranchSwitch() throws Exception {
+		git.branchCreate().setName("abranch").call();
+		git.checkout().setName("abranch").call();
+		File aFile = writeTrashFile("a.bin", "aaa");
+		writeTrashFile(".gitattributes", "a.bin filter=lfs");
+		git.add().addFilepattern(".").call();
+		git.commit().setMessage("acommit").call();
+		git.checkout().setName("master").call();
+		git.branchCreate().setName("bbranch").call();
+		git.checkout().setName("bbranch").call();
+		File bFile = writeTrashFile("b.bin", "bbb");
+		writeTrashFile(".gitattributes", "b.bin filter=lfs");
+		git.add().addFilepattern(".").call();
+		git.commit().setMessage("bcommit").call();
+		git.checkout().setName("abranch").call();
+		checkFile(aFile, "aaa");
+		git.checkout().setName("bbranch").call();
+		checkFile(bFile, "bbb");
+	}
+
+	@Test
 	public void checkoutNonLfsPointer() throws Exception {
 		String content = "size_t\nsome_function(void* ptr);\n";
 		File smallFile = writeTrashFile("Test.txt", content);
diff --git a/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/internal/LfsConnectionFactoryTest.java b/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/internal/LfsConnectionFactoryTest.java
new file mode 100644
index 0000000..badcb7d
--- /dev/null
+++ b/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/internal/LfsConnectionFactoryTest.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2022 Nail Samatov <sanail@yandex.ru> 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
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.lfs.internal;
+
+import static org.eclipse.jgit.lib.Constants.DEFAULT_REMOTE_NAME;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.RemoteAddCommand;
+import org.eclipse.jgit.attributes.FilterCommandRegistry;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lfs.CleanFilter;
+import org.eclipse.jgit.lfs.Protocol;
+import org.eclipse.jgit.lfs.SmudgeFilter;
+import org.eclipse.jgit.lfs.errors.LfsConfigInvalidException;
+import org.eclipse.jgit.lfs.lib.Constants;
+import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.transport.URIish;
+import org.eclipse.jgit.transport.http.HttpConnection;
+import org.eclipse.jgit.util.HttpSupport;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class LfsConnectionFactoryTest extends RepositoryTestCase {
+
+	private static final String SMUDGE_NAME = org.eclipse.jgit.lib.Constants.BUILTIN_FILTER_PREFIX
+			+ Constants.ATTR_FILTER_DRIVER_PREFIX
+			+ org.eclipse.jgit.lib.Constants.ATTR_FILTER_TYPE_SMUDGE;
+
+	private static final String CLEAN_NAME = org.eclipse.jgit.lib.Constants.BUILTIN_FILTER_PREFIX
+			+ Constants.ATTR_FILTER_DRIVER_PREFIX
+			+ org.eclipse.jgit.lib.Constants.ATTR_FILTER_TYPE_CLEAN;
+
+	private final static String LFS_SERVER_URL1 = "https://lfs.server1/test/uri";
+
+	private final static String LFS_SERVER_URL2 = "https://lfs.server2/test/uri";
+
+	private final static String ORIGIN_URL = "https://git.server/test/uri";
+
+	private Git git;
+
+	@BeforeClass
+	public static void installLfs() {
+		FilterCommandRegistry.register(SMUDGE_NAME, SmudgeFilter.FACTORY);
+		FilterCommandRegistry.register(CLEAN_NAME, CleanFilter.FACTORY);
+	}
+
+	@AfterClass
+	public static void removeLfs() {
+		FilterCommandRegistry.unregister(SMUDGE_NAME);
+		FilterCommandRegistry.unregister(CLEAN_NAME);
+	}
+
+	@Override
+	@Before
+	public void setUp() throws Exception {
+		super.setUp();
+		git = new Git(db);
+
+		// Just to have a non empty repo
+		writeTrashFile("Test.txt", "Hello world from the LFS Factory Test");
+		git.add().addFilepattern("Test.txt").call();
+		git.commit().setMessage("Initial commit").call();
+	}
+
+	@Test
+	public void lfsUrlFromRemoteUrlWithDotGit() throws Exception {
+		addRemoteUrl("https://localhost/repo.git");
+		checkLfsUrl("https://localhost/repo.git/info/lfs");
+	}
+
+	@Test
+	public void lfsUrlFromRemoteUrlWithoutDotGit() throws Exception {
+		addRemoteUrl("https://localhost/repo");
+		checkLfsUrl("https://localhost/repo.git/info/lfs");
+	}
+
+	@Test
+	public void lfsUrlFromLocalConfig() throws Exception {
+		addRemoteUrl("https://localhost/repo");
+
+		StoredConfig cfg = ((Repository) db).getConfig();
+		cfg.setString(ConfigConstants.CONFIG_SECTION_LFS,
+				null,
+				ConfigConstants.CONFIG_KEY_URL,
+				"https://localhost/repo/lfs");
+		cfg.save();
+
+		checkLfsUrl("https://localhost/repo/lfs");
+	}
+
+	@Test
+	public void lfsUrlFromOriginConfig() throws Exception {
+		addRemoteUrl("https://localhost/repo");
+
+		StoredConfig cfg = ((Repository) db).getConfig();
+		cfg.setString(ConfigConstants.CONFIG_SECTION_LFS,
+				org.eclipse.jgit.lib.Constants.DEFAULT_REMOTE_NAME,
+				ConfigConstants.CONFIG_KEY_URL,
+				"https://localhost/repo/lfs");
+		cfg.save();
+
+		checkLfsUrl("https://localhost/repo/lfs");
+	}
+
+	@Test
+	public void lfsUrlNotConfigured() throws Exception {
+		assertThrows(LfsConfigInvalidException.class,
+				() -> LfsConnectionFactory.getLfsConnection(db,
+				HttpSupport.METHOD_POST, Protocol.OPERATION_DOWNLOAD));
+	}
+
+	@Test
+	public void checkGetLfsConnection_lfsurl_lfsconfigFromWorkingDir()
+			throws Exception {
+		writeLfsConfig();
+		checkLfsUrl(LFS_SERVER_URL1);
+	}
+
+	@Test
+	public void checkGetLfsConnection_lfsurl_lfsconfigFromIndex()
+			throws Exception {
+		writeLfsConfig();
+		git.add().addFilepattern(Constants.DOT_LFS_CONFIG).call();
+		deleteTrashFile(Constants.DOT_LFS_CONFIG);
+		checkLfsUrl(LFS_SERVER_URL1);
+	}
+
+	@Test
+	public void checkGetLfsConnection_lfsurl_lfsconfigFromHEAD()
+			throws Exception {
+		writeLfsConfig();
+		git.add().addFilepattern(Constants.DOT_LFS_CONFIG).call();
+		git.commit().setMessage("Commit LFS Config").call();
+
+		/*
+		 * reading .lfsconfig from HEAD seems only testable using a bare repo,
+		 * since otherwise working tree or index are used
+		 */
+		File directory = createTempDirectory("testBareRepo");
+		try (Repository bareRepoDb = Git.cloneRepository()
+				.setDirectory(directory)
+				.setURI(db.getDirectory().toURI().toString()).setBare(true)
+				.call().getRepository()) {
+
+			checkLfsUrl(LFS_SERVER_URL1);
+		}
+	}
+
+	@Test
+	public void checkGetLfsConnection_remote_lfsconfigFromWorkingDir()
+			throws Exception {
+		addRemoteUrl(ORIGIN_URL);
+		writeLfsConfig(LFS_SERVER_URL1, "lfs", DEFAULT_REMOTE_NAME, "url");
+		checkLfsUrl(LFS_SERVER_URL1);
+	}
+
+	/**
+	 * Test the config file precedence.
+	 *
+	 * Checking only with the local repository config is sufficient since from
+	 * that point the "normal" precedence is used.
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void checkGetLfsConnection_ConfigFilePrecedence_lfsconfigFromWorkingDir()
+			throws Exception {
+		writeLfsConfig();
+		checkLfsUrl(LFS_SERVER_URL1);
+
+		StoredConfig config = git.getRepository().getConfig();
+		config.setString(ConfigConstants.CONFIG_SECTION_LFS, null,
+				ConfigConstants.CONFIG_KEY_URL, LFS_SERVER_URL2);
+		config.save();
+
+		checkLfsUrl(LFS_SERVER_URL2);
+	}
+
+	@Test
+	public void checkGetLfsConnection_InvalidLfsConfig_WorkingDir()
+			throws Exception {
+		writeInvalidLfsConfig();
+		LfsConfigInvalidException actualException = assertThrows(
+				LfsConfigInvalidException.class, () -> {
+			LfsConnectionFactory.getLfsConnection(db, HttpSupport.METHOD_POST,
+					Protocol.OPERATION_DOWNLOAD);
+		});
+		assertTrue(getStackTrace(actualException)
+				.contains("Invalid line in config file"));
+	}
+
+	@Test
+	public void checkGetLfsConnection_InvalidLfsConfig_Index()
+			throws Exception {
+		writeInvalidLfsConfig();
+		git.add().addFilepattern(Constants.DOT_LFS_CONFIG).call();
+		deleteTrashFile(Constants.DOT_LFS_CONFIG);
+		LfsConfigInvalidException actualException = assertThrows(
+				LfsConfigInvalidException.class, () -> {
+			LfsConnectionFactory.getLfsConnection(db, HttpSupport.METHOD_POST,
+					Protocol.OPERATION_DOWNLOAD);
+		});
+		assertTrue(getStackTrace(actualException)
+				.contains("Invalid line in config file"));
+	}
+
+	@Test
+	public void checkGetLfsConnection_InvalidLfsConfig_HEAD() throws Exception {
+		writeInvalidLfsConfig();
+		git.add().addFilepattern(Constants.DOT_LFS_CONFIG).call();
+		git.commit().setMessage("Commit LFS Config").call();
+
+		/*
+		 * reading .lfsconfig from HEAD seems only testable using a bare repo,
+		 * since otherwise working tree or index are used
+		 */
+		File directory = createTempDirectory("testBareRepo");
+		try (Repository bareRepoDb = Git.cloneRepository()
+				.setDirectory(directory)
+				.setURI(db.getDirectory().toURI().toString()).setBare(true)
+				.call().getRepository()) {
+			LfsConfigInvalidException actualException = assertThrows(
+					LfsConfigInvalidException.class,
+					() -> {
+						LfsConnectionFactory.getLfsConnection(db,
+								HttpSupport.METHOD_POST,
+								Protocol.OPERATION_DOWNLOAD);
+					});
+			assertTrue(getStackTrace(actualException)
+					.contains("Invalid line in config file"));
+		}
+	}
+
+	private void addRemoteUrl(String remotUrl) throws Exception {
+		RemoteAddCommand add = git.remoteAdd();
+		add.setUri(new URIish(remotUrl));
+		add.setName(org.eclipse.jgit.lib.Constants.DEFAULT_REMOTE_NAME);
+		add.call();
+	}
+
+	/**
+	 * Returns the stack trace of the provided exception as string
+	 *
+	 * @param actualException
+	 * @return The exception stack trace as string
+	 */
+	private String getStackTrace(Exception actualException) {
+		StringWriter sw = new StringWriter();
+		PrintWriter pw = new PrintWriter(sw);
+		actualException.printStackTrace(pw);
+		return sw.toString();
+	}
+
+	private void writeLfsConfig() throws IOException {
+		writeLfsConfig(LFS_SERVER_URL1, "lfs", "url");
+	}
+
+	private void writeLfsConfig(String lfsUrl, String section, String name)
+			throws IOException {
+		writeLfsConfig(lfsUrl, section, null, name);
+	}
+
+	/*
+	 * Write simple lfs config with single entry. Do not use FileBasedConfig to
+	 * avoid introducing new dependency (for now).
+	 */
+	private void writeLfsConfig(String lfsUrl, String section,
+			String subsection, String name) throws IOException {
+		StringBuilder config = new StringBuilder();
+		config.append("[");
+		config.append(section);
+		if (subsection != null) {
+			config.append(" \"");
+			config.append(subsection);
+			config.append("\"");
+		}
+		config.append("]\n");
+		config.append("    ");
+		config.append(name);
+		config.append(" = ");
+		config.append(lfsUrl);
+		writeTrashFile(Constants.DOT_LFS_CONFIG, config.toString());
+	}
+
+	private void writeInvalidLfsConfig() throws IOException {
+		writeTrashFile(Constants.DOT_LFS_CONFIG,
+				"{lfs]\n    url = " + LFS_SERVER_URL1);
+	}
+
+	private void checkLfsUrl(String lfsUrl) throws IOException {
+		HttpConnection lfsServerConn;
+		lfsServerConn = LfsConnectionFactory.getLfsConnection(db,
+				HttpSupport.METHOD_POST, Protocol.OPERATION_DOWNLOAD);
+
+		assertEquals(lfsUrl + Protocol.OBJECTS_LFS_ENDPOINT,
+				lfsServerConn.getURL().toString());
+	}
+}
diff --git a/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF
index fba58f8..09ffffb 100644
--- a/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF
@@ -3,31 +3,33 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.lfs
 Bundle-SymbolicName: org.eclipse.jgit.lfs
-Bundle-Version: 6.0.1.qualifier
+Bundle-Version: 6.1.1.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %Bundle-Vendor
-Export-Package: org.eclipse.jgit.lfs;version="6.0.1",
- org.eclipse.jgit.lfs.errors;version="6.0.1",
- org.eclipse.jgit.lfs.internal;version="6.0.1";x-friends:="org.eclipse.jgit.lfs.test,org.eclipse.jgit.lfs.server.fs,org.eclipse.jgit.lfs.server",
- org.eclipse.jgit.lfs.lib;version="6.0.1"
+Export-Package: org.eclipse.jgit.lfs;version="6.1.1",
+ org.eclipse.jgit.lfs.errors;version="6.1.1",
+ org.eclipse.jgit.lfs.internal;version="6.1.1";x-friends:="org.eclipse.jgit.lfs.test,org.eclipse.jgit.lfs.server.fs,org.eclipse.jgit.lfs.server",
+ org.eclipse.jgit.lfs.lib;version="6.1.1"
 Bundle-RequiredExecutionEnvironment: JavaSE-11
 Import-Package: com.google.gson;version="[2.8.2,3.0.0)",
  com.google.gson.stream;version="[2.8.2,3.0.0)",
- org.eclipse.jgit.annotations;version="[6.0.1,6.1.0)";resolution:=optional,
- org.eclipse.jgit.api.errors;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.attributes;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.diff;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.errors;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.hooks;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lib;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.nls;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.revwalk;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.storage.file;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.storage.pack;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport.http;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.treewalk;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.treewalk.filter;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.util;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.util.io;version="[6.0.1,6.1.0)"
+ org.eclipse.jgit.annotations;version="[6.1.1,6.2.0)";resolution:=optional,
+ org.eclipse.jgit.api.errors;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.attributes;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.diff;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.dircache;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.errors;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.hooks;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.storage.file;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lib;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.nls;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.revwalk;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.storage.file;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.storage.pack;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport.http;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.treewalk;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.treewalk.filter;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.util;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.util.io;version="[6.1.1,6.2.0)",
+ org.slf4j;version="[1.7.0,2.0.0)"
diff --git a/org.eclipse.jgit.lfs/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.lfs/META-INF/SOURCE-MANIFEST.MF
index a715be6..7a2a446 100644
--- a/org.eclipse.jgit.lfs/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.lfs/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.lfs - Sources
 Bundle-SymbolicName: org.eclipse.jgit.lfs.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 6.0.1.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.lfs;version="6.0.1.qualifier";roots="."
+Bundle-Version: 6.1.1.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.lfs;version="6.1.1.qualifier";roots="."
diff --git a/org.eclipse.jgit.lfs/pom.xml b/org.eclipse.jgit.lfs/pom.xml
index eb57db3..949c568 100644
--- a/org.eclipse.jgit.lfs/pom.xml
+++ b/org.eclipse.jgit.lfs/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.lfs</artifactId>
diff --git a/org.eclipse.jgit.lfs/resources/org/eclipse/jgit/lfs/internal/LfsText.properties b/org.eclipse.jgit.lfs/resources/org/eclipse/jgit/lfs/internal/LfsText.properties
index 0e00f14..642b83d 100644
--- a/org.eclipse.jgit.lfs/resources/org/eclipse/jgit/lfs/internal/LfsText.properties
+++ b/org.eclipse.jgit.lfs/resources/org/eclipse/jgit/lfs/internal/LfsText.properties
@@ -1,19 +1,19 @@
 corruptLongObject=The content hash ''{0}'' of the long object ''{1}'' doesn''t match its id, the corrupt object will be deleted.
-incorrectLONG_OBJECT_ID_LENGTH=Incorrect LONG_OBJECT_ID_LENGTH.
-inconsistentMediafileLength=Mediafile {0} has unexpected length; expected {1} but found {2}.
+dotLfsConfigReadFailed=Reading .lfsconfig failed
 inconsistentContentLength=Unexpected content length reported by LFS server ({0}), expected {1} but reported was {2}
+inconsistentMediafileLength=Mediafile {0} has unexpected length; expected {1} but found {2}.
+incorrectLONG_OBJECT_ID_LENGTH=Incorrect LONG_OBJECT_ID_LENGTH.
 invalidLongId=Invalid id: {0}
 invalidLongIdLength=Invalid id length {0}; should be {1}
-lfsUnavailable=LFS is not available for repository {0}
-protocolError=LFS Protocol Error {0}: {1}
-requiredHashFunctionNotAvailable=Required hash function {0} not available.
-repositoryNotFound=Repository {0} not found
-repositoryReadOnly=Repository {0} is read-only
-lfsUnavailable=LFS is not available for repository {0}
-lfsUnathorized=Not authorized to perform operation {0} on repository {1}
 lfsFailedToGetRepository=failed to get repository {0}
 lfsNoDownloadUrl="Need to download object from LFS server but couldn't determine LFS server URL"
+lfsUnauthorized=Not authorized to perform operation {0} on repository {1}
+lfsUnavailable=LFS is not available for repository {0}
+missingLocalObject="Local Object {0} is missing"
+protocolError=LFS Protocol Error {0}: {1}
+repositoryNotFound=Repository {0} not found
+repositoryReadOnly=Repository {0} is read-only
+requiredHashFunctionNotAvailable=Required hash function {0} not available.
 serverFailure=When trying to open a connection to {0} the server responded with an error code. rc={1}
-wrongAmoutOfDataReceived=While downloading data from the content server {0} {1} bytes have been received while {2} have been expected
 userConfigInvalid="User config file {0} invalid {1}"
-missingLocalObject="Local Object {0} is missing"
\ No newline at end of file
+wrongAmountOfDataReceived=While downloading data from the content server {0} {1} bytes have been received while {2} have been expected
\ No newline at end of file
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPrePushHook.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPrePushHook.java
index d6ce855..ebf46e0 100644
--- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPrePushHook.java
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPrePushHook.java
@@ -113,6 +113,9 @@
 
 		try (ObjectWalk walk = new ObjectWalk(getRepository())) {
 			for (RemoteRefUpdate up : refs) {
+				if (up.isDelete()) {
+					continue;
+				}
 				walk.setRewriteParents(false);
 				excludeRemoteRefs(walk);
 				walk.markStart(walk.parseCommit(up.getNewObjectId()));
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/SmudgeFilter.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/SmudgeFilter.java
index 3411887..c26a1bf 100644
--- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/SmudgeFilter.java
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/SmudgeFilter.java
@@ -205,7 +205,7 @@
 					long bytesCopied = Files.copy(contentIn, path);
 					if (bytesCopied != o.size) {
 						throw new IOException(MessageFormat.format(
-								LfsText.get().wrongAmoutOfDataReceived,
+								LfsText.get().wrongAmountOfDataReceived,
 								contentServerConn.getURL(),
 								Long.valueOf(bytesCopied),
 								Long.valueOf(o.size)));
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsUnauthorized.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsUnauthorized.java
index 36889db..0dc6aea 100644
--- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsUnauthorized.java
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsUnauthorized.java
@@ -31,7 +31,7 @@
 	 *            the repository name.
 	 */
 	public LfsUnauthorized(String operation, String name) {
-		super(MessageFormat.format(LfsText.get().lfsUnathorized, operation,
+		super(MessageFormat.format(LfsText.get().lfsUnauthorized, operation,
 				name));
 	}
 }
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsConfig.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsConfig.java
new file mode 100644
index 0000000..71d395c
--- /dev/null
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsConfig.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2022, Matthias Fromme <mfromme@dspace.de>
+ *
+ * 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.lfs.internal;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lfs.errors.LfsConfigInvalidException;
+import org.eclipse.jgit.lfs.lib.Constants;
+import org.eclipse.jgit.lib.BlobBasedConfig;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.treewalk.TreeWalk;
+
+import static org.eclipse.jgit.lib.Constants.HEAD;
+
+/**
+ * Encapsulate access to the .lfsconfig.
+ *
+ * According to the document
+ * https://github.com/git-lfs/git-lfs/blob/main/docs/man/git-lfs-config.5.ronn
+ * the order to find the .lfsconfig file is:
+ *
+ * <pre>
+ *   1. in the root of the working tree
+ *   2. in the index
+ *   3. in the HEAD, for bare repositories this is the only place
+ *      that is searched
+ * </pre>
+ *
+ * Values from the .lfsconfig are used only if not specified in another git
+ * config file to allow local override without modifiction of a committed file.
+ */
+public class LfsConfig {
+	private Repository db;
+	private Config delegate;
+
+	/**
+	 * Create a new instance of the LfsConfig.
+	 *
+	 * @param db
+	 *            the associated repo
+	 * @throws IOException
+	 */
+	public LfsConfig(Repository db) throws IOException {
+		this.db = db;
+		delegate = this.load();
+	}
+
+	/**
+	 * Read the .lfsconfig file from the repository
+	 *
+	 * @return The loaded lfs config or null if it does not exist
+	 *
+	 * @throws IOException
+	 */
+	private Config load() throws IOException {
+		Config result = null;
+
+		if (!db.isBare()) {
+			result = loadFromWorkingTree();
+			if (result == null) {
+				result = loadFromIndex();
+			}
+		}
+
+		if (result == null) {
+			result = loadFromHead();
+		}
+
+		if (result == null) {
+			result = emptyConfig();
+		}
+
+		return result;
+	}
+
+	/**
+	 * Try to read the lfs config from a file called .lfsconfig at the top level
+	 * of the working tree.
+	 *
+	 * @return the config, or <code>null</code>
+	 * @throws IOException
+	 */
+	@Nullable
+	private Config loadFromWorkingTree()
+			throws IOException {
+		File lfsConfig = db.getFS().resolve(db.getWorkTree(),
+				Constants.DOT_LFS_CONFIG);
+		if (lfsConfig.exists() && lfsConfig.isFile()) {
+			FileBasedConfig config = new FileBasedConfig(lfsConfig, db.getFS());
+			try {
+				config.load();
+				return config;
+			} catch (ConfigInvalidException e) {
+				throw new LfsConfigInvalidException(
+						LfsText.get().dotLfsConfigReadFailed, e);
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Try to read the lfs config from an entry called .lfsconfig contained in
+	 * the index.
+	 *
+	 * @return the config, or <code>null</code> if the entry does not exist
+	 * @throws IOException
+	 */
+	@Nullable
+	private Config loadFromIndex()
+			throws IOException {
+		try {
+			DirCacheEntry entry = db.readDirCache()
+					.getEntry(Constants.DOT_LFS_CONFIG);
+			if (entry != null) {
+				return new BlobBasedConfig(null, db, entry.getObjectId());
+			}
+		} catch (ConfigInvalidException e) {
+			throw new LfsConfigInvalidException(
+					LfsText.get().dotLfsConfigReadFailed, e);
+		}
+		return null;
+	}
+
+	/**
+	 * Try to read the lfs config from an entry called .lfsconfig contained in
+	 * the head revision.
+	 *
+	 * @return the config, or <code>null</code> if the file does not exist
+	 * @throws IOException
+	 */
+	@Nullable
+	private Config loadFromHead() throws IOException {
+		try (RevWalk revWalk = new RevWalk(db)) {
+			ObjectId headCommitId = db.resolve(HEAD);
+			if (headCommitId == null) {
+				return null;
+			}
+			RevCommit commit = revWalk.parseCommit(headCommitId);
+			RevTree tree = commit.getTree();
+			TreeWalk treewalk = TreeWalk.forPath(db, Constants.DOT_LFS_CONFIG,
+					tree);
+			if (treewalk != null) {
+				return new BlobBasedConfig(null, db, treewalk.getObjectId(0));
+			}
+		} catch (ConfigInvalidException e) {
+			throw new LfsConfigInvalidException(
+					LfsText.get().dotLfsConfigReadFailed, e);
+		}
+		return null;
+	}
+
+	/**
+	 * Create an empty config as fallback to avoid null pointer checks.
+	 *
+	 * @return an empty config
+	 */
+	private Config emptyConfig() {
+		return new Config();
+	}
+
+	/**
+	 * Get string value or null if not found.
+	 *
+	 * First tries to find the value in the git config files. If not found tries
+	 * to find data in .lfsconfig.
+	 *
+	 * @param section
+	 *            the section
+	 * @param subsection
+	 *            the subsection for the value
+	 * @param name
+	 *            the key name
+	 * @return a String value from the config, <code>null</code> if not found
+	 */
+	public String getString(final String section, final String subsection,
+			final String name) {
+		String result = db.getConfig().getString(section, subsection, name);
+		if (result == null) {
+			result = delegate.getString(section, subsection, name);
+		}
+		return result;
+	}
+}
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsConnectionFactory.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsConnectionFactory.java
index e221913..12b688d 100644
--- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsConnectionFactory.java
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsConnectionFactory.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017, Markus Duft <markus.duft@ssi-schaefer.com> and others
+ * Copyright (C) 2017, 2022 Markus Duft <markus.duft@ssi-schaefer.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
@@ -39,12 +39,12 @@
 import org.eclipse.jgit.transport.http.HttpConnection;
 import org.eclipse.jgit.util.HttpSupport;
 import org.eclipse.jgit.util.SshSupport;
+import org.eclipse.jgit.util.StringUtils;
 
 /**
  * Provides means to get a valid LFS connection for a given repository.
  */
 public class LfsConnectionFactory {
-
 	private static final int SSH_AUTH_TIMEOUT_SECONDS = 30;
 	private static final String SCHEME_HTTPS = "https"; //$NON-NLS-1$
 	private static final String SCHEME_SSH = "ssh"; //$NON-NLS-1$
@@ -64,7 +64,7 @@
 	 *            be used for
 	 * @param purpose
 	 *            the action, e.g. Protocol.OPERATION_DOWNLOAD
-	 * @return the url for the lfs server. e.g.
+	 * @return the connection for the lfs server. e.g.
 	 *         "https://github.com/github/git-lfs.git/info/lfs"
 	 * @throws IOException
 	 */
@@ -92,13 +92,30 @@
 		return connection;
 	}
 
+	/**
+	 * Get LFS Server URL.
+	 *
+	 * @param db
+	 *            the repository to work with
+	 * @param purpose
+	 *            the action, e.g. Protocol.OPERATION_DOWNLOAD
+	 * @param additionalHeaders
+	 *            additional headers that can be used to connect to LFS server
+	 * @return the URL for the LFS server. e.g.
+	 *         "https://github.com/github/git-lfs.git/info/lfs"
+	 * @throws IOException
+	 *             if the LFS config is invalid or cannot be accessed
+	 * @see <a href=
+	 *      "https://github.com/git-lfs/git-lfs/blob/main/docs/api/server-discovery.md">
+	 *      Server Discovery documentation</a>
+	 */
 	private static String getLfsUrl(Repository db, String purpose,
 			Map<String, String> additionalHeaders)
-			throws LfsConfigInvalidException {
-		StoredConfig config = db.getConfig();
+			throws IOException {
+		LfsConfig config = new LfsConfig(db);
 		String lfsUrl = config.getString(ConfigConstants.CONFIG_SECTION_LFS,
-				null,
-				ConfigConstants.CONFIG_KEY_URL);
+				null, ConfigConstants.CONFIG_KEY_URL);
+
 		Exception ex = null;
 		if (lfsUrl == null) {
 			String remoteUrl = null;
@@ -106,6 +123,7 @@
 				lfsUrl = config.getString(ConfigConstants.CONFIG_SECTION_LFS,
 						remote,
 						ConfigConstants.CONFIG_KEY_URL);
+
 				// This could be done better (more precise logic), but according
 				// to https://github.com/git-lfs/git-lfs/issues/1759 git-lfs
 				// generally only supports 'origin' in an integrated workflow.
@@ -125,8 +143,6 @@
 						| CommandFailedException e) {
 					ex = e;
 				}
-			} else {
-				lfsUrl = lfsUrl + Protocol.INFO_LFS_ENDPOINT;
 			}
 		}
 		if (lfsUrl == null) {
@@ -149,7 +165,8 @@
 			additionalHeaders.putAll(action.header);
 			return action.href;
 		}
-		return remoteUrl + Protocol.INFO_LFS_ENDPOINT;
+		return StringUtils.nameWithDotGit(remoteUrl)
+				+ Protocol.INFO_LFS_ENDPOINT;
 	}
 
 	private static Protocol.ExpiringAction getSshAuthentication(
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsText.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsText.java
index 1ca37a9..06234c1 100644
--- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsText.java
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsText.java
@@ -28,21 +28,22 @@
 
 	// @formatter:off
 	/***/ public String corruptLongObject;
-	/***/ public String inconsistentMediafileLength;
+	/***/ public String dotLfsConfigReadFailed;
 	/***/ public String inconsistentContentLength;
+	/***/ public String inconsistentMediafileLength;
 	/***/ public String incorrectLONG_OBJECT_ID_LENGTH;
 	/***/ public String invalidLongId;
 	/***/ public String invalidLongIdLength;
-	/***/ public String lfsUnavailable;
-	/***/ public String protocolError;
-	/***/ public String requiredHashFunctionNotAvailable;
-	/***/ public String repositoryNotFound;
-	/***/ public String repositoryReadOnly;
-	/***/ public String lfsUnathorized;
 	/***/ public String lfsFailedToGetRepository;
 	/***/ public String lfsNoDownloadUrl;
-	/***/ public String serverFailure;
-	/***/ public String wrongAmoutOfDataReceived;
-	/***/ public String userConfigInvalid;
+	/***/ public String lfsUnauthorized;
+	/***/ public String lfsUnavailable;
 	/***/ public String missingLocalObject;
+	/***/ public String protocolError;
+	/***/ public String repositoryNotFound;
+	/***/ public String repositoryReadOnly;
+	/***/ public String requiredHashFunctionNotAvailable;
+	/***/ public String serverFailure;
+	/***/ public String userConfigInvalid;
+	/***/ public String wrongAmountOfDataReceived;
 }
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/Constants.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/Constants.java
index 3212a63..9b41ec3 100644
--- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/Constants.java
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/Constants.java
@@ -82,6 +82,13 @@
 	public static final String ATTR_FILTER_DRIVER_PREFIX = "lfs/";
 
 	/**
+	 * Config file name for lfs specific configuration
+	 *
+	 * @since 6.1
+	 */
+	public static final String DOT_LFS_CONFIG = ".lfsconfig";
+
+	/**
 	 * Create a new digest function for objects.
 	 *
 	 * @return a new digest object.
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml
index cc1bf85..cd22e24 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit"
       label="%featureName"
-      version="6.0.1.qualifier"
+      version="6.1.1.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml
index 874b274..54feb9d 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.gpg.bc.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.gpg.bc.feature/feature.xml
index be02662..2061948 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.gpg.bc.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.gpg.bc.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.gpg.bc"
       label="%featureName"
-      version="6.0.1.qualifier"
+      version="6.1.1.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
@@ -23,7 +23,7 @@
    </url>
 
    <requires>
-      <import plugin="org.eclipse.jgit" version="6.0.1" match="equivalent"/>
+      <import plugin="org.eclipse.jgit" version="6.1.1" match="equivalent"/>
    </requires>
 
    <plugin
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.gpg.bc.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.gpg.bc.feature/pom.xml
index 2909e80..760903e 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.gpg.bc.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.gpg.bc.feature/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml
index 8937a83..e899ad5 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.http.apache"
       label="%featureName"
-      version="6.0.1.qualifier"
+      version="6.1.1.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
@@ -23,7 +23,7 @@
    </url>
 
    <requires>
-      <import plugin="org.eclipse.jgit" version="6.0.1" match="equivalent"/>
+      <import plugin="org.eclipse.jgit" version="6.1.1" match="equivalent"/>
    </requires>
 
    <plugin
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml
index 2292c2e..b725dff 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml
index 6eee7cd..99073e4 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.junit"
       label="%featureName"
-      version="6.0.1.qualifier"
+      version="6.1.1.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
@@ -24,7 +24,7 @@
 
    <requires>
       <import plugin="com.jcraft.jsch"/>
-      <import plugin="org.eclipse.jgit" version="6.0.1" match="equivalent"/>
+      <import plugin="org.eclipse.jgit" version="6.1.1" match="equivalent"/>
    </requires>
 
    <plugin
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml
index dc4159b..3566dbb 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml
index f0a4884..c04051b 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.lfs"
       label="%featureName"
-      version="6.0.1.qualifier"
+      version="6.1.1.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
@@ -23,7 +23,7 @@
    </url>
 
    <requires>
-      <import feature="org.eclipse.jgit" version="6.0.1" match="equivalent"/>
+      <import feature="org.eclipse.jgit" version="6.1.1" match="equivalent"/>
    </requires>
 
    <plugin
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/pom.xml
index e344c19..50338a6 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml
index f5889e5..3939f51 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.pgm"
       label="%featureName"
-      version="6.0.1.qualifier"
+      version="6.1.1.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
@@ -35,9 +35,9 @@
          version="0.0.0"/>
 
    <requires>
-      <import feature="org.eclipse.jgit" version="6.0.1" match="equivalent"/>
-      <import feature="org.eclipse.jgit.lfs" version="6.0.1" match="equivalent"/>
-      <import feature="org.eclipse.jgit.ssh.apache" version="6.0.1" match="equivalent"/>
+      <import feature="org.eclipse.jgit" version="6.1.1" match="equivalent"/>
+      <import feature="org.eclipse.jgit.lfs" version="6.1.1" match="equivalent"/>
+      <import feature="org.eclipse.jgit.ssh.apache" version="6.1.1" match="equivalent"/>
    </requires>
 
    <plugin
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml
index ae95704..4b0e26a 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml
index e12e599..9ccf39e 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.repository</artifactId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml
index d5d1554..d3e662b 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.source"
       label="%featureName"
-      version="6.0.1.qualifier"
+      version="6.1.1.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
@@ -23,7 +23,7 @@
    </url>
 
    <requires>
-      <import feature="org.eclipse.jgit" version="6.0.1" match="equivalent"/>
+      <import feature="org.eclipse.jgit" version="6.1.1" match="equivalent"/>
    </requires>
 
    <plugin
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml
index a20645e..d8b5de2 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
@@ -30,7 +30,7 @@
     <dependency>
       <groupId>org.eclipse.jgit.feature</groupId>
       <artifactId>org.eclipse.jgit</artifactId>
-      <version>6.0.1-SNAPSHOT</version>
+      <version>6.1.1-SNAPSHOT</version>
     </dependency>
   </dependencies>
 
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/feature.xml
index ae62fa0..e039647 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.ssh.apache"
       label="%featureName"
-      version="6.0.1.qualifier"
+      version="6.1.1.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
@@ -23,7 +23,7 @@
    </url>
 
    <requires>
-      <import feature="org.eclipse.jgit" version="6.0.1" match="equivalent"/>
+      <import feature="org.eclipse.jgit" version="6.1.1" match="equivalent"/>
    </requires>
 
    <plugin
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/pom.xml
index 6f12afa..591b448 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.jsch.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.jsch.feature/feature.xml
index df42e93..3df4a92 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.jsch.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.jsch.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.ssh.jsch"
       label="%featureName"
-      version="6.0.1.qualifier"
+      version="6.1.1.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
@@ -23,7 +23,7 @@
    </url>
 
    <requires>
-      <import plugin="org.eclipse.jgit" version="6.0.1" match="equivalent"/>
+      <import plugin="org.eclipse.jgit" version="6.1.1" match="equivalent"/>
    </requires>
 
    <plugin
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.jsch.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.jsch.feature/pom.xml
index 37ada5d..e461bff 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.jsch.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.jsch.feature/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/META-INF/MANIFEST.MF b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/META-INF/MANIFEST.MF
index d31cd37..5d61eef 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/META-INF/MANIFEST.MF
@@ -2,4 +2,4 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: JGit Target Platform Bundle
 Bundle-SymbolicName: org.eclipse.jgit.target
-Bundle-Version: 6.0.1.qualifier
+Bundle-Version: 6.1.1.qualifier
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 91370cc..4e40232 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="1641393954">
+<target name="jgit-4.17" sequenceNumber="1646256653">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="jakarta.servlet-api" version="4.0.0"/>
@@ -23,8 +23,8 @@
       <repository id="jetty-10.0.x" location="https://download.eclipse.org/eclipse/jetty/10.0.6/"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="com.google.gson" version="2.8.8.v20211029-0838"/>
-      <unit id="com.google.gson.source" version="2.8.8.v20211029-0838"/>
+      <unit id="com.google.gson" version="2.8.9.v20220111-1409"/>
+      <unit id="com.google.gson.source" version="2.8.9.v20220111-1409"/>
       <unit id="com.jcraft.jsch" version="0.1.55.v20190404-1902"/>
       <unit id="com.jcraft.jsch.source" version="0.1.55.v20190404-1902"/>
       <unit id="com.jcraft.jzlib" version="1.1.1.v201205102305"/>
@@ -51,22 +51,22 @@
       <unit id="org.apache.commons.logging.source" version="1.2.0.v20180409-1502"/>
       <unit id="org.apache.httpcomponents.httpclient" version="4.5.13.v20210128-2225"/>
       <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.13.v20210128-2225"/>
-      <unit id="org.apache.httpcomponents.httpcore" version="4.4.14.v20210128-2225"/>
-      <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.14.v20210128-2225"/>
-      <unit id="org.apache.sshd.osgi" version="2.7.0.v20210623-0618"/>
-      <unit id="org.apache.sshd.osgi.source" version="2.7.0.v20210623-0618"/>
-      <unit id="org.apache.sshd.sftp" version="2.7.0.v20210623-0618"/>
-      <unit id="org.apache.sshd.sftp.source" version="2.7.0.v20210623-0618"/>
+      <unit id="org.apache.httpcomponents.httpcore" version="4.4.15.v20220209-2345"/>
+      <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.15.v20220209-2345"/>
+      <unit id="org.apache.sshd.osgi" version="2.8.0.v20211227-1750"/>
+      <unit id="org.apache.sshd.osgi.source" version="2.8.0.v20211227-1750"/>
+      <unit id="org.apache.sshd.sftp" version="2.8.0.v20211227-1750"/>
+      <unit id="org.apache.sshd.sftp.source" version="2.8.0.v20211227-1750"/>
       <unit id="org.assertj" version="3.20.2.v20210706-1104"/>
       <unit id="org.assertj.source" version="3.20.2.v20210706-1104"/>
-      <unit id="org.bouncycastle.bcpg" version="1.69.0.v20210713-1924"/>
-      <unit id="org.bouncycastle.bcpg.source" version="1.69.0.v20210713-1924"/>
-      <unit id="org.bouncycastle.bcpkix" version="1.69.0.v20210713-1924"/>
-      <unit id="org.bouncycastle.bcpkix.source" version="1.69.0.v20210713-1924"/>
-      <unit id="org.bouncycastle.bcprov" version="1.69.0.v20210923-1401"/>
-      <unit id="org.bouncycastle.bcprov.source" version="1.69.0.v20210923-1401"/>
-      <unit id="org.bouncycastle.bcutil" version="1.69.0.v20210713-1924"/>
-      <unit id="org.bouncycastle.bcutil.source" version="1.69.0.v20210713-1924"/>
+      <unit id="org.bouncycastle.bcpg" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcpg.source" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcpkix" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcpkix.source" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcprov" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcprov.source" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcutil" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcutil.source" version="1.70.0.v20220105-1522"/>
       <unit id="org.hamcrest" version="2.2.0.v20210711-0821"/>
       <unit id="org.hamcrest.source" version="2.2.0.v20210711-0821"/>
       <unit id="org.hamcrest.core" version="1.3.0.v20180420-1519"/>
@@ -87,7 +87,7 @@
       <unit id="org.slf4j.binding.simple.source" version="1.7.30.v20200204-2150"/>
       <unit id="org.tukaani.xz" version="1.9.0.v20210624-1259"/>
       <unit id="org.tukaani.xz.source" version="1.9.0.v20210624-1259"/>
-      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/R20211213173813/repository"/>
+      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/R20220302172233/repository"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.osgi" version="0.0.0"/>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.tpd
index c2b13cc..dbb450a 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.tpd
@@ -1,7 +1,7 @@
 target "jgit-4.17" with source configurePhase
 
 include "projects/jetty-10.0.x.tpd"
-include "orbit/R20211213173813-2021-12.tpd"
+include "orbit/R20220302172233-2022-03.tpd"
 
 location "https://download.eclipse.org/releases/2020-09/" {
 	org.eclipse.osgi lazy
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 28204f5..1628217 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="1641394122">
+<target name="jgit-4.18" sequenceNumber="1646256653">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="jakarta.servlet-api" version="4.0.0"/>
@@ -23,8 +23,8 @@
       <repository id="jetty-10.0.x" location="https://download.eclipse.org/eclipse/jetty/10.0.6/"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="com.google.gson" version="2.8.8.v20211029-0838"/>
-      <unit id="com.google.gson.source" version="2.8.8.v20211029-0838"/>
+      <unit id="com.google.gson" version="2.8.9.v20220111-1409"/>
+      <unit id="com.google.gson.source" version="2.8.9.v20220111-1409"/>
       <unit id="com.jcraft.jsch" version="0.1.55.v20190404-1902"/>
       <unit id="com.jcraft.jsch.source" version="0.1.55.v20190404-1902"/>
       <unit id="com.jcraft.jzlib" version="1.1.1.v201205102305"/>
@@ -51,22 +51,22 @@
       <unit id="org.apache.commons.logging.source" version="1.2.0.v20180409-1502"/>
       <unit id="org.apache.httpcomponents.httpclient" version="4.5.13.v20210128-2225"/>
       <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.13.v20210128-2225"/>
-      <unit id="org.apache.httpcomponents.httpcore" version="4.4.14.v20210128-2225"/>
-      <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.14.v20210128-2225"/>
-      <unit id="org.apache.sshd.osgi" version="2.7.0.v20210623-0618"/>
-      <unit id="org.apache.sshd.osgi.source" version="2.7.0.v20210623-0618"/>
-      <unit id="org.apache.sshd.sftp" version="2.7.0.v20210623-0618"/>
-      <unit id="org.apache.sshd.sftp.source" version="2.7.0.v20210623-0618"/>
+      <unit id="org.apache.httpcomponents.httpcore" version="4.4.15.v20220209-2345"/>
+      <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.15.v20220209-2345"/>
+      <unit id="org.apache.sshd.osgi" version="2.8.0.v20211227-1750"/>
+      <unit id="org.apache.sshd.osgi.source" version="2.8.0.v20211227-1750"/>
+      <unit id="org.apache.sshd.sftp" version="2.8.0.v20211227-1750"/>
+      <unit id="org.apache.sshd.sftp.source" version="2.8.0.v20211227-1750"/>
       <unit id="org.assertj" version="3.20.2.v20210706-1104"/>
       <unit id="org.assertj.source" version="3.20.2.v20210706-1104"/>
-      <unit id="org.bouncycastle.bcpg" version="1.69.0.v20210713-1924"/>
-      <unit id="org.bouncycastle.bcpg.source" version="1.69.0.v20210713-1924"/>
-      <unit id="org.bouncycastle.bcpkix" version="1.69.0.v20210713-1924"/>
-      <unit id="org.bouncycastle.bcpkix.source" version="1.69.0.v20210713-1924"/>
-      <unit id="org.bouncycastle.bcprov" version="1.69.0.v20210923-1401"/>
-      <unit id="org.bouncycastle.bcprov.source" version="1.69.0.v20210923-1401"/>
-      <unit id="org.bouncycastle.bcutil" version="1.69.0.v20210713-1924"/>
-      <unit id="org.bouncycastle.bcutil.source" version="1.69.0.v20210713-1924"/>
+      <unit id="org.bouncycastle.bcpg" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcpg.source" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcpkix" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcpkix.source" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcprov" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcprov.source" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcutil" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcutil.source" version="1.70.0.v20220105-1522"/>
       <unit id="org.hamcrest" version="2.2.0.v20210711-0821"/>
       <unit id="org.hamcrest.source" version="2.2.0.v20210711-0821"/>
       <unit id="org.hamcrest.core" version="1.3.0.v20180420-1519"/>
@@ -87,7 +87,7 @@
       <unit id="org.slf4j.binding.simple.source" version="1.7.30.v20200204-2150"/>
       <unit id="org.tukaani.xz" version="1.9.0.v20210624-1259"/>
       <unit id="org.tukaani.xz.source" version="1.9.0.v20210624-1259"/>
-      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/R20211213173813/repository"/>
+      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/R20220302172233/repository"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.osgi" version="0.0.0"/>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.tpd
index ff6741d..911c67c 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.tpd
@@ -1,7 +1,7 @@
 target "jgit-4.18" with source configurePhase
 
 include "projects/jetty-10.0.x.tpd"
-include "orbit/R20211213173813-2021-12.tpd"
+include "orbit/R20220302172233-2022-03.tpd"
 
 location "https://download.eclipse.org/releases/2020-12/" {
 	org.eclipse.osgi lazy
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 20b0529..ab18f7b 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="1641394141">
+<target name="jgit-4.19-staging" sequenceNumber="1646256653">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="jakarta.servlet-api" version="4.0.0"/>
@@ -23,8 +23,8 @@
       <repository id="jetty-10.0.x" location="https://download.eclipse.org/eclipse/jetty/10.0.6/"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="com.google.gson" version="2.8.8.v20211029-0838"/>
-      <unit id="com.google.gson.source" version="2.8.8.v20211029-0838"/>
+      <unit id="com.google.gson" version="2.8.9.v20220111-1409"/>
+      <unit id="com.google.gson.source" version="2.8.9.v20220111-1409"/>
       <unit id="com.jcraft.jsch" version="0.1.55.v20190404-1902"/>
       <unit id="com.jcraft.jsch.source" version="0.1.55.v20190404-1902"/>
       <unit id="com.jcraft.jzlib" version="1.1.1.v201205102305"/>
@@ -51,22 +51,22 @@
       <unit id="org.apache.commons.logging.source" version="1.2.0.v20180409-1502"/>
       <unit id="org.apache.httpcomponents.httpclient" version="4.5.13.v20210128-2225"/>
       <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.13.v20210128-2225"/>
-      <unit id="org.apache.httpcomponents.httpcore" version="4.4.14.v20210128-2225"/>
-      <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.14.v20210128-2225"/>
-      <unit id="org.apache.sshd.osgi" version="2.7.0.v20210623-0618"/>
-      <unit id="org.apache.sshd.osgi.source" version="2.7.0.v20210623-0618"/>
-      <unit id="org.apache.sshd.sftp" version="2.7.0.v20210623-0618"/>
-      <unit id="org.apache.sshd.sftp.source" version="2.7.0.v20210623-0618"/>
+      <unit id="org.apache.httpcomponents.httpcore" version="4.4.15.v20220209-2345"/>
+      <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.15.v20220209-2345"/>
+      <unit id="org.apache.sshd.osgi" version="2.8.0.v20211227-1750"/>
+      <unit id="org.apache.sshd.osgi.source" version="2.8.0.v20211227-1750"/>
+      <unit id="org.apache.sshd.sftp" version="2.8.0.v20211227-1750"/>
+      <unit id="org.apache.sshd.sftp.source" version="2.8.0.v20211227-1750"/>
       <unit id="org.assertj" version="3.20.2.v20210706-1104"/>
       <unit id="org.assertj.source" version="3.20.2.v20210706-1104"/>
-      <unit id="org.bouncycastle.bcpg" version="1.69.0.v20210713-1924"/>
-      <unit id="org.bouncycastle.bcpg.source" version="1.69.0.v20210713-1924"/>
-      <unit id="org.bouncycastle.bcpkix" version="1.69.0.v20210713-1924"/>
-      <unit id="org.bouncycastle.bcpkix.source" version="1.69.0.v20210713-1924"/>
-      <unit id="org.bouncycastle.bcprov" version="1.69.0.v20210923-1401"/>
-      <unit id="org.bouncycastle.bcprov.source" version="1.69.0.v20210923-1401"/>
-      <unit id="org.bouncycastle.bcutil" version="1.69.0.v20210713-1924"/>
-      <unit id="org.bouncycastle.bcutil.source" version="1.69.0.v20210713-1924"/>
+      <unit id="org.bouncycastle.bcpg" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcpg.source" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcpkix" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcpkix.source" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcprov" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcprov.source" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcutil" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcutil.source" version="1.70.0.v20220105-1522"/>
       <unit id="org.hamcrest" version="2.2.0.v20210711-0821"/>
       <unit id="org.hamcrest.source" version="2.2.0.v20210711-0821"/>
       <unit id="org.hamcrest.core" version="1.3.0.v20180420-1519"/>
@@ -87,7 +87,7 @@
       <unit id="org.slf4j.binding.simple.source" version="1.7.30.v20200204-2150"/>
       <unit id="org.tukaani.xz" version="1.9.0.v20210624-1259"/>
       <unit id="org.tukaani.xz.source" version="1.9.0.v20210624-1259"/>
-      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/R20211213173813/repository"/>
+      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/R20220302172233/repository"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.osgi" version="0.0.0"/>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.tpd
index 8fcaa47..fdb8b11 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.tpd
@@ -1,7 +1,7 @@
 target "jgit-4.19-staging" with source configurePhase
 
 include "projects/jetty-10.0.x.tpd"
-include "orbit/R20211213173813-2021-12.tpd"
+include "orbit/R20220302172233-2022-03.tpd"
 
 location "https://download.eclipse.org/staging/2021-03/" {
 	org.eclipse.osgi lazy
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 153314d..4c840dc 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="1641394152">
+<target name="jgit-4.20" sequenceNumber="1646256653">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="jakarta.servlet-api" version="4.0.0"/>
@@ -23,8 +23,8 @@
       <repository id="jetty-10.0.x" location="https://download.eclipse.org/eclipse/jetty/10.0.6/"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="com.google.gson" version="2.8.8.v20211029-0838"/>
-      <unit id="com.google.gson.source" version="2.8.8.v20211029-0838"/>
+      <unit id="com.google.gson" version="2.8.9.v20220111-1409"/>
+      <unit id="com.google.gson.source" version="2.8.9.v20220111-1409"/>
       <unit id="com.jcraft.jsch" version="0.1.55.v20190404-1902"/>
       <unit id="com.jcraft.jsch.source" version="0.1.55.v20190404-1902"/>
       <unit id="com.jcraft.jzlib" version="1.1.1.v201205102305"/>
@@ -51,22 +51,22 @@
       <unit id="org.apache.commons.logging.source" version="1.2.0.v20180409-1502"/>
       <unit id="org.apache.httpcomponents.httpclient" version="4.5.13.v20210128-2225"/>
       <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.13.v20210128-2225"/>
-      <unit id="org.apache.httpcomponents.httpcore" version="4.4.14.v20210128-2225"/>
-      <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.14.v20210128-2225"/>
-      <unit id="org.apache.sshd.osgi" version="2.7.0.v20210623-0618"/>
-      <unit id="org.apache.sshd.osgi.source" version="2.7.0.v20210623-0618"/>
-      <unit id="org.apache.sshd.sftp" version="2.7.0.v20210623-0618"/>
-      <unit id="org.apache.sshd.sftp.source" version="2.7.0.v20210623-0618"/>
+      <unit id="org.apache.httpcomponents.httpcore" version="4.4.15.v20220209-2345"/>
+      <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.15.v20220209-2345"/>
+      <unit id="org.apache.sshd.osgi" version="2.8.0.v20211227-1750"/>
+      <unit id="org.apache.sshd.osgi.source" version="2.8.0.v20211227-1750"/>
+      <unit id="org.apache.sshd.sftp" version="2.8.0.v20211227-1750"/>
+      <unit id="org.apache.sshd.sftp.source" version="2.8.0.v20211227-1750"/>
       <unit id="org.assertj" version="3.20.2.v20210706-1104"/>
       <unit id="org.assertj.source" version="3.20.2.v20210706-1104"/>
-      <unit id="org.bouncycastle.bcpg" version="1.69.0.v20210713-1924"/>
-      <unit id="org.bouncycastle.bcpg.source" version="1.69.0.v20210713-1924"/>
-      <unit id="org.bouncycastle.bcpkix" version="1.69.0.v20210713-1924"/>
-      <unit id="org.bouncycastle.bcpkix.source" version="1.69.0.v20210713-1924"/>
-      <unit id="org.bouncycastle.bcprov" version="1.69.0.v20210923-1401"/>
-      <unit id="org.bouncycastle.bcprov.source" version="1.69.0.v20210923-1401"/>
-      <unit id="org.bouncycastle.bcutil" version="1.69.0.v20210713-1924"/>
-      <unit id="org.bouncycastle.bcutil.source" version="1.69.0.v20210713-1924"/>
+      <unit id="org.bouncycastle.bcpg" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcpg.source" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcpkix" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcpkix.source" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcprov" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcprov.source" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcutil" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcutil.source" version="1.70.0.v20220105-1522"/>
       <unit id="org.hamcrest" version="2.2.0.v20210711-0821"/>
       <unit id="org.hamcrest.source" version="2.2.0.v20210711-0821"/>
       <unit id="org.hamcrest.core" version="1.3.0.v20180420-1519"/>
@@ -87,7 +87,7 @@
       <unit id="org.slf4j.binding.simple.source" version="1.7.30.v20200204-2150"/>
       <unit id="org.tukaani.xz" version="1.9.0.v20210624-1259"/>
       <unit id="org.tukaani.xz.source" version="1.9.0.v20210624-1259"/>
-      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/R20211213173813/repository"/>
+      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/R20220302172233/repository"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.osgi" version="0.0.0"/>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20.tpd
index 33adb72..120ee64 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20.tpd
@@ -1,7 +1,7 @@
 target "jgit-4.20" with source configurePhase
 
 include "projects/jetty-10.0.x.tpd"
-include "orbit/R20211213173813-2021-12.tpd"
+include "orbit/R20220302172233-2022-03.tpd"
 
 location "https://download.eclipse.org/releases/2021-06/" {
 	org.eclipse.osgi lazy
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 07de1af..7e8cd91 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="1641394170">
+<target name="jgit-4.21" sequenceNumber="1646256653">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="jakarta.servlet-api" version="4.0.0"/>
@@ -23,8 +23,8 @@
       <repository id="jetty-10.0.x" location="https://download.eclipse.org/eclipse/jetty/10.0.6/"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="com.google.gson" version="2.8.8.v20211029-0838"/>
-      <unit id="com.google.gson.source" version="2.8.8.v20211029-0838"/>
+      <unit id="com.google.gson" version="2.8.9.v20220111-1409"/>
+      <unit id="com.google.gson.source" version="2.8.9.v20220111-1409"/>
       <unit id="com.jcraft.jsch" version="0.1.55.v20190404-1902"/>
       <unit id="com.jcraft.jsch.source" version="0.1.55.v20190404-1902"/>
       <unit id="com.jcraft.jzlib" version="1.1.1.v201205102305"/>
@@ -51,22 +51,22 @@
       <unit id="org.apache.commons.logging.source" version="1.2.0.v20180409-1502"/>
       <unit id="org.apache.httpcomponents.httpclient" version="4.5.13.v20210128-2225"/>
       <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.13.v20210128-2225"/>
-      <unit id="org.apache.httpcomponents.httpcore" version="4.4.14.v20210128-2225"/>
-      <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.14.v20210128-2225"/>
-      <unit id="org.apache.sshd.osgi" version="2.7.0.v20210623-0618"/>
-      <unit id="org.apache.sshd.osgi.source" version="2.7.0.v20210623-0618"/>
-      <unit id="org.apache.sshd.sftp" version="2.7.0.v20210623-0618"/>
-      <unit id="org.apache.sshd.sftp.source" version="2.7.0.v20210623-0618"/>
+      <unit id="org.apache.httpcomponents.httpcore" version="4.4.15.v20220209-2345"/>
+      <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.15.v20220209-2345"/>
+      <unit id="org.apache.sshd.osgi" version="2.8.0.v20211227-1750"/>
+      <unit id="org.apache.sshd.osgi.source" version="2.8.0.v20211227-1750"/>
+      <unit id="org.apache.sshd.sftp" version="2.8.0.v20211227-1750"/>
+      <unit id="org.apache.sshd.sftp.source" version="2.8.0.v20211227-1750"/>
       <unit id="org.assertj" version="3.20.2.v20210706-1104"/>
       <unit id="org.assertj.source" version="3.20.2.v20210706-1104"/>
-      <unit id="org.bouncycastle.bcpg" version="1.69.0.v20210713-1924"/>
-      <unit id="org.bouncycastle.bcpg.source" version="1.69.0.v20210713-1924"/>
-      <unit id="org.bouncycastle.bcpkix" version="1.69.0.v20210713-1924"/>
-      <unit id="org.bouncycastle.bcpkix.source" version="1.69.0.v20210713-1924"/>
-      <unit id="org.bouncycastle.bcprov" version="1.69.0.v20210923-1401"/>
-      <unit id="org.bouncycastle.bcprov.source" version="1.69.0.v20210923-1401"/>
-      <unit id="org.bouncycastle.bcutil" version="1.69.0.v20210713-1924"/>
-      <unit id="org.bouncycastle.bcutil.source" version="1.69.0.v20210713-1924"/>
+      <unit id="org.bouncycastle.bcpg" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcpg.source" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcpkix" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcpkix.source" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcprov" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcprov.source" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcutil" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcutil.source" version="1.70.0.v20220105-1522"/>
       <unit id="org.hamcrest" version="2.2.0.v20210711-0821"/>
       <unit id="org.hamcrest.source" version="2.2.0.v20210711-0821"/>
       <unit id="org.hamcrest.core" version="1.3.0.v20180420-1519"/>
@@ -87,7 +87,7 @@
       <unit id="org.slf4j.binding.simple.source" version="1.7.30.v20200204-2150"/>
       <unit id="org.tukaani.xz" version="1.9.0.v20210624-1259"/>
       <unit id="org.tukaani.xz.source" version="1.9.0.v20210624-1259"/>
-      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/R20211213173813/repository"/>
+      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/R20220302172233/repository"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.osgi" version="0.0.0"/>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.21.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.21.tpd
index adf62d5..0ec2a52 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.21.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.21.tpd
@@ -1,7 +1,7 @@
 target "jgit-4.21" with source configurePhase
 
 include "projects/jetty-10.0.x.tpd"
-include "orbit/R20211213173813-2021-12.tpd"
+include "orbit/R20220302172233-2022-03.tpd"
 
 location "https://download.eclipse.org/releases/2021-09/" {
 	org.eclipse.osgi lazy
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 97b5fb6..b229da1 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="1641394177">
+<target name="jgit-4.22" sequenceNumber="1646256653">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="jakarta.servlet-api" version="4.0.0"/>
@@ -23,8 +23,8 @@
       <repository id="jetty-10.0.x" location="https://download.eclipse.org/eclipse/jetty/10.0.6/"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="com.google.gson" version="2.8.8.v20211029-0838"/>
-      <unit id="com.google.gson.source" version="2.8.8.v20211029-0838"/>
+      <unit id="com.google.gson" version="2.8.9.v20220111-1409"/>
+      <unit id="com.google.gson.source" version="2.8.9.v20220111-1409"/>
       <unit id="com.jcraft.jsch" version="0.1.55.v20190404-1902"/>
       <unit id="com.jcraft.jsch.source" version="0.1.55.v20190404-1902"/>
       <unit id="com.jcraft.jzlib" version="1.1.1.v201205102305"/>
@@ -51,22 +51,22 @@
       <unit id="org.apache.commons.logging.source" version="1.2.0.v20180409-1502"/>
       <unit id="org.apache.httpcomponents.httpclient" version="4.5.13.v20210128-2225"/>
       <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.13.v20210128-2225"/>
-      <unit id="org.apache.httpcomponents.httpcore" version="4.4.14.v20210128-2225"/>
-      <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.14.v20210128-2225"/>
-      <unit id="org.apache.sshd.osgi" version="2.7.0.v20210623-0618"/>
-      <unit id="org.apache.sshd.osgi.source" version="2.7.0.v20210623-0618"/>
-      <unit id="org.apache.sshd.sftp" version="2.7.0.v20210623-0618"/>
-      <unit id="org.apache.sshd.sftp.source" version="2.7.0.v20210623-0618"/>
+      <unit id="org.apache.httpcomponents.httpcore" version="4.4.15.v20220209-2345"/>
+      <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.15.v20220209-2345"/>
+      <unit id="org.apache.sshd.osgi" version="2.8.0.v20211227-1750"/>
+      <unit id="org.apache.sshd.osgi.source" version="2.8.0.v20211227-1750"/>
+      <unit id="org.apache.sshd.sftp" version="2.8.0.v20211227-1750"/>
+      <unit id="org.apache.sshd.sftp.source" version="2.8.0.v20211227-1750"/>
       <unit id="org.assertj" version="3.20.2.v20210706-1104"/>
       <unit id="org.assertj.source" version="3.20.2.v20210706-1104"/>
-      <unit id="org.bouncycastle.bcpg" version="1.69.0.v20210713-1924"/>
-      <unit id="org.bouncycastle.bcpg.source" version="1.69.0.v20210713-1924"/>
-      <unit id="org.bouncycastle.bcpkix" version="1.69.0.v20210713-1924"/>
-      <unit id="org.bouncycastle.bcpkix.source" version="1.69.0.v20210713-1924"/>
-      <unit id="org.bouncycastle.bcprov" version="1.69.0.v20210923-1401"/>
-      <unit id="org.bouncycastle.bcprov.source" version="1.69.0.v20210923-1401"/>
-      <unit id="org.bouncycastle.bcutil" version="1.69.0.v20210713-1924"/>
-      <unit id="org.bouncycastle.bcutil.source" version="1.69.0.v20210713-1924"/>
+      <unit id="org.bouncycastle.bcpg" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcpg.source" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcpkix" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcpkix.source" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcprov" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcprov.source" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcutil" version="1.70.0.v20220105-1522"/>
+      <unit id="org.bouncycastle.bcutil.source" version="1.70.0.v20220105-1522"/>
       <unit id="org.hamcrest" version="2.2.0.v20210711-0821"/>
       <unit id="org.hamcrest.source" version="2.2.0.v20210711-0821"/>
       <unit id="org.hamcrest.core" version="1.3.0.v20180420-1519"/>
@@ -87,7 +87,7 @@
       <unit id="org.slf4j.binding.simple.source" version="1.7.30.v20200204-2150"/>
       <unit id="org.tukaani.xz" version="1.9.0.v20210624-1259"/>
       <unit id="org.tukaani.xz.source" version="1.9.0.v20210624-1259"/>
-      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/R20211213173813/repository"/>
+      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/R20220302172233/repository"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.osgi" version="0.0.0"/>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.22.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.22.tpd
index 37c93a7..eb1723c 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.22.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.22.tpd
@@ -1,7 +1,7 @@
 target "jgit-4.22" with source configurePhase
 
 include "projects/jetty-10.0.x.tpd"
-include "orbit/R20211213173813-2021-12.tpd"
+include "orbit/R20220302172233-2022-03.tpd"
 
 location "https://download.eclipse.org/releases/2021-12/" {
 	org.eclipse.osgi lazy
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20220302172233-2022-03.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20220302172233-2022-03.tpd
new file mode 100644
index 0000000..fafc689
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20220302172233-2022-03.tpd
@@ -0,0 +1,69 @@
+target "R20220302172233" with source configurePhase
+// see https://download.eclipse.org/tools/orbit/downloads/
+
+location "https://download.eclipse.org/tools/orbit/downloads/drops/R20220302172233/repository" {
+	com.google.gson [2.8.9.v20220111-1409,2.8.9.v20220111-1409]
+	com.google.gson.source [2.8.9.v20220111-1409,2.8.9.v20220111-1409]
+	com.jcraft.jsch [0.1.55.v20190404-1902,0.1.55.v20190404-1902]
+	com.jcraft.jsch.source [0.1.55.v20190404-1902,0.1.55.v20190404-1902]
+	com.jcraft.jzlib [1.1.1.v201205102305,1.1.1.v201205102305]
+	com.jcraft.jzlib.source [1.1.1.v201205102305,1.1.1.v201205102305]
+	com.sun.jna [5.8.0.v20210503-0343,5.8.0.v20210503-0343]
+	com.sun.jna.source [5.8.0.v20210503-0343,5.8.0.v20210503-0343]
+	com.sun.jna.platform [5.8.0.v20210406-1004,5.8.0.v20210406-1004]
+	com.sun.jna.platform.source [5.8.0.v20210406-1004,5.8.0.v20210406-1004]
+	javaewah [1.1.13.v20211029-0839,1.1.13.v20211029-0839]
+	javaewah.source [1.1.13.v20211029-0839,1.1.13.v20211029-0839]
+	net.bytebuddy.byte-buddy [1.9.0.v20181107-1410,1.9.0.v20181107-1410]
+	net.bytebuddy.byte-buddy-agent [1.9.0.v20181106-1534,1.9.0.v20181106-1534]
+	net.bytebuddy.byte-buddy-agent.source [1.9.0.v20181106-1534,1.9.0.v20181106-1534]
+	net.bytebuddy.byte-buddy.source [1.9.0.v20181107-1410,1.9.0.v20181107-1410]
+	net.i2p.crypto.eddsa [0.3.0.v20210923-1401,0.3.0.v20210923-1401]
+	net.i2p.crypto.eddsa.source [0.3.0.v20210923-1401,0.3.0.v20210923-1401]
+	org.apache.ant [1.10.12.v20211102-1452,1.10.12.v20211102-1452]
+	org.apache.ant.source [1.10.12.v20211102-1452,1.10.12.v20211102-1452]
+	org.apache.commons.codec [1.14.0.v20200818-1422,1.14.0.v20200818-1422]
+	org.apache.commons.codec.source [1.14.0.v20200818-1422,1.14.0.v20200818-1422]
+	org.apache.commons.compress [1.21.0.v20211103-2100,1.21.0.v20211103-2100]
+	org.apache.commons.compress.source [1.21.0.v20211103-2100,1.21.0.v20211103-2100]
+	org.apache.commons.logging [1.2.0.v20180409-1502,1.2.0.v20180409-1502]
+	org.apache.commons.logging.source [1.2.0.v20180409-1502,1.2.0.v20180409-1502]
+	org.apache.httpcomponents.httpclient [4.5.13.v20210128-2225,4.5.13.v20210128-2225]
+	org.apache.httpcomponents.httpclient.source [4.5.13.v20210128-2225,4.5.13.v20210128-2225]
+	org.apache.httpcomponents.httpcore [4.4.15.v20220209-2345,4.4.15.v20220209-2345]
+	org.apache.httpcomponents.httpcore.source [4.4.15.v20220209-2345,4.4.15.v20220209-2345]
+	org.apache.sshd.osgi [2.8.0.v20211227-1750,2.8.0.v20211227-1750]
+	org.apache.sshd.osgi.source [2.8.0.v20211227-1750,2.8.0.v20211227-1750]
+	org.apache.sshd.sftp [2.8.0.v20211227-1750,2.8.0.v20211227-1750]
+	org.apache.sshd.sftp.source [2.8.0.v20211227-1750,2.8.0.v20211227-1750]
+	org.assertj [3.20.2.v20210706-1104,3.20.2.v20210706-1104]
+	org.assertj.source [3.20.2.v20210706-1104,3.20.2.v20210706-1104]
+	org.bouncycastle.bcpg [1.70.0.v20220105-1522,1.70.0.v20220105-1522]
+	org.bouncycastle.bcpg.source [1.70.0.v20220105-1522,1.70.0.v20220105-1522]
+	org.bouncycastle.bcpkix [1.70.0.v20220105-1522,1.70.0.v20220105-1522]
+	org.bouncycastle.bcpkix.source [1.70.0.v20220105-1522,1.70.0.v20220105-1522]
+	org.bouncycastle.bcprov [1.70.0.v20220105-1522,1.70.0.v20220105-1522]
+	org.bouncycastle.bcprov.source [1.70.0.v20220105-1522,1.70.0.v20220105-1522]
+	org.bouncycastle.bcutil [1.70.0.v20220105-1522,1.70.0.v20220105-1522]
+	org.bouncycastle.bcutil.source [1.70.0.v20220105-1522,1.70.0.v20220105-1522]
+	org.hamcrest [2.2.0.v20210711-0821,2.2.0.v20210711-0821]
+	org.hamcrest.source [2.2.0.v20210711-0821,2.2.0.v20210711-0821]
+	org.hamcrest.core [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
+	org.hamcrest.core.source [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
+	org.hamcrest.library [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
+	org.hamcrest.library.source [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
+	org.junit [4.13.2.v20211018-1956,4.13.2.v20211018-1956]
+	org.junit.source [4.13.2.v20211018-1956,4.13.2.v20211018-1956]
+	org.kohsuke.args4j [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
+	org.kohsuke.args4j.source [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
+	org.mockito [2.23.0.v20200310-1642,2.23.0.v20200310-1642]
+	org.mockito.source [2.23.0.v20200310-1642,2.23.0.v20200310-1642]
+	org.objenesis [2.6.0.v20180420-1519,2.6.0.v20180420-1519]
+	org.objenesis.source [2.6.0.v20180420-1519,2.6.0.v20180420-1519]
+	org.slf4j.api [1.7.30.v20200204-2150,1.7.30.v20200204-2150]
+	org.slf4j.api.source [1.7.30.v20200204-2150,1.7.30.v20200204-2150]
+	org.slf4j.binding.simple [1.7.30.v20200204-2150,1.7.30.v20200204-2150]
+	org.slf4j.binding.simple.source [1.7.30.v20200204-2150,1.7.30.v20200204-2150]
+	org.tukaani.xz [1.9.0.v20210624-1259,1.9.0.v20210624-1259]
+	org.tukaani.xz.source [1.9.0.v20210624-1259,1.9.0.v20210624-1259]
+}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml
index 8bee1fc..2a95042 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml
@@ -16,7 +16,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.target</artifactId>
diff --git a/org.eclipse.jgit.packaging/pom.xml b/org.eclipse.jgit.packaging/pom.xml
index 80a64bd..b650598 100644
--- a/org.eclipse.jgit.packaging/pom.xml
+++ b/org.eclipse.jgit.packaging/pom.xml
@@ -16,7 +16,7 @@
 
   <groupId>org.eclipse.jgit</groupId>
   <artifactId>jgit.tycho.parent</artifactId>
-  <version>6.0.1-SNAPSHOT</version>
+  <version>6.1.1-SNAPSHOT</version>
   <packaging>pom</packaging>
 
   <name>JGit Tycho Parent</name>
diff --git a/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF
index af00b0a..f5c4de4 100644
--- a/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF
@@ -3,28 +3,30 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.pgm.test
 Bundle-SymbolicName: org.eclipse.jgit.pgm.test
-Bundle-Version: 6.0.1.qualifier
+Bundle-Version: 6.1.1.qualifier
 Bundle-Vendor: %Bundle-Vendor
 Bundle-Localization: plugin
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-11
-Import-Package: org.eclipse.jgit.api;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.api.errors;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.diff;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.dircache;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.storage.file;version="6.0.1",
- org.eclipse.jgit.junit;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lib;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.merge;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.pgm;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.pgm.internal;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.pgm.opt;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.revwalk;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.storage.file;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.treewalk;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.util;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.util.io;version="[6.0.1,6.1.0)",
+Import-Package: org.eclipse.jgit.api;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.api.errors;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.diff;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.dircache;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.diffmergetool;version="6.1.1",
+ org.eclipse.jgit.internal.storage.file;version="6.1.1",
+ org.eclipse.jgit.junit;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lib;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lib.internal;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.merge;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.pgm;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.pgm.internal;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.pgm.opt;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.revwalk;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.storage.file;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.treewalk;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.util;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.util.io;version="[6.1.1,6.2.0)",
  org.hamcrest.core;bundle-version="[2.2.0,3.0.0)",
  org.junit;version="[4.13,5.0.0)",
  org.junit.rules;version="[4.13,5.0.0)",
diff --git a/org.eclipse.jgit.pgm.test/pom.xml b/org.eclipse.jgit.pgm.test/pom.xml
index cd2bb63..c824788 100644
--- a/org.eclipse.jgit.pgm.test/pom.xml
+++ b/org.eclipse.jgit.pgm.test/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.pgm.test</artifactId>
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DescribeTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DescribeTest.java
index 4bad73b..c785443 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DescribeTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DescribeTest.java
@@ -89,6 +89,31 @@
 	}
 
 	@Test
+	public void testDescribeCommitMatchAbbrev() throws Exception {
+		initialCommitAndTag();
+		secondCommit();
+		assertArrayEquals(new String[] { "v1.0-1-g56f6cebdf3f5", "" },
+				execute("git describe --abbrev 12 --match v1.*"));
+	}
+
+	@Test
+	public void testDescribeCommitMatchAbbrevMin() throws Exception {
+		initialCommitAndTag();
+		secondCommit();
+		assertArrayEquals(new String[] { "v1.0-1-g56f6", "" },
+				execute("git describe --abbrev -5 --match v1.*"));
+	}
+
+	@Test
+	public void testDescribeCommitMatchAbbrevMax() throws Exception {
+		initialCommitAndTag();
+		secondCommit();
+		assertArrayEquals(new String[] {
+				"v1.0-1-g56f6cebdf3f5ceeecd803365abf0996fb1fa006d", "" },
+				execute("git describe --abbrev 50 --match v1.*"));
+	}
+
+	@Test
 	public void testDescribeCommitMatch2() throws Exception {
 		initialCommitAndTag();
 		secondCommit();
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
new file mode 100644
index 0000000..e7bf484
--- /dev/null
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DiffToolTest.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2021, Simeon Andreev <simeon.danailov.andreev@gmail.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
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.pgm;
+
+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;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.treewalk.FileTreeIterator;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.junit.Before;
+import org.junit.Test;
+import org.kohsuke.args4j.Argument;
+
+/**
+ * Testing the {@code difftool} command.
+ */
+public class DiffToolTest extends CLIRepositoryTestCase {
+	public static class GitCliJGitWrapperParser {
+		@Argument(index = 0, metaVar = "metaVar_command", required = true, handler = SubcommandHandler.class)
+		TextBuiltin subcommand;
+
+		@Argument(index = 1, metaVar = "metaVar_arg")
+		List<String> arguments = new ArrayList<>();
+	}
+
+	private String[] runAndCaptureUsingInitRaw(String... args)
+			throws Exception {
+		CLIGitCommand.Result result = new CLIGitCommand.Result();
+
+		GitCliJGitWrapperParser bean = new GitCliJGitWrapperParser();
+		CmdLineParser clp = new CmdLineParser(bean);
+		clp.parseArgument(args);
+
+		TextBuiltin cmd = bean.subcommand;
+		cmd.initRaw(db, null, null, result.out, result.err);
+		cmd.execute(bean.arguments.toArray(new String[bean.arguments.size()]));
+		if (cmd.getOutputWriter() != null) {
+			cmd.getOutputWriter().flush();
+		}
+		if (cmd.getErrorWriter() != null) {
+			cmd.getErrorWriter().flush();
+		}
+		return result.outLines().toArray(new String[0]);
+	}
+
+	private Git git;
+
+	@Override
+	@Before
+	public void setUp() throws Exception {
+		super.setUp();
+		git = new Git(db);
+		git.commit().setMessage("initial commit").call();
+	}
+
+	@Test
+	public void testTool() throws Exception {
+		RevCommit commit = createUnstagedChanges();
+		List<DiffEntry> changes = getRepositoryChanges(commit);
+		String[] expectedOutput = getExpectedDiffToolOutput(changes);
+
+		String[] options = {
+				"--tool",
+				"-t",
+		};
+
+		for (String option : options) {
+			assertArrayOfLinesEquals("Incorrect output for option: " + option,
+					expectedOutput,
+					runAndCaptureUsingInitRaw("difftool", option,
+							"some_tool"));
+		}
+	}
+
+	@Test
+	public void testToolTrustExitCode() throws Exception {
+		RevCommit commit = createUnstagedChanges();
+		List<DiffEntry> changes = getRepositoryChanges(commit);
+		String[] expectedOutput = getExpectedDiffToolOutput(changes);
+
+		String[] options = { "--tool", "-t", };
+
+		for (String option : options) {
+			assertArrayOfLinesEquals("Incorrect output for option: " + option,
+					expectedOutput, runAndCaptureUsingInitRaw("difftool",
+							"--trust-exit-code", option, "some_tool"));
+		}
+	}
+
+	@Test
+	public void testToolNoGuiNoPromptNoTrustExitcode() throws Exception {
+		RevCommit commit = createUnstagedChanges();
+		List<DiffEntry> changes = getRepositoryChanges(commit);
+		String[] expectedOutput = getExpectedDiffToolOutput(changes);
+
+		String[] options = { "--tool", "-t", };
+
+		for (String option : options) {
+			assertArrayOfLinesEquals("Incorrect output for option: " + option,
+					expectedOutput, runAndCaptureUsingInitRaw("difftool",
+							"--no-gui", "--no-prompt", "--no-trust-exit-code",
+							option, "some_tool"));
+		}
+	}
+
+	@Test
+	public void testToolCached() throws Exception {
+		RevCommit commit = createStagedChanges();
+		List<DiffEntry> changes = getRepositoryChanges(commit);
+		String[] expectedOutput = getExpectedDiffToolOutput(changes);
+
+		String[] options = { "--cached", "--staged", };
+
+		for (String option : options) {
+			assertArrayOfLinesEquals("Incorrect output for option: " + option,
+					expectedOutput, runAndCaptureUsingInitRaw("difftool",
+							option, "--tool", "some_tool"));
+		}
+	}
+
+	@Test
+	public void testToolHelp() throws Exception {
+		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.",
+		};
+		expectedOutput.addAll(Arrays.asList(userDefinedToolsHelp));
+
+		String option = "--tool-help";
+		assertArrayOfLinesEquals("Incorrect output for option: " + option,
+				expectedOutput.toArray(new String[0]), runAndCaptureUsingInitRaw("difftool", option));
+	}
+
+	private RevCommit createUnstagedChanges() throws Exception {
+		writeTrashFile("a", "Hello world a");
+		writeTrashFile("b", "Hello world b");
+		git.add().addFilepattern(".").call();
+		RevCommit commit = git.commit().setMessage("files a & b").call();
+		writeTrashFile("a", "New Hello world a");
+		writeTrashFile("b", "New Hello world b");
+		return commit;
+	}
+
+	private RevCommit createStagedChanges() throws Exception {
+		RevCommit commit = createUnstagedChanges();
+		git.add().addFilepattern(".").call();
+		return commit;
+	}
+
+	private List<DiffEntry> getRepositoryChanges(RevCommit commit)
+			throws Exception {
+		TreeWalk tw = new TreeWalk(db);
+		tw.addTree(commit.getTree());
+		FileTreeIterator modifiedTree = new FileTreeIterator(db);
+		tw.addTree(modifiedTree);
+		List<DiffEntry> changes = DiffEntry.scan(tw);
+		return changes;
+	}
+
+	private String[] getExpectedDiffToolOutput(List<DiffEntry> changes) {
+		String[] expectedToolOutput = new String[changes.size()];
+		for (int i = 0; i < changes.size(); ++i) {
+			DiffEntry change = changes.get(i);
+			String newPath = change.getNewPath();
+			String oldPath = change.getOldPath();
+			String newIdName = change.getNewId().name();
+			String oldIdName = change.getOldId().name();
+			String expectedLine = "M\t" + newPath + " (" + newIdName + ")"
+					+ "\t" + oldPath + " (" + oldIdName + ")";
+			expectedToolOutput[i] = expectedLine;
+		}
+		return expectedToolOutput;
+	}
+
+	private static void assertArrayOfLinesEquals(String failMessage,
+			String[] expected, String[] actual) {
+		assertEquals(failMessage, toString(expected), toString(actual));
+	}
+}
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/FetchTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/FetchTest.java
index 564bd5f..1c6b783 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/FetchTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/FetchTest.java
@@ -33,6 +33,7 @@
 		git.commit().setMessage("initial commit").call();
 
 		Repository remoteRepository = createWorkRepository();
+		addRepoToClose(remoteRepository);
 		remoteGit = new Git(remoteRepository);
 
 		// setup the first repository to fetch from the second repository
diff --git a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
index ffaafc1..ed980b5 100644
--- a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.pgm
 Bundle-SymbolicName: org.eclipse.jgit.pgm
-Bundle-Version: 6.0.1.qualifier
+Bundle-Version: 6.1.1.qualifier
 Bundle-Vendor: %Bundle-Vendor
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-11
@@ -14,47 +14,49 @@
  org.eclipse.jetty.servlet;version="[10.0.0,11.0.0)",
  org.eclipse.jetty.util;version="[10.0.0,11.0.0)",
  org.eclipse.jetty.util.component;version="[10.0.0,11.0.0)",
- org.eclipse.jgit.api;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.api.errors;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.archive;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.awtui;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.blame;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.diff;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.dircache;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.errors;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.gitrepo;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.storage.io;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.storage.pack;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.storage.reftable;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lfs;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lfs.server;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lfs.server.fs;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lfs.server.s3;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lib;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.merge;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.nls;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.notes;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.revplot;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.revwalk;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.revwalk.filter;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.storage.file;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.storage.pack;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport.http.apache;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport.resolver;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport.ssh.jsch;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport.sshd;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.treewalk;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.treewalk.filter;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.util;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.util.io;version="[6.0.1,6.1.0)",
+ org.eclipse.jgit.api;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.api.errors;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.archive;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.awtui;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.blame;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.diff;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.dircache;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.errors;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.gitrepo;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.storage.file;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.diffmergetool;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.storage.io;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.storage.reftable;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lfs;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lfs.server;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lfs.server.fs;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lfs.server.s3;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lib;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.merge;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lib.internal;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.nls;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.notes;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.revplot;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.revwalk;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.revwalk.filter;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.storage.file;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.storage.pack;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport.http.apache;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport.resolver;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport.ssh.jsch;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport.sshd;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.treewalk;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.treewalk.filter;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.util;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.util.io;version="[6.1.1,6.2.0)",
  org.kohsuke.args4j;version="[2.33.0,3.0.0)",
  org.kohsuke.args4j.spi;version="[2.33.0,3.0.0)"
-Export-Package: org.eclipse.jgit.console;version="6.0.1";
+Export-Package: org.eclipse.jgit.console;version="6.1.1";
  uses:="org.eclipse.jgit.transport,
   org.eclipse.jgit.util",
- org.eclipse.jgit.pgm;version="6.0.1";
+ org.eclipse.jgit.pgm;version="6.1.1";
   uses:="org.eclipse.jgit.transport,
    org.eclipse.jgit.util.io,
    org.eclipse.jgit.awtui,
@@ -66,14 +68,14 @@
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.api,
    javax.swing",
- org.eclipse.jgit.pgm.debug;version="6.0.1";
+ org.eclipse.jgit.pgm.debug;version="6.1.1";
   uses:="org.eclipse.jgit.util.io,
    org.eclipse.jgit.pgm,
    org.eclipse.jetty.servlet",
- org.eclipse.jgit.pgm.internal;version="6.0.1";
+ org.eclipse.jgit.pgm.internal;version="6.1.1";
   x-friends:="org.eclipse.jgit.pgm.test,
    org.eclipse.jgit.test",
- org.eclipse.jgit.pgm.opt;version="6.0.1";
+ org.eclipse.jgit.pgm.opt;version="6.1.1";
   uses:="org.kohsuke.args4j,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
diff --git a/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF
index 26269d7..0a5dd88 100644
--- a/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.pgm - Sources
 Bundle-SymbolicName: org.eclipse.jgit.pgm.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 6.0.1.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.pgm;version="6.0.1.qualifier";roots="."
+Bundle-Version: 6.1.1.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.pgm;version="6.1.1.qualifier";roots="."
diff --git a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
index e645255..8c44764 100644
--- a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
+++ b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
@@ -12,6 +12,7 @@
 org.eclipse.jgit.pgm.Daemon
 org.eclipse.jgit.pgm.Describe
 org.eclipse.jgit.pgm.Diff
+org.eclipse.jgit.pgm.DiffTool
 org.eclipse.jgit.pgm.DiffTree
 org.eclipse.jgit.pgm.Fetch
 org.eclipse.jgit.pgm.Gc
diff --git a/org.eclipse.jgit.pgm/pom.xml b/org.eclipse.jgit.pgm/pom.xml
index 05e0bcc..c189dc7 100644
--- a/org.eclipse.jgit.pgm/pom.xml
+++ b/org.eclipse.jgit.pgm/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.pgm</artifactId>
diff --git a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
index ae10af2..c364376 100644
--- a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
+++ b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
@@ -58,6 +58,9 @@
 dateInfo=Date:   {0}
 deletedBranch=Deleted branch {0}
 deletedRemoteBranch=Deleted remote branch {0}
+diffToolHelpSetToFollowing='git difftool --tool=<tool>' may be set to one of the following:\n{0}\n\tuser-defined:\n{1}\nThe following tools are valid, but not currently available:\n{2}\nSome of the tools listed above only work in a windowed\nenvironment. If run in a terminal-only session, they will fail.
+diffToolLaunch=Viewing ({0}/{1}): '{2}'\nLaunch '{3}' [Y/n]?
+diffToolDied=external diff died, stopping at {0}
 doesNotExist={0} does not exist
 dontOverwriteLocalChanges=error: Your local changes to the following file would be overwritten by merge:
 everythingUpToDate=Everything up-to-date
@@ -145,6 +148,7 @@
 metaVar_seconds=SECONDS
 metaVar_service=SERVICE
 metaVar_tagLocalUser=<GPG key ID>
+metaVar_tool=TOOL
 metaVar_treeish=tree-ish
 metaVar_uriish=uri-ish
 metaVar_url=URL
@@ -225,6 +229,7 @@
 unsupportedOperation=Unsupported operation: {0}
 untrackedFiles=Untracked files:
 updating=Updating {0}..{1}
+usage_Abbrev=Instead of using the default number of hexadecimal digits (which will vary according to the number of objects in the repository with a default of 7) of the abbreviated object name, use <n> digits, or as many digits as needed to form a unique object name. An <n> of 0 will suppress long format, only showing the closest tag.
 usage_Aggressive=This option will cause gc to more aggressively optimize the repository at the expense of taking much more time
 usage_AlwaysFallback=Show uniquely abbreviated commit object as fallback
 usage_bareClone=Make a bare Git repository. That is, instead of creating [DIRECTORY] and placing the administrative files in [DIRECTORY]/.git, make the [DIRECTORY] itself the $GIT_DIR.
@@ -249,6 +254,8 @@
 usage_DisplayTheVersionOfJgit=Display the version of jgit
 usage_Gc=Cleanup unnecessary files and optimize the local repository
 usage_Glog=View commit history as a graph
+usage_DiffGuiTool=When git-difftool is invoked with the -g or --gui option the default diff tool will be read from the configured diff.guitool variable instead of diff.tool.
+usage_noGui=The --no-gui option can be used to override -g or --gui setting.
 usage_IndexPack=Build pack index file for an existing packed archive
 usage_LFSDirectory=Directory to store large objects
 usage_LFSPort=Server http port
@@ -296,6 +303,7 @@
 usage_Status=Show the working tree status
 usage_StopTrackingAFile=Stop tracking a file
 usage_TextHashFunctions=Scan repository to compute maximum number of collisions for hash functions
+usage_ToolForDiff=Use the diff tool specified by <tool>. Run git difftool --tool-help for the list of valid <tool> settings.\nIf a diff tool is not specified, git difftool will use the configuration variable diff.tool.
 usage_UpdateRemoteRepositoryFromLocalRefs=Update remote repository from local refs
 usage_UseAll=Use all refs found in refs/
 usage_UseTags=Use any tag including lightweight tags
@@ -342,6 +350,7 @@
 usage_date=date format, one of default, rfc, local, iso, short, raw (as defined by git-log(1) ), locale or localelocal (jgit extensions)
 usage_detectRenames=detect renamed files
 usage_diffAlgorithm=the diff algorithm to use. Currently supported are: 'myers', 'histogram'
+usage_DiffTool=git difftool is a Git command that allows you to compare and edit files between revisions using common diff tools.\ngit difftool is a frontend to git diff and accepts the same options and arguments.
 usage_directoriesToExport=directories to export
 usage_disableTheServiceInAllRepositories=disable the service in all repositories
 usage_displayAListOfAllRegisteredJgitCommands=Display a list of all registered jgit commands
@@ -396,6 +405,8 @@
 usage_performFsckStyleChecksOnReceive=perform fsck style checks on receive
 usage_portNumberToListenOn=port number to listen on
 usage_printOnlyBranchesThatContainTheCommit=print only branches that contain the commit
+usage_prompt=Prompt before each invocation of the diff tool. This is the default behaviour; the option is provided to override any configuration settings.
+usage_noPrompt=Do not prompt before launching a diff tool.
 usage_pruneStaleTrackingRefs=prune stale tracking refs
 usage_pushUrls=push URLs are manipulated
 usage_quiet=don't show progress messages
@@ -423,6 +434,8 @@
 usage_sshDriver=Selects the built-in ssh library to use, JSch or Apache MINA sshd.
 usage_symbolicVersionForTheProject=Symbolic version for the project
 usage_tags=fetch all tags
+usage_trustExitCode=git-difftool invokes a diff tool individually on each file. Errors reported by the diff tool are ignored by default. Use --trust-exit-code to make git-difftool exit when an invoked diff tool returns a non-zero exit code.\ngit-difftool will forward the exit code of the invoked tool when --trust-exit-code is used.
+usage_noTrustExitCode=This option can be used to override --trust-exit-code setting.
 usage_notags=do not fetch tags
 usage_tagAnnotated=create an annotated tag, unsigned unless -s or -u are given, or config tag.gpgSign is true
 usage_tagDelete=delete tag
@@ -431,6 +444,7 @@
 usage_tagSign=create a signed annotated tag
 usage_tagNoSign=suppress signing the tag
 usage_tagVerify=Verify the GPG signature
+usage_toolHelp=Print a list of diff tools that may be used with --tool.
 usage_untrackedFilesMode=show untracked files
 usage_updateRef=reference to update
 usage_updateRemoteRefsFromAnotherRepository=Update remote refs from another repository
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Blame.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Blame.java
index 2b49cf7..1a3a2f6 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Blame.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Blame.java
@@ -15,6 +15,7 @@
 
 import static java.lang.Integer.valueOf;
 import static java.lang.Long.valueOf;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
 import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
 
 import java.io.IOException;
@@ -116,7 +117,8 @@
 
 		boolean autoAbbrev = abbrev == 0;
 		if (abbrev == 0) {
-			abbrev = db.getConfig().getInt("core", "abbrev", 7); //$NON-NLS-1$ //$NON-NLS-2$
+			abbrev = db.getConfig().getInt("core", "abbrev", //$NON-NLS-1$ //$NON-NLS-2$
+					OBJECT_ID_ABBREV_STRING_LENGTH);
 		}
 		if (!showBlankBoundary) {
 			root = db.getConfig().getBoolean("blame", "blankboundary", false); //$NON-NLS-1$ //$NON-NLS-2$
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Describe.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Describe.java
index 8aa119a..116db03 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Describe.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Describe.java
@@ -44,6 +44,9 @@
 	@Option(name = "--match", usage = "usage_Match", metaVar = "metaVar_pattern")
 	private List<String> patterns = new ArrayList<>();
 
+	@Option(name = "--abbrev", usage = "usage_Abbrev")
+	private Integer abbrev;
+
 	/** {@inheritDoc} */
 	@Override
 	protected void run() {
@@ -57,6 +60,9 @@
 			cmd.setTags(useTags);
 			cmd.setAlways(always);
 			cmd.setMatch(patterns.toArray(new String[0]));
+			if (abbrev != null) {
+				cmd.setAbbrev(abbrev.intValue());
+			}
 			String result = null;
 			try {
 				result = cmd.call();
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
new file mode 100644
index 0000000..1288817
--- /dev/null
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTool.java
@@ -0,0 +1,257 @@
+/*
+ * 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.pgm;
+
+import static org.eclipse.jgit.lib.Constants.HEAD;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.text.MessageFormat;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jgit.diff.DiffEntry;
+import org.eclipse.jgit.diff.DiffFormatter;
+import org.eclipse.jgit.dircache.DirCacheIterator;
+import org.eclipse.jgit.errors.AmbiguousObjectException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.RevisionSyntaxException;
+import org.eclipse.jgit.internal.diffmergetool.DiffTools;
+import org.eclipse.jgit.internal.diffmergetool.ExternalDiffTool;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.TextProgressMonitor;
+import org.eclipse.jgit.lib.internal.BooleanTriState;
+import org.eclipse.jgit.pgm.internal.CLIText;
+import org.eclipse.jgit.pgm.opt.PathTreeFilterHandler;
+import org.eclipse.jgit.treewalk.AbstractTreeIterator;
+import org.eclipse.jgit.treewalk.CanonicalTreeParser;
+import org.eclipse.jgit.treewalk.FileTreeIterator;
+import org.eclipse.jgit.treewalk.filter.TreeFilter;
+import org.eclipse.jgit.util.StringUtils;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
+
+@Command(name = "difftool", common = true, usage = "usage_DiffTool")
+class DiffTool extends TextBuiltin {
+	private DiffFormatter diffFmt;
+
+	private DiffTools diffTools;
+
+	@Argument(index = 0, metaVar = "metaVar_treeish")
+	private AbstractTreeIterator oldTree;
+
+	@Argument(index = 1, metaVar = "metaVar_treeish")
+	private AbstractTreeIterator newTree;
+
+	@Option(name = "--tool", aliases = {
+			"-t" }, metaVar = "metaVar_tool", usage = "usage_ToolForDiff")
+	private String toolName;
+
+	@Option(name = "--cached", aliases = { "--staged" }, usage = "usage_cached")
+	private boolean cached;
+
+	private BooleanTriState prompt = BooleanTriState.UNSET;
+
+	@Option(name = "--prompt", usage = "usage_prompt")
+	void setPrompt(@SuppressWarnings("unused") boolean on) {
+		prompt = BooleanTriState.TRUE;
+	}
+
+	@Option(name = "--no-prompt", aliases = { "-y" }, usage = "usage_noPrompt")
+	void noPrompt(@SuppressWarnings("unused") boolean on) {
+		prompt = BooleanTriState.FALSE;
+	}
+
+	@Option(name = "--tool-help", usage = "usage_toolHelp")
+	private boolean toolHelp;
+
+	private BooleanTriState gui = BooleanTriState.UNSET;
+
+	@Option(name = "--gui", aliases = { "-g" }, usage = "usage_DiffGuiTool")
+	void setGui(@SuppressWarnings("unused") boolean on) {
+		gui = BooleanTriState.TRUE;
+	}
+
+	@Option(name = "--no-gui", usage = "usage_noGui")
+	void noGui(@SuppressWarnings("unused") boolean on) {
+		gui = BooleanTriState.FALSE;
+	}
+
+	private BooleanTriState trustExitCode = BooleanTriState.UNSET;
+
+	@Option(name = "--trust-exit-code", usage = "usage_trustExitCode")
+	void setTrustExitCode(@SuppressWarnings("unused") boolean on) {
+		trustExitCode = BooleanTriState.TRUE;
+	}
+
+	@Option(name = "--no-trust-exit-code", usage = "usage_noTrustExitCode")
+	void noTrustExitCode(@SuppressWarnings("unused") boolean on) {
+		trustExitCode = BooleanTriState.FALSE;
+	}
+
+	@Option(name = "--", metaVar = "metaVar_paths", handler = PathTreeFilterHandler.class)
+	private TreeFilter pathFilter = TreeFilter.ALL;
+
+	@Override
+	protected void init(Repository repository, String gitDir) {
+		super.init(repository, gitDir);
+		diffFmt = new DiffFormatter(new BufferedOutputStream(outs));
+		diffTools = new DiffTools(repository);
+	}
+
+	@Override
+	protected void run() {
+		try {
+			if (toolHelp) {
+				showToolHelp();
+			} else {
+				boolean showPrompt = diffTools.isInteractive();
+				if (prompt != BooleanTriState.UNSET) {
+					showPrompt = prompt == BooleanTriState.TRUE;
+				}
+				String toolNamePrompt = toolName;
+				if (showPrompt) {
+					if (StringUtils.isEmptyOrNull(toolNamePrompt)) {
+						toolNamePrompt = diffTools.getDefaultToolName(gui);
+					}
+				}
+				// get the changed files
+				List<DiffEntry> files = getFiles();
+				if (files.size() > 0) {
+					compare(files, showPrompt, toolNamePrompt);
+				}
+			}
+			outw.flush();
+		} catch (RevisionSyntaxException | IOException e) {
+			throw die(e.getMessage(), e);
+		} finally {
+			diffFmt.close();
+		}
+	}
+
+	private void compare(List<DiffEntry> files, boolean showPrompt,
+			String toolNamePrompt) throws IOException {
+		for (int fileIndex = 0; fileIndex < files.size(); fileIndex++) {
+			DiffEntry ent = files.get(fileIndex);
+			String mergedFilePath = ent.getNewPath();
+			if (mergedFilePath.equals(DiffEntry.DEV_NULL)) {
+				mergedFilePath = ent.getOldPath();
+			}
+			// check if user wants to launch compare
+			boolean launchCompare = true;
+			if (showPrompt) {
+				launchCompare = isLaunchCompare(fileIndex + 1, files.size(),
+						mergedFilePath, toolNamePrompt);
+			}
+			if (launchCompare) {
+				switch (ent.getChangeType()) {
+				case MODIFY:
+					outw.println("M\t" + ent.getNewPath() //$NON-NLS-1$
+							+ " (" + ent.getNewId().name() + ")" //$NON-NLS-1$ //$NON-NLS-2$
+							+ "\t" + ent.getOldPath() //$NON-NLS-1$
+							+ " (" + ent.getOldId().name() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+					int ret = diffTools.compare(ent.getNewPath(),
+							ent.getOldPath(), ent.getNewId().name(),
+							ent.getOldId().name(), toolName, prompt, gui,
+							trustExitCode);
+					if (ret != 0) {
+						throw die(MessageFormat.format(
+								CLIText.get().diffToolDied, mergedFilePath));
+					}
+					break;
+				default:
+					break;
+				}
+			} else {
+				break;
+			}
+		}
+	}
+
+	@SuppressWarnings("boxing")
+	private boolean isLaunchCompare(int fileIndex, int fileCount,
+			String fileName, String toolNamePrompt) throws IOException {
+		boolean launchCompare = true;
+		outw.println(MessageFormat.format(CLIText.get().diffToolLaunch,
+				fileIndex, fileCount, fileName, toolNamePrompt));
+		outw.flush();
+		BufferedReader br = new BufferedReader(
+				new InputStreamReader(ins, StandardCharsets.UTF_8));
+		String line = null;
+		if ((line = br.readLine()) != null) {
+			if (!line.equalsIgnoreCase("Y")) { //$NON-NLS-1$
+				launchCompare = false;
+			}
+		}
+		return launchCompare;
+	}
+
+	private void showToolHelp() throws IOException {
+		StringBuilder availableToolNames = new StringBuilder();
+		for (String name : diffTools.getAvailableTools().keySet()) {
+			availableToolNames.append(String.format("\t\t%s\n", name)); //$NON-NLS-1$
+		}
+		StringBuilder notAvailableToolNames = new StringBuilder();
+		for (String name : diffTools.getNotAvailableTools().keySet()) {
+			notAvailableToolNames.append(String.format("\t\t%s\n", name)); //$NON-NLS-1$
+		}
+		StringBuilder userToolNames = new StringBuilder();
+		Map<String, ExternalDiffTool> userTools = diffTools
+				.getUserDefinedTools();
+		for (String name : userTools.keySet()) {
+			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,
+				userToolNames, notAvailableToolNames));
+	}
+
+	private List<DiffEntry> getFiles()
+			throws RevisionSyntaxException, AmbiguousObjectException,
+			IncorrectObjectTypeException, IOException {
+		diffFmt.setRepository(db);
+		if (cached) {
+			if (oldTree == null) {
+				ObjectId head = db.resolve(HEAD + "^{tree}"); //$NON-NLS-1$
+				if (head == null) {
+					die(MessageFormat.format(CLIText.get().notATree, HEAD));
+				}
+				CanonicalTreeParser p = new CanonicalTreeParser();
+				try (ObjectReader reader = db.newObjectReader()) {
+					p.reset(reader, head);
+				}
+				oldTree = p;
+			}
+			newTree = new DirCacheIterator(db.readDirCache());
+		} else if (oldTree == null) {
+			oldTree = new DirCacheIterator(db.readDirCache());
+			newTree = new FileTreeIterator(db);
+		} else if (newTree == null) {
+			newTree = new FileTreeIterator(db);
+		}
+
+		TextProgressMonitor pm = new TextProgressMonitor(errw);
+		pm.setDelayStart(2, TimeUnit.SECONDS);
+		diffFmt.setProgressMonitor(pm);
+		diffFmt.setPathFilter(pathFilter);
+
+		List<DiffEntry> files = diffFmt.scan(oldTree, newTree);
+		return files;
+	}
+
+}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Merge.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Merge.java
index ca4877f..27a3d90 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Merge.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Merge.java
@@ -10,6 +10,8 @@
 
 package org.eclipse.jgit.pgm;
 
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
+
 import java.io.IOException;
 import java.text.MessageFormat;
 import java.util.Map;
@@ -144,8 +146,10 @@
 			case FAST_FORWARD:
 				ObjectId oldHeadId = oldHead.getObjectId();
 				if (oldHeadId != null) {
-					String oldId = oldHeadId.abbreviate(7).name();
-					String newId = result.getNewHead().abbreviate(7).name();
+					String oldId = oldHeadId
+							.abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH).name();
+					String newId = result.getNewHead()
+							.abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH).name();
 					outw.println(MessageFormat.format(CLIText.get().updating,
 							oldId, newId));
 				}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reflog.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reflog.java
index 030119e..c63532d 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reflog.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reflog.java
@@ -9,6 +9,8 @@
  */
 package org.eclipse.jgit.pgm;
 
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
+
 import java.io.IOException;
 import java.util.Collection;
 
@@ -45,7 +47,8 @@
 
 	private String toString(ReflogEntry entry, int i) {
 		final StringBuilder s = new StringBuilder();
-		s.append(entry.getNewId().abbreviate(7).name());
+		s.append(entry.getNewId().abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH)
+				.name());
 		s.append(" "); //$NON-NLS-1$
 		s.append(ref == null ? Constants.HEAD : Repository.shortenRefName(ref));
 		s.append("@{" + i + "}:"); //$NON-NLS-1$ //$NON-NLS-2$
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java
index 8e49a76..7fe5b0f 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java
@@ -136,6 +136,9 @@
 	/***/ public String dateInfo;
 	/***/ public String deletedBranch;
 	/***/ public String deletedRemoteBranch;
+	/***/ public String diffToolHelpSetToFollowing;
+	/***/ public String diffToolLaunch;
+	/***/ public String diffToolDied;
 	/***/ public String doesNotExist;
 	/***/ public String dontOverwriteLocalChanges;
 	/***/ public String everythingUpToDate;
diff --git a/org.eclipse.jgit.ssh.apache.agent/BUILD b/org.eclipse.jgit.ssh.apache.agent/BUILD
index 0c8cf83..f2e4d55 100644
--- a/org.eclipse.jgit.ssh.apache.agent/BUILD
+++ b/org.eclipse.jgit.ssh.apache.agent/BUILD
@@ -17,6 +17,6 @@
         "//lib:slf4j-api",
         "//lib:sshd-osgi",
         "//org.eclipse.jgit:jgit",
-        "//org.eclipse.jgit.ssh.apache:ssh-apache"
+        "//org.eclipse.jgit.ssh.apache:ssh-apache",
     ],
 )
diff --git a/org.eclipse.jgit.ssh.apache.agent/META-INF/MANIFEST.MF b/org.eclipse.jgit.ssh.apache.agent/META-INF/MANIFEST.MF
index 3fd0f52..1a5746d 100644
--- a/org.eclipse.jgit.ssh.apache.agent/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ssh.apache.agent/META-INF/MANIFEST.MF
@@ -2,15 +2,15 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %Bundle-Name
 Bundle-SymbolicName: org.eclipse.jgit.ssh.apache.agent;singleton:=true
-Bundle-Version: 6.0.1.qualifier
+Bundle-Version: 6.1.1.qualifier
 Bundle-Vendor: %Bundle-Vendor
-Fragment-Host: org.eclipse.jgit.ssh.apache;bundle-version="[6.0.1,6.1.0)"
+Fragment-Host: org.eclipse.jgit.ssh.apache;bundle-version="[6.1.1,6.2.0)"
 Bundle-ActivationPolicy: lazy
 Automatic-Module-Name: org.eclipse.jgit.ssh.apache.agent
 Bundle-RequiredExecutionEnvironment: JavaSE-11
-Import-Package: org.eclipse.jgit.transport.sshd;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.nls;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.util;version="[6.0.1,6.1.0)"
+Import-Package: org.eclipse.jgit.transport.sshd;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.nls;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.util;version="[6.1.1,6.2.0)"
 Require-Bundle: com.sun.jna;bundle-version="[5.8.0,6.0.0)",
  com.sun.jna.platform;bundle-version="[5.8.0,6.0.0)"
-Export-Package: org.eclipse.jgit.internal.transport.sshd.agent.connector;version="6.0.1";x-internal:=true
+Export-Package: org.eclipse.jgit.internal.transport.sshd.agent.connector;version="6.1.1";x-internal:=true
diff --git a/org.eclipse.jgit.ssh.apache.agent/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.ssh.apache.agent/META-INF/SOURCE-MANIFEST.MF
index 808de53..d06e6b1 100644
--- a/org.eclipse.jgit.ssh.apache.agent/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.ssh.apache.agent/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.ssh.apache.agent - Sources
 Bundle-SymbolicName: org.eclipse.jgit.ssh.apache.agent.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 6.0.1.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.ssh.apache.agent;version="6.0.1.qualifier";roots="."
+Bundle-Version: 6.1.1.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.ssh.apache.agent;version="6.1.1.qualifier";roots="."
diff --git a/org.eclipse.jgit.ssh.apache.agent/README.md b/org.eclipse.jgit.ssh.apache.agent/README.md
new file mode 100644
index 0000000..6d62a2f
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.agent/README.md
@@ -0,0 +1,82 @@
+# JGit SSH agent transport support for Apache MINA sshd
+
+This bundle provides optional support for communicating with an SSH agent
+for SSH or SFTP authentication. It is an OSGi fragment for bundle
+[org.eclipse.jgit.ssh.apache](../org.eclipse.jgit.ssh.apache/README.md),
+and it provides transports for local communication with SSH agents.
+
+## Supported SSH agent transports
+
+### Linux, OS X, BSD
+
+On Linux, OS X, and BSD, the only transport mechanism supported is the usual
+communication via a Unix domain socket. This is the only protocol the OpenSSH
+SSH agent supports. A Unix domain socket appears as a special file in the file
+system; this file name is typically available in the environment variable
+`SSH_AUTH_SOCK`.
+
+The SSH config `IdentityAgent` can be set to this socket filename to specify
+exactly which Unix domain socket to use, or it can be set to `SSH_AUTH_SOCK`
+to use the value from that environment variable. If `IdentityAgent` is not set
+at all, JGit uses `SSH_AUTH_SOCK` by default. If the variable is not set, no
+SSH agent will be used. `IdentityAgent` can also be set to `none` to not use
+any SSH agent.
+
+### Windows
+
+On Windows, two different transports are supported:
+
+* A transport over a Windows named pipe. This is used by Win32-OpenSSH, and is available for Pageant since version 0.75.
+* A Pageant-specific legacy transport via shared memory; useful for Pageant and GPG's gpg-agent.
+
+Possible settings of `IdentityAgent` to select a particular transport are
+
+* `//./pipe/openssh-ssh-agent`: the Windows named pipe of Win32-OpenSSH.
+* `//./pageant`: the shared-memory mechanism of Pageant.
+* `none`: do not use any SSH agent.
+* `//./pipe/&lt;any_valid_pipe_name>`: use a specific Windows named pipe.
+
+The default transport on Windows if `IdentityAgent` is not set at all is the
+Pageant shared-memory transport. Environment variable `SSH_AUTH_SOCK` needs
+not be set for Pageant, and _must not_ be set for Win32-OpenSSH.
+
+It is also possible to use a named pipe as transport for Pageant (as of
+version 0.75). Unfortunately, Pageant unnecessarily cryptographically
+obfuscates the pipe name, so it is not possible for JGit to determine it
+automatically. The pipe name is `pageant.<user name>.<sha256>`, for instance
+`pageant.myself.c5687736ba755a70b000955cb191698aed7db221c2b0710199eb1f5298922ab5`.
+A user can look up the name by starting Pageant and then running the
+command `dir \\.\pipe\\` in a command shell. Once the name is known, setting
+`IdentityAgent` to the pipe name as
+`//./pipe/pageant.myself.c5687736ba755a70b000955cb191698aed7db221c2b0710199eb1f5298922ab5`
+makes JGit use this Windows named pipe for communication with Pageant.
+
+(You can use forward slashes in the `~/.ssh/config` file. SSH config file
+parsing has its own rules about backslashes in config files; which are
+treated as escape characters in some contexts. With backslashes one would
+have to write, e.g., `\\\\.\pipe\openssh-ssh-agent`.)
+
+With these two transport mechanisms, Pageant and Win32-OpenSSH are supported.
+As for GPG: the gpg-agent can be configured to emulate ssh-agent (presumably
+via a WinSockets2 "Unix domain socket" on Windows) or to emulate Pageant
+(using the shared memory mechanism). Running gpg-agent with the
+`enable-ssh-support` option is
+[reported not to work on Windows](https://dev.gnupg.org/T3883), though. But
+the PuTTY emulation in gpg-agent (option `enable-putty-support`) _should_ work,
+so it should be possible to use gpg-agent instead of Pageant.
+
+Neither Pageant (as of version 0.76) nor Win32-OpenSSH (as of version 8.6)
+support the `confirm` or lifetime constraints for `AddKeysToAgent`. gpg-agent
+apparently does, even when communicating over the Pageant shared memory
+mechanism.
+
+The ssh-agent from git bash on Windows is currently not supported. It would
+need a connector handling Cygwin socket files and the Cygwin handshake over
+a TCP stream socket bound to the loopback interface. The Cygwin socket file
+_is_ exposed in the Windows file system at %TEMP%\ssh-XXXXXXXXXX\agent.&lt;number>,
+but it does not have a fixed name (the X's and the number are variable and
+change each time ssh-agent is started).
+
+## Implementation
+
+The implementation of all transports uses JNA.
\ No newline at end of file
diff --git a/org.eclipse.jgit.ssh.apache.agent/pom.xml b/org.eclipse.jgit.ssh.apache.agent/pom.xml
index 1bd3fad..77b9c95 100644
--- a/org.eclipse.jgit.ssh.apache.agent/pom.xml
+++ b/org.eclipse.jgit.ssh.apache.agent/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.ssh.apache.agent</artifactId>
@@ -132,7 +132,6 @@
         </configuration>
       </plugin>
 
-      <!-- New in 6.0; uncomment in 6.1
       <plugin>
           <groupId>com.github.siom79.japicmp</groupId>
           <artifactId>japicmp-maven-plugin</artifactId>
@@ -155,6 +154,9 @@
                   <includes>
                       <include>org.eclipse.jgit.*</include>
                   </includes>
+                  <excludes>
+                      <exclude>*.internal.*</exclude>
+                  </excludes>
                   <accessModifier>public</accessModifier>
                   <breakBuildOnModifications>false</breakBuildOnModifications>
                   <breakBuildOnBinaryIncompatibleModifications>false</breakBuildOnBinaryIncompatibleModifications>
@@ -174,11 +176,9 @@
           </execution>
         </executions>
       </plugin>
-      -->
     </plugins>
   </build>
 
-  <!-- New in 6.0, uncomment in 6.1
   <reporting>
     <plugins>
       <plugin>
@@ -210,6 +210,9 @@
                   <includes>
                       <include>org.eclipse.jgit.*</include>
                   </includes>
+                  <excludes>
+                      <exclude>*.internal.*</exclude>
+                  </excludes>
                   <accessModifier>public</accessModifier>
                   <breakBuildOnModifications>false</breakBuildOnModifications>
                   <breakBuildOnBinaryIncompatibleModifications>false</breakBuildOnBinaryIncompatibleModifications>
@@ -223,5 +226,4 @@
       </plugin>
     </plugins>
   </reporting>
-  -->
 </project>
diff --git a/org.eclipse.jgit.ssh.apache.agent/resources/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.properties b/org.eclipse.jgit.ssh.apache.agent/resources/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.properties
index 6fce083..a3b4e91 100644
--- a/org.eclipse.jgit.ssh.apache.agent/resources/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.properties
+++ b/org.eclipse.jgit.ssh.apache.agent/resources/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.properties
@@ -2,9 +2,11 @@
 errLastError=System message for error {0} could not be retrieved, got {1}
 errReleaseSharedMemory=Cannot release shared memory: {0} - {1}
 errUnknown=unknown error
+errUnknownIdentityAgent=IdentityAgent ''{0}'' unknown
 logErrorLoadLibrary=Cannot load socket library; SSH agent support is switched off
 msgCloseFailed=Cannot close SSH agent socket {0}
 msgConnectFailed=Could not connect to SSH agent via socket ''{0}''
+msgConnectPipeFailed=Could not connect to SSH agent via pipe ''{0}''
 msgNoMappedFile=Could not create file mapping: {0} - {1}
 msgNoSharedMemory=Could not initialize shared memory: {0} - {1}
 msgPageantUnavailable=Could not connect to Pageant
@@ -15,3 +17,4 @@
 msgShortRead=Short read from SSH agent, expected {0} bytes, got {1} bytes; last read() returned {2}
 pageant=Pageant
 unixDefaultAgent=ssh-agent
+winOpenSsh=Win32 OpenSSH
diff --git a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Factory.java b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Factory.java
index d7409b0..1cee1be 100644
--- a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Factory.java
+++ b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Factory.java
@@ -11,11 +11,15 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.text.MessageFormat;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
 
 import org.eclipse.jgit.transport.sshd.agent.Connector;
 import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory;
+import org.eclipse.jgit.util.StringUtils;
 import org.eclipse.jgit.util.SystemReader;
 
 /**
@@ -29,7 +33,20 @@
 	public Connector create(String identityAgent, File homeDir)
 			throws IOException {
 		if (SystemReader.getInstance().isWindows()) {
-			return new PageantConnector();
+			if (StringUtils.isEmptyOrNull(identityAgent)) {
+				// Default.
+				return new PageantConnector();
+			}
+			String winPath = identityAgent.replace('/', '\\');
+			if (PageantConnector.DESCRIPTOR.getIdentityAgent()
+					.equalsIgnoreCase(winPath)) {
+				return new PageantConnector();
+			}
+			if (winPath.toLowerCase(Locale.ROOT).startsWith("\\\\.\\pipe\\")) { //$NON-NLS-1$
+				return new WinPipeConnector(winPath);
+			}
+			throw new IOException(MessageFormat.format(
+					Texts.get().errUnknownIdentityAgent, identityAgent));
 		}
 		return new UnixDomainSocketConnector(identityAgent);
 	}
@@ -55,7 +72,11 @@
 	 */
 	@Override
 	public Collection<ConnectorDescriptor> getSupportedConnectors() {
-		return Collections.singleton(getDefaultConnector());
+		if (SystemReader.getInstance().isWindows()) {
+			return List.of(PageantConnector.DESCRIPTOR,
+					WinPipeConnector.DESCRIPTOR);
+		}
+		return Collections.singleton(UnixDomainSocketConnector.DESCRIPTOR);
 	}
 
 	@Override
diff --git a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/LibraryHolder.java b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/LibraryHolder.java
index b09b55f..0a592d0 100644
--- a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/LibraryHolder.java
+++ b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/LibraryHolder.java
@@ -53,6 +53,10 @@
 		kernel = Kernel32.INSTANCE;
 	}
 
+	String systemError() {
+		return systemError("[{0}] - {1}"); //$NON-NLS-1$
+	}
+
 	String systemError(String pattern) {
 		int lastError = kernel.GetLastError();
 		String msg;
diff --git a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/PageantConnector.java b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/PageantConnector.java
index b0e3bce..19684ec 100644
--- a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/PageantConnector.java
+++ b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/PageantConnector.java
@@ -26,7 +26,10 @@
 
 		@Override
 		public String getIdentityAgent() {
-			return "pageant"; //$NON-NLS-1$
+			// This must be an absolute Windows path name to avoid that
+			// OpenSshConfigFile treats it as a relative path name. Use an UNC
+			// name on localhost, like for pipes.
+			return "\\\\.\\pageant"; //$NON-NLS-1$
 		}
 
 		@Override
diff --git a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Sockets.java b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Sockets.java
index 3d95bdb..52cf5f2 100644
--- a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Sockets.java
+++ b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Sockets.java
@@ -24,11 +24,6 @@
 	}
 
 	/**
-	 * Default SSH agent socket environment variable name.
-	 */
-	public static final String ENV_SSH_AUTH_SOCK = "SSH_AUTH_SOCK"; //$NON-NLS-1$
-
-	/**
 	 * Domain for Unix domain sockets.
 	 */
 	public static final int AF_UNIX = 1;
diff --git a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.java b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.java
index fb45b30..f387c76 100644
--- a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.java
+++ b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.java
@@ -31,9 +31,11 @@
 	/***/ public String errLastError;
 	/***/ public String errReleaseSharedMemory;
 	/***/ public String errUnknown;
+	/***/ public String errUnknownIdentityAgent;
 	/***/ public String logErrorLoadLibrary;
 	/***/ public String msgCloseFailed;
 	/***/ public String msgConnectFailed;
+	/***/ public String msgConnectPipeFailed;
 	/***/ public String msgNoMappedFile;
 	/***/ public String msgNoSharedMemory;
 	/***/ public String msgPageantUnavailable;
@@ -44,5 +46,6 @@
 	/***/ public String msgShortRead;
 	/***/ public String pageant;
 	/***/ public String unixDefaultAgent;
+	/***/ public String winOpenSsh;
 
 }
diff --git a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/UnixDomainSocketConnector.java b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/UnixDomainSocketConnector.java
index 3b75f3a..95ac34f 100644
--- a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/UnixDomainSocketConnector.java
+++ b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/UnixDomainSocketConnector.java
@@ -11,10 +11,10 @@
 
 import static org.eclipse.jgit.internal.transport.sshd.agent.connector.Sockets.AF_UNIX;
 import static org.eclipse.jgit.internal.transport.sshd.agent.connector.Sockets.DEFAULT_PROTOCOL;
-import static org.eclipse.jgit.internal.transport.sshd.agent.connector.Sockets.ENV_SSH_AUTH_SOCK;
 import static org.eclipse.jgit.internal.transport.sshd.agent.connector.Sockets.SOCK_STREAM;
 import static org.eclipse.jgit.internal.transport.sshd.agent.connector.UnixSockets.FD_CLOEXEC;
 import static org.eclipse.jgit.internal.transport.sshd.agent.connector.UnixSockets.F_SETFD;
+import static org.eclipse.jgit.transport.SshConstants.ENV_SSH_AUTH_SOCKET;
 
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
@@ -46,7 +46,7 @@
 
 		@Override
 		public String getIdentityAgent() {
-			return ENV_SSH_AUTH_SOCK;
+			return ENV_SSH_AUTH_SOCKET;
 		}
 
 		@Override
@@ -91,8 +91,9 @@
 	public UnixDomainSocketConnector(String socketFile) {
 		super();
 		String file = socketFile;
-		if (StringUtils.isEmptyOrNull(file)) {
-			file = SystemReader.getInstance().getenv(ENV_SSH_AUTH_SOCK);
+		if (StringUtils.isEmptyOrNull(file)
+				|| ENV_SSH_AUTH_SOCKET.equals(file)) {
+			file = SystemReader.getInstance().getenv(ENV_SSH_AUTH_SOCKET);
 		}
 		this.socketFile = file;
 	}
diff --git a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/WinPipeConnector.java b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/WinPipeConnector.java
new file mode 100644
index 0000000..7bad90f
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/WinPipeConnector.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2021, Thomas Wolf <thomas.wolf@paranor.ch> 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
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.transport.sshd.agent.connector;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.sshd.common.SshException;
+import org.eclipse.jgit.transport.sshd.agent.AbstractConnector;
+import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory.ConnectorDescriptor;
+import org.eclipse.jgit.util.StringUtils;
+
+import com.sun.jna.LastErrorException;
+import com.sun.jna.platform.win32.WinBase;
+import com.sun.jna.platform.win32.WinError;
+import com.sun.jna.platform.win32.WinNT;
+import com.sun.jna.platform.win32.WinNT.HANDLE;
+import com.sun.jna.ptr.IntByReference;
+
+/**
+ * A connector based on JNA using Windows' named pipes to communicate with an
+ * ssh agent. This is used by Microsoft's Win32-OpenSSH port.
+ */
+public class WinPipeConnector extends AbstractConnector {
+
+	// Pipe names are, like other file names, case-insensitive on Windows.
+	private static final String CANONICAL_PIPE_NAME = "\\\\.\\pipe\\openssh-ssh-agent"; //$NON-NLS-1$
+
+	/**
+	 * {@link ConnectorDescriptor} for the {@link PageantConnector}.
+	 */
+	public static final ConnectorDescriptor DESCRIPTOR = new ConnectorDescriptor() {
+
+		@Override
+		public String getIdentityAgent() {
+			return CANONICAL_PIPE_NAME;
+		}
+
+		@Override
+		public String getDisplayName() {
+			return Texts.get().winOpenSsh;
+		}
+	};
+
+	private static final int FILE_SHARE_NONE = 0;
+
+	private static final int FILE_ATTRIBUTE_NONE = 0;
+
+	private final String pipeName;
+
+	private final AtomicBoolean connected = new AtomicBoolean();
+
+	// It's a byte pipe, so the normal Windows file mechanisms can be used.
+	// Would one of the standard Java File I/O abstractions work?
+	private volatile HANDLE fileHandle;
+
+	/**
+	 * Creates a {@link WinPipeConnector} for the given named pipe.
+	 *
+	 * @param pipeName
+	 *            to connect to
+	 */
+	public WinPipeConnector(String pipeName) {
+		this.pipeName = pipeName.replace('/', '\\');
+	}
+
+	@Override
+	public boolean connect() throws IOException {
+		if (StringUtils.isEmptyOrNull(pipeName)) {
+			return false;
+		}
+		HANDLE file = fileHandle;
+		synchronized (this) {
+			if (connected.get()) {
+				return true;
+			}
+			LibraryHolder libs = LibraryHolder.getLibrary();
+			if (libs == null) {
+				return false;
+			}
+			file = libs.kernel.CreateFile(pipeName,
+					WinNT.GENERIC_READ | WinNT.GENERIC_WRITE, FILE_SHARE_NONE,
+					null, WinNT.OPEN_EXISTING, FILE_ATTRIBUTE_NONE, null);
+			if (file == null || file == WinBase.INVALID_HANDLE_VALUE) {
+				int errorCode = libs.kernel.GetLastError();
+				if (errorCode == WinError.ERROR_FILE_NOT_FOUND
+						&& CANONICAL_PIPE_NAME.equalsIgnoreCase(pipeName)) {
+					// OpenSSH agent not running. Don't throw.
+					return false;
+				}
+				LastErrorException cause = new LastErrorException(
+						libs.systemError());
+				throw new IOException(MessageFormat
+						.format(Texts.get().msgConnectPipeFailed, pipeName),
+						cause);
+			}
+			connected.set(true);
+		}
+		fileHandle = file;
+		return connected.get();
+	}
+
+	@Override
+	public synchronized void close() throws IOException {
+		HANDLE file = fileHandle;
+		if (connected.getAndSet(false) && fileHandle != null) {
+			fileHandle = null;
+			LibraryHolder libs = LibraryHolder.getLibrary();
+			boolean success = libs.kernel.CloseHandle(file);
+			if (!success) {
+				LastErrorException cause = new LastErrorException(
+						libs.systemError());
+				throw new IOException(MessageFormat
+						.format(Texts.get().msgCloseFailed, pipeName), cause);
+			}
+		}
+	}
+
+	@Override
+	public byte[] rpc(byte command, byte[] message) throws IOException {
+		prepareMessage(command, message);
+		HANDLE file = fileHandle;
+		if (!connected.get() || file == null) {
+			// No translation, internal error
+			throw new IllegalStateException("Not connected to SSH agent"); //$NON-NLS-1$
+		}
+		LibraryHolder libs = LibraryHolder.getLibrary();
+		writeFully(libs, file, message);
+		// Now receive the reply
+		byte[] lengthBuf = new byte[4];
+		readFully(libs, file, lengthBuf);
+		int length = toLength(command, lengthBuf);
+		byte[] payload = new byte[length];
+		readFully(libs, file, payload);
+		return payload;
+	}
+
+	private void writeFully(LibraryHolder libs, HANDLE file, byte[] message)
+			throws IOException {
+		byte[] buf = message;
+		int toWrite = buf.length;
+		try {
+			while (toWrite > 0) {
+				IntByReference written = new IntByReference();
+				boolean success = libs.kernel.WriteFile(file, buf, buf.length,
+						written, null);
+				if (!success) {
+					throw new LastErrorException(libs.systemError());
+				}
+				int actuallyWritten = written.getValue();
+				toWrite -= actuallyWritten;
+				if (actuallyWritten > 0 && toWrite > 0) {
+					buf = Arrays.copyOfRange(buf, actuallyWritten, buf.length);
+				}
+			}
+		} catch (LastErrorException e) {
+			throw new IOException(MessageFormat.format(
+					Texts.get().msgSendFailed, Integer.toString(message.length),
+					Integer.toString(toWrite)), e);
+		}
+	}
+
+	private void readFully(LibraryHolder libs, HANDLE file, byte[] data)
+			throws IOException {
+		int n = 0;
+		int offset = 0;
+		while (offset < data.length && (n = read(libs, file, data, offset,
+				data.length - offset)) > 0) {
+			offset += n;
+		}
+		if (offset < data.length) {
+			throw new SshException(MessageFormat.format(
+					Texts.get().msgShortRead, Integer.toString(data.length),
+					Integer.toString(offset), Integer.toString(n)));
+		}
+	}
+
+	private int read(LibraryHolder libs, HANDLE file, byte[] buffer, int offset,
+			int length) throws IOException {
+		try {
+			int toRead = length;
+			IntByReference read = new IntByReference();
+			if (offset == 0) {
+				boolean success = libs.kernel.ReadFile(file, buffer, toRead,
+						read, null);
+				if (!success) {
+					throw new LastErrorException(libs.systemError());
+				}
+				return read.getValue();
+			}
+			byte[] data = new byte[length];
+			boolean success = libs.kernel.ReadFile(file, buffer, toRead, read,
+					null);
+			if (!success) {
+				throw new LastErrorException(libs.systemError());
+			}
+			int actuallyRead = read.getValue();
+			if (actuallyRead > 0) {
+				System.arraycopy(data, 0, buffer, offset, actuallyRead);
+			}
+			return actuallyRead;
+		} catch (LastErrorException e) {
+			throw new IOException(MessageFormat.format(
+					Texts.get().msgReadFailed, Integer.toString(length)), e);
+		}
+	}
+}
diff --git a/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF
index 8ff9735..411d43c 100644
--- a/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF
@@ -3,34 +3,34 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.ssh.apache.test
 Bundle-SymbolicName: org.eclipse.jgit.ssh.apache.test
-Bundle-Version: 6.0.1.qualifier
+Bundle-Version: 6.1.1.qualifier
 Bundle-Vendor: %Bundle-Vendor
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-11
-Import-Package: org.apache.sshd.client.config.hosts;version="[2.7.0,2.8.0)",
- org.apache.sshd.common;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.auth;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.config.keys;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.helpers;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.kex;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.keyprovider;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.session;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.signature;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.util.net;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.util.security;version="[2.7.0,2.8.0)",
- org.apache.sshd.core;version="[2.7.0,2.8.0)",
- org.apache.sshd.server;version="[2.7.0,2.8.0)",
- org.apache.sshd.server.forward;version="[2.7.0,2.8.0)",
- org.eclipse.jgit.api;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.api.errors;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.transport.sshd.proxy;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.junit;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.junit.ssh;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lib;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport.sshd;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport.sshd.agent;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.util;version="[6.0.1,6.1.0)",
+Import-Package: org.apache.sshd.client.config.hosts;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.auth;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.config.keys;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.helpers;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.kex;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.keyprovider;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.session;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.signature;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.util.net;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.util.security;version="[2.8.0,2.9.0)",
+ org.apache.sshd.core;version="[2.8.0,2.9.0)",
+ org.apache.sshd.server;version="[2.8.0,2.9.0)",
+ org.apache.sshd.server.forward;version="[2.8.0,2.9.0)",
+ org.eclipse.jgit.api;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.api.errors;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.transport.sshd.proxy;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.junit;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.junit.ssh;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lib;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport.sshd;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport.sshd.agent;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.util;version="[6.1.1,6.2.0)",
  org.hamcrest;version="[1.1.0,3.0.0)",
  org.junit;version="[4.13,5.0.0)",
  org.junit.experimental.theories;version="[4.13,5.0.0)",
diff --git a/org.eclipse.jgit.ssh.apache.test/pom.xml b/org.eclipse.jgit.ssh.apache.test/pom.xml
index a88ddaf..3e98e33 100644
--- a/org.eclipse.jgit.ssh.apache.test/pom.xml
+++ b/org.eclipse.jgit.ssh.apache.test/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.ssh.apache.test</artifactId>
diff --git a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/ApacheSshTest.java b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/ApacheSshTest.java
index ccaf98c..3d7c765 100644
--- a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/ApacheSshTest.java
+++ b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/ApacheSshTest.java
@@ -107,6 +107,32 @@
 				"IdentityFile " + privateKey1.getAbsolutePath());
 	}
 
+	/**
+	 * Test for SSHD-1231. If authentication is attempted first with an RSA key,
+	 * which is rejected, and then with some other key type (here ed25519),
+	 * authentication fails in bug SSHD-1231.
+	 *
+	 * @throws Exception
+	 *             on errors
+	 * @see <a href=
+	 *      "https://issues.apache.org/jira/browse/SSHD-1231">SSHD-1231</a>
+	 */
+	@Test
+	public void testWrongKeyFirst() throws Exception {
+		File userKey = new File(getTemporaryDirectory(), "userkey");
+		copyTestResource("id_ed25519", userKey);
+		File publicKey = new File(getTemporaryDirectory(), "userkey.pub");
+		copyTestResource("id_ed25519.pub", publicKey);
+		server.setTestUserPublicKey(publicKey.toPath());
+		cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
+				"Host git", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"IdentityFile " + privateKey1.getAbsolutePath(), // RSA
+				"IdentityFile " + userKey.getAbsolutePath());
+	}
+
 	@Test
 	public void testHashedKnownHosts() throws Exception {
 		assertTrue("Failed to delete known_hosts", knownHosts.delete());
diff --git a/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF b/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF
index 81e01cc..73feaec 100644
--- a/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF
@@ -6,9 +6,9 @@
 Bundle-Vendor: %Bundle-Vendor
 Bundle-Localization: plugin
 Bundle-ActivationPolicy: lazy
-Bundle-Version: 6.0.1.qualifier
+Bundle-Version: 6.1.1.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-11
-Export-Package: org.eclipse.jgit.internal.transport.sshd;version="6.0.1";x-internal:=true;
+Export-Package: org.eclipse.jgit.internal.transport.sshd;version="6.1.1";x-internal:=true;
   uses:="org.apache.sshd.client,
    org.apache.sshd.client.auth,
    org.apache.sshd.client.auth.keyboard,
@@ -23,72 +23,75 @@
    org.apache.sshd.common.signature,
    org.apache.sshd.common.util.buffer,
    org.eclipse.jgit.transport",
- org.eclipse.jgit.internal.transport.sshd.agent;version="6.0.1";x-internal:=true,
- org.eclipse.jgit.internal.transport.sshd.auth;version="6.0.1";x-internal:=true,
- org.eclipse.jgit.internal.transport.sshd.proxy;version="6.0.1";x-friends:="org.eclipse.jgit.ssh.apache.test",
- org.eclipse.jgit.transport.sshd;version="6.0.1";
+ org.eclipse.jgit.internal.transport.sshd.agent;version="6.1.1";x-internal:=true,
+ org.eclipse.jgit.internal.transport.sshd.auth;version="6.1.1";x-internal:=true,
+ org.eclipse.jgit.internal.transport.sshd.proxy;version="6.1.1";x-friends:="org.eclipse.jgit.ssh.apache.test",
+ org.eclipse.jgit.transport.sshd;version="6.1.1";
   uses:="org.eclipse.jgit.transport,
    org.apache.sshd.client.config.hosts,
    org.apache.sshd.common.keyprovider,
    org.eclipse.jgit.util,
    org.apache.sshd.client.session,
    org.apache.sshd.client.keyverifier",
- org.eclipse.jgit.transport.sshd.agent;version="6.0.1"
+ org.eclipse.jgit.transport.sshd.agent;version="6.1.1"
 Import-Package: net.i2p.crypto.eddsa;version="[0.3.0,0.4.0)",
- org.apache.sshd.agent;version="[2.7.0,2.8.0)",
- org.apache.sshd.client;version="[2.7.0,2.8.0)",
- org.apache.sshd.client.auth;version="[2.7.0,2.8.0)",
- org.apache.sshd.client.auth.keyboard;version="[2.7.0,2.8.0)",
- org.apache.sshd.client.auth.password;version="[2.7.0,2.8.0)",
- org.apache.sshd.client.auth.pubkey;version="[2.7.0,2.8.0)",
- org.apache.sshd.client.channel;version="[2.7.0,2.8.0)",
- org.apache.sshd.client.config.hosts;version="[2.7.0,2.8.0)",
- org.apache.sshd.client.config.keys;version="[2.7.0,2.8.0)",
- org.apache.sshd.client.future;version="[2.7.0,2.8.0)",
- org.apache.sshd.client.keyverifier;version="[2.7.0,2.8.0)",
- org.apache.sshd.client.session;version="[2.7.0,2.8.0)",
- org.apache.sshd.client.session.forward;version="[2.7.0,2.8.0)",
- org.apache.sshd.common;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.auth;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.channel;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.compression;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.config.keys;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.config.keys.loader;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.config.keys.loader.openssh.kdf;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.digest;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.forward;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.future;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.helpers;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.io;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.kex;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.kex.extension;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.kex.extension.parser;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.keyprovider;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.mac;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.random;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.session;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.session.helpers;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.signature;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.util;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.util.buffer;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.util.closeable;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.util.io;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.util.io.functors;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.util.io.resource;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.util.logging;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.util.net;version="[2.7.0,2.8.0)",
- org.apache.sshd.common.util.security;version="[2.7.0,2.8.0)",
- org.apache.sshd.core;version="[2.7.0,2.8.0)",
- org.apache.sshd.server.auth;version="[2.7.0,2.8.0)",
- org.apache.sshd.sftp;version="[2.7.0,2.8.0)",
- org.apache.sshd.sftp.client;version="[2.7.0,2.8.0)",
- org.apache.sshd.sftp.common;version="[2.7.0,2.8.0)",
- org.eclipse.jgit.annotations;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.errors;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.fnmatch;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.transport.ssh;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.nls;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.util;version="[6.0.1,6.1.0)",
+ org.apache.sshd.agent;version="[2.8.0,2.9.0)",
+ org.apache.sshd.client;version="[2.8.0,2.9.0)",
+ org.apache.sshd.client.auth;version="[2.8.0,2.9.0)",
+ org.apache.sshd.client.auth.keyboard;version="[2.8.0,2.9.0)",
+ org.apache.sshd.client.auth.password;version="[2.8.0,2.9.0)",
+ org.apache.sshd.client.auth.pubkey;version="[2.8.0,2.9.0)",
+ org.apache.sshd.client.channel;version="[2.8.0,2.9.0)",
+ org.apache.sshd.client.config.hosts;version="[2.8.0,2.9.0)",
+ org.apache.sshd.client.config.keys;version="[2.8.0,2.9.0)",
+ org.apache.sshd.client.future;version="[2.8.0,2.9.0)",
+ org.apache.sshd.client.keyverifier;version="[2.8.0,2.9.0)",
+ org.apache.sshd.client.session;version="[2.8.0,2.9.0)",
+ org.apache.sshd.client.session.forward;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.auth;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.channel;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.compression;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.config.keys;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.config.keys.loader;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.config.keys.loader.openssh.kdf;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.config.keys.u2f;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.digest;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.forward;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.future;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.helpers;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.io;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.kex;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.kex.extension;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.kex.extension.parser;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.keyprovider;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.mac;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.random;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.session;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.session.helpers;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.signature;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.util;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.util.buffer;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.util.buffer.keys;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.util.closeable;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.util.io;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.util.io.der;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.util.io.functors;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.util.io.resource;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.util.logging;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.util.net;version="[2.8.0,2.9.0)",
+ org.apache.sshd.common.util.security;version="[2.8.0,2.9.0)",
+ org.apache.sshd.core;version="[2.8.0,2.9.0)",
+ org.apache.sshd.server.auth;version="[2.8.0,2.9.0)",
+ org.apache.sshd.sftp;version="[2.8.0,2.9.0)",
+ org.apache.sshd.sftp.client;version="[2.8.0,2.9.0)",
+ org.apache.sshd.sftp.common;version="[2.8.0,2.9.0)",
+ org.eclipse.jgit.annotations;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.errors;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.fnmatch;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.storage.file;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.transport.ssh;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.nls;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.util;version="[6.1.1,6.2.0)",
  org.slf4j;version="[1.7.0,2.0.0)"
diff --git a/org.eclipse.jgit.ssh.apache/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.ssh.apache/META-INF/SOURCE-MANIFEST.MF
index b770b90..9fefa6e 100644
--- a/org.eclipse.jgit.ssh.apache/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.ssh.apache/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.ssh.apache - Sources
 Bundle-SymbolicName: org.eclipse.jgit.ssh.apache.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 6.0.1.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.ssh.apache;version="6.0.1.qualifier";roots="."
+Bundle-Version: 6.1.1.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.ssh.apache;version="6.1.1.qualifier";roots="."
diff --git a/org.eclipse.jgit.ssh.apache/README.md b/org.eclipse.jgit.ssh.apache/README.md
index cba87ac..f06b2f6 100644
--- a/org.eclipse.jgit.ssh.apache/README.md
+++ b/org.eclipse.jgit.ssh.apache/README.md
@@ -43,6 +43,53 @@
   .call();
 ```
 
+## Support for SSH agents
+
+There exist two IETF draft RFCs for communication with an SSH agent:
+
+* an older [SSH1 protocol](https://tools.ietf.org/html/draft-ietf-secsh-agent-02) that can deal only with DSA and RSA keys, and
+* a newer [SSH2 protocol](https://tools.ietf.org/html/draft-miller-ssh-agent-04) (from OpenSSH).
+
+JGit only supports the newer OpenSSH protocol.
+
+Communication with an SSH agent can occur over any transport protocol, and different
+SSH agents may use different transports for local communication. JGit provides some
+transports via the [org.eclipse.jgit.ssh.apache.agent](../org.eclipse.jgit.ssh.apache.agent/README.md)
+fragment, which are discovered from `org.eclipse.jgit.ssh.apache` also via the `ServiceLoader` mechanism;
+the SPI (service provider interface) is `org.eclipse.jgit.transport.sshd.agent.ConnectorFactory`.
+
+If such a `ConnectorFactory` implementation is found, JGit may use an SSH agent. If none
+is available, JGit cannot communicate with an SSH agent, and will not attempt to use one.
+
+### SSH configurations for SSH agents
+
+There are several SSH properties that can be used in the `~/.ssh/config` file to configure
+the use of an SSH agent. For the details, see the [OpenBSD ssh-config documentation](https://man.openbsd.org/ssh_config.5).
+
+* **AddKeysToAgent** can be set to `no`, `yes`, or `ask`. If set to `yes`, keys will be added
+  to the agent if they're not yet in the agent. If set to `ask`, the user will be prompted
+  before doing so, and can opt out of adding the key. JGit also supports the additional
+  settings `confirm` and key lifetimes.
+* **IdentityAgent** can be set to choose which SSH agent to use, if there are several running.
+  It can also be set to `none` to explicitly switch off using an SSH agent at all.
+* **IdentitiesOnly** if set to `yes` and an SSH agent is used, only keys from the agent that are
+  also listed in an `IdentityFile` property will be considered. (It'll also switch off trying
+  default key names, such as `~/.ssh/id_rsa` or `~/.ssh/id_ed25519`; only keys listed explicitly
+  will be used.)
+
+### Limitations
+
+As mentioned above JGit only implements the newer OpenSSH protocol. OpenSSH fully implements this,
+but some other SSH agents only offer partial implementations. In particular on Windows, neither
+Pageant nor Win32-OpenSSH implement the `confirm` or lifetime constraints for `AddKeysToAgent`. With
+such SSH agents, these settings should not be used in `~/.ssh/config`. GPG's gpg-agent can be run
+with option `enable_putty_support` and can then be used as a Pageant replacement. gpg-agent appears
+to support these key constraints.
+
+OpenSSH does not implement ed448 keys, and neither does Apache MINA sshd, and hence such keys are
+not supported in JGit if its built-in SSH implementation is used. ed448 or other unsupported keys
+provided by an SSH agent are ignored.
+
 ## Using a different SSH implementation
 
 To use a different SSH implementation:
diff --git a/org.eclipse.jgit.ssh.apache/pom.xml b/org.eclipse.jgit.ssh.apache/pom.xml
index 340cca1..674c7a4 100644
--- a/org.eclipse.jgit.ssh.apache/pom.xml
+++ b/org.eclipse.jgit.ssh.apache/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.ssh.apache</artifactId>
@@ -50,6 +50,16 @@
       <groupId>org.apache.sshd</groupId>
       <artifactId>sshd-sftp</artifactId>
       <version>${apache-sshd-version}</version>
+      <exclusions>
+          <exclusion>
+              <groupId>org.apache.sshd</groupId>
+              <artifactId>sshd-core</artifactId>
+          </exclusion>
+          <exclusion>
+              <groupId>org.apache.sshd</groupId>
+              <artifactId>sshd-common</artifactId>
+          </exclusion>
+      </exclusions>
     </dependency>
 
     <dependency>
@@ -154,6 +164,9 @@
                   <includes>
                       <include>org.eclipse.jgit.*</include>
                   </includes>
+                  <excludes>
+                      <exclude>*.internal.*</exclude>
+                  </excludes>
                   <accessModifier>public</accessModifier>
                   <breakBuildOnModifications>false</breakBuildOnModifications>
                   <breakBuildOnBinaryIncompatibleModifications>false</breakBuildOnBinaryIncompatibleModifications>
@@ -207,6 +220,9 @@
                   <includes>
                       <include>org.eclipse.jgit.*</include>
                   </includes>
+                  <excludes>
+                      <exclude>*.internal.*</exclude>
+                  </excludes>
                   <accessModifier>public</accessModifier>
                   <breakBuildOnModifications>false</breakBuildOnModifications>
                   <breakBuildOnBinaryIncompatibleModifications>false</breakBuildOnBinaryIncompatibleModifications>
diff --git a/org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties b/org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties
index 2bba736..4f735ba 100644
--- a/org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties
+++ b/org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties
@@ -1,5 +1,6 @@
 authenticationCanceled=SSH authentication canceled: no password given
 authenticationOnClosedSession=Authentication canceled: session is already closing or closed
+cannotReadPublicKey=Cannot read public key from file {0}
 closeListenerFailed=Ssh session close listener failed
 configInvalidPath=Invalid path in ssh config key {0}: {1}
 configInvalidPattern=Invalid pattern in ssh config key {0}: {1}
@@ -77,6 +78,8 @@
 proxySocksUnexpectedMessage=Unexpected message received from SOCKS5 proxy {0}; client state {1}: {2}
 proxySocksUnexpectedVersion=Expected SOCKS version 5, got {0}
 proxySocksUsernameTooLong=User name for proxy {0} must be at most 255 bytes long, is {1} bytes: {2}
+pubkeyAuthAddKeyToAgentError=Could not add {0} key with fingerprint {1} to the SSH agent
+pubkeyAuthAddKeyToAgentQuestion=Add the {0} key with fingerprint {1} to the SSH agent?
 pubkeyAuthWrongCommand=Public key authentication received unknown SSH command {0} from {1} ({2})
 pubkeyAuthWrongKey=Public key authentication received wrong key; sent {0}, got back {1} from {2} ({3})
 pubkeyAuthWrongSignatureAlgorithm=Public key authentication requested signature type {0} but got back {1} from {2} ({3})
@@ -85,9 +88,13 @@
 serverIdWithNul=Server identification contains a NUL character: {0}
 sessionCloseFailed=Closing the session failed
 sessionWithoutUsername=SSH session created without user name; cannot authenticate
+sshAgentEdDSAFormatError=Cannot add ed25519 key to the SSH agent because it is encoded as {0} instead of PKCS#8
+sshAgentPayloadLengthError=Expected {0,choice,0#no bytes|1#one byte|1<{0} bytes} but got {1}
 sshAgentReplyLengthError=Invalid SSH agent reply message length {0} after command {1}
 sshAgentReplyUnexpected=Unexpected reply from ssh-agent: {0}
 sshAgentShortReadBuffer=Short read from SSH agent
+sshAgentUnknownKey=SSH agent delivered a key that cannot be handled
+sshAgentWrongKeyLength=SSH agent delivered illogical key length {0} at offset {1} in buffer of length {2}
 sshAgentWrongNumberOfKeys=Invalid number of SSH agent keys: {0}
 sshClosingDown=Apache MINA sshd session factory is closing down; cannot create new ssh sessions on this factory
 sshCommandTimeout={0} timed out after {1} seconds while opening the channel
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java
index e2dbb4c..5100bc9 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java
@@ -42,7 +42,6 @@
 import org.apache.sshd.common.io.IoWriteFuture;
 import org.apache.sshd.common.kex.BuiltinDHFactories;
 import org.apache.sshd.common.kex.DHFactory;
-import org.apache.sshd.common.kex.KexProposalOption;
 import org.apache.sshd.common.kex.KeyExchangeFactory;
 import org.apache.sshd.common.kex.extension.KexExtensionHandler;
 import org.apache.sshd.common.kex.extension.KexExtensionHandler.AvailabilityPhase;
@@ -199,24 +198,6 @@
 		}
 	}
 
-	@Override
-	protected Map<KexProposalOption, String> setNegotiationResult(
-			Map<KexProposalOption, String> guess) {
-		Map<KexProposalOption, String> result = super.setNegotiationResult(
-				guess);
-		// This should be doable with a SessionListener, too, but I don't see
-		// how to add a listener in time to catch the negotiation end for sure
-		// given that the super-constructor already starts KEX.
-		//
-		// TODO: This override can be removed once we use sshd 2.8.0.
-		if (log.isDebugEnabled()) {
-			result.forEach((option, value) -> log.debug(
-					"setNegotiationResult({}) Kex: {} = {}", this, //$NON-NLS-1$
-					option.getDescription(), value));
-		}
-		return result;
-	}
-
 	Set<String> getAllAvailableSignatureAlgorithms() {
 		Set<String> allAvailable = new HashSet<>();
 		BuiltinSignatures.VALUES.forEach(s -> allAvailable.add(s.getName()));
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthentication.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthentication.java
index c082a9a..96da0cc 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthentication.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthentication.java
@@ -12,25 +12,68 @@
 import static java.text.MessageFormat.format;
 import static org.eclipse.jgit.transport.SshConstants.PUBKEY_ACCEPTED_ALGORITHMS;
 
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.InvalidPathException;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
+import org.apache.sshd.agent.SshAgent;
+import org.apache.sshd.agent.SshAgentFactory;
+import org.apache.sshd.agent.SshAgentKeyConstraint;
 import org.apache.sshd.client.auth.pubkey.KeyAgentIdentity;
 import org.apache.sshd.client.auth.pubkey.PublicKeyIdentity;
 import org.apache.sshd.client.auth.pubkey.UserAuthPublicKey;
+import org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyIterator;
 import org.apache.sshd.client.config.hosts.HostConfigEntry;
 import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.FactoryManager;
 import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
+import org.apache.sshd.common.config.keys.u2f.SecurityKeyPublicKey;
 import org.apache.sshd.common.signature.Signature;
+import org.apache.sshd.common.signature.SignatureFactoriesManager;
+import org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile;
+import org.eclipse.jgit.transport.CredentialItem;
+import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.SshConstants;
+import org.eclipse.jgit.transport.URIish;
 import org.eclipse.jgit.util.StringUtils;
 
 /**
  * Custom {@link UserAuthPublicKey} implementation for handling SSH config
- * PubkeyAcceptedAlgorithms.
+ * PubkeyAcceptedAlgorithms and interaction with the SSH agent.
  */
 public class JGitPublicKeyAuthentication extends UserAuthPublicKey {
 
+	private SshAgent agent;
+
+	private HostConfigEntry hostConfig;
+
+	private boolean addKeysToAgent;
+
+	private boolean askBeforeAdding;
+
+	private String skProvider;
+
+	private SshAgentKeyConstraint[] constraints;
+
 	JGitPublicKeyAuthentication(List<NamedFactory<Signature>> factories) {
 		super(factories);
 	}
@@ -43,7 +86,7 @@
 					+ rawSession.getClass().getCanonicalName());
 		}
 		JGitClientSession session = (JGitClientSession) rawSession;
-		HostConfigEntry hostConfig = session.getHostConfigEntry();
+		hostConfig = session.getHostConfigEntry();
 		// Set signature algorithms for public key authentication
 		String pubkeyAlgos = hostConfig.getProperty(PUBKEY_ACCEPTED_ALGORITHMS);
 		if (!StringUtils.isEmptyOrNull(pubkeyAlgos)) {
@@ -64,46 +107,291 @@
 		// If we don't set signature factories here, the default ones from the
 		// session will be used.
 		super.init(session, service);
-		// In sshd 2.7.0, we end up now with a key iterator that uses keys
-		// provided by an ssh-agent even if IdentitiesOnly is true. So if
-		// needed, filter out any KeyAgentIdentity.
-		if (hostConfig.isIdentitiesOnly()) {
-			Iterator<PublicKeyIdentity> original = keys;
-			// The original iterator will already have gotten the identities
-			// from the agent. Unfortunately there's nothing we can do about
-			// that; it'll have to be fixed upstream. (As will, ultimately,
-			// respecting isIdentitiesOnly().) At least we can simply not
-			// use the keys the agent provided.
-			//
-			// See https://issues.apache.org/jira/browse/SSHD-1218
-			keys = new Iterator<>() {
+	}
 
-				private PublicKeyIdentity value;
+	@Override
+	protected Iterator<PublicKeyIdentity> createPublicKeyIterator(
+			ClientSession session, SignatureFactoriesManager manager)
+			throws Exception {
+		agent = getAgent(session);
+		if (agent != null) {
+			parseAddKeys(hostConfig);
+			if (addKeysToAgent) {
+				skProvider = hostConfig.getProperty(SshConstants.SECURITY_KEY_PROVIDER);
+			}
+		}
+		return new KeyIterator(session, manager);
+	}
+
+	@Override
+	protected PublicKeyIdentity resolveAttemptedPublicKeyIdentity(
+			ClientSession session, String service) throws Exception {
+		PublicKeyIdentity result = getNextKey(session, service);
+		// This fixes SSHD-1231. Can be removed once we're using Apache MINA
+		// sshd > 2.8.0.
+		//
+		// See https://issues.apache.org/jira/browse/SSHD-1231
+		currentAlgorithms.clear();
+		return result;
+	}
+
+	private PublicKeyIdentity getNextKey(ClientSession session, String service)
+			throws Exception {
+		PublicKeyIdentity id = super.resolveAttemptedPublicKeyIdentity(session,
+				service);
+		if (addKeysToAgent && id != null && !(id instanceof KeyAgentIdentity)) {
+			KeyPair key = id.getKeyIdentity();
+			if (key != null && key.getPublic() != null
+					&& key.getPrivate() != null) {
+				// We've just successfully loaded a key that wasn't in the
+				// agent. Add it to the agent.
+				//
+				// Keys are added after loading, as in OpenSSH. The alternative
+				// might be to add a key only after (partially) successful
+				// authentication?
+				PublicKey pk = key.getPublic();
+				String fingerprint = KeyUtils.getFingerPrint(pk);
+				String keyType = KeyUtils.getKeyType(key);
+				try {
+					// Check that the key is not in the agent already.
+					if (agentHasKey(pk)) {
+						return id;
+					}
+					if (askBeforeAdding
+							&& (session instanceof JGitClientSession)) {
+						CredentialsProvider provider = ((JGitClientSession) session)
+								.getCredentialsProvider();
+						CredentialItem.YesNoType question = new CredentialItem.YesNoType(
+								format(SshdText
+										.get().pubkeyAuthAddKeyToAgentQuestion,
+										keyType, fingerprint));
+						boolean result = provider != null
+								&& provider.supports(question)
+								&& provider.get(getUri(), question);
+						if (!result || !question.getValue()) {
+							// Don't add the key.
+							return id;
+						}
+					}
+					SshAgentKeyConstraint[] rules = constraints;
+					if (pk instanceof SecurityKeyPublicKey && !StringUtils.isEmptyOrNull(skProvider)) {
+						rules = Arrays.copyOf(rules, rules.length + 1);
+						rules[rules.length - 1] =
+								new SshAgentKeyConstraint.FidoProviderExtension(skProvider);
+					}
+					// Unfortunately a comment associated with the key is lost
+					// by Apache MINA sshd, and there is also no way to get the
+					// original file name for keys loaded from a file. So add it
+					// without comment.
+					agent.addIdentity(key, null, rules);
+				} catch (IOException e) {
+					// Do not re-throw: we don't want authentication to fail if
+					// we cannot add the key to the agent.
+					log.error(
+							format(SshdText.get().pubkeyAuthAddKeyToAgentError,
+									keyType, fingerprint),
+							e);
+					// Note that as of Win32-OpenSSH 8.6 and Pageant 0.76,
+					// neither can handle key constraints. Pageant fails
+					// gracefully, not adding the key and returning
+					// SSH_AGENT_FAILURE. Win32-OpenSSH closes the connection
+					// without even returning a failure message, which violates
+					// the SSH agent protocol and makes all subsequent requests
+					// to the agent fail.
+				}
+			}
+		}
+		return id;
+	}
+
+	private boolean agentHasKey(PublicKey pk) throws IOException {
+		Iterable<? extends Map.Entry<PublicKey, String>> ids = agent
+				.getIdentities();
+		if (ids == null) {
+			return false;
+		}
+		Iterator<? extends Map.Entry<PublicKey, String>> iter = ids.iterator();
+		while (iter.hasNext()) {
+			if (KeyUtils.compareKeys(iter.next().getKey(), pk)) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	private URIish getUri() {
+		String uri = SshConstants.SSH_SCHEME + "://"; //$NON-NLS-1$
+		String userName = hostConfig.getUsername();
+		if (!StringUtils.isEmptyOrNull(userName)) {
+			uri += userName + '@';
+		}
+		uri += hostConfig.getHost();
+		int port = hostConfig.getPort();
+		if (port > 0 && port != SshConstants.SSH_DEFAULT_PORT) {
+			uri += ":" + port; //$NON-NLS-1$
+		}
+		try {
+			return new URIish(uri);
+		} catch (URISyntaxException e) {
+			log.error(e.getLocalizedMessage(), e);
+		}
+		return new URIish();
+	}
+
+	private SshAgent getAgent(ClientSession session) throws Exception {
+		FactoryManager manager = Objects.requireNonNull(
+				session.getFactoryManager(), "No session factory manager"); //$NON-NLS-1$
+		SshAgentFactory factory = manager.getAgentFactory();
+		if (factory == null) {
+			return null;
+		}
+		return factory.createClient(session, manager);
+	}
+
+	private void parseAddKeys(HostConfigEntry config) {
+		String value = config.getProperty(SshConstants.ADD_KEYS_TO_AGENT);
+		if (StringUtils.isEmptyOrNull(value)) {
+			addKeysToAgent = false;
+			return;
+		}
+		String[] values = value.split(","); //$NON-NLS-1$
+		List<SshAgentKeyConstraint> rules = new ArrayList<>(2);
+		switch (values[0]) {
+		case "yes": //$NON-NLS-1$
+			addKeysToAgent = true;
+			break;
+		case "no": //$NON-NLS-1$
+			addKeysToAgent = false;
+			break;
+		case "ask": //$NON-NLS-1$
+			addKeysToAgent = true;
+			askBeforeAdding = true;
+			break;
+		case "confirm": //$NON-NLS-1$
+			addKeysToAgent = true;
+			rules.add(SshAgentKeyConstraint.CONFIRM);
+			if (values.length > 1) {
+				int seconds = OpenSshConfigFile.timeSpec(values[1]);
+				if (seconds > 0) {
+					rules.add(new SshAgentKeyConstraint.LifeTime(seconds));
+				}
+			}
+			break;
+		default:
+			int seconds = OpenSshConfigFile.timeSpec(values[0]);
+			if (seconds > 0) {
+				addKeysToAgent = true;
+				rules.add(new SshAgentKeyConstraint.LifeTime(seconds));
+			}
+			break;
+		}
+		constraints = rules.toArray(new SshAgentKeyConstraint[0]);
+	}
+
+	@Override
+	protected void releaseKeys() throws IOException {
+		addKeysToAgent = false;
+		askBeforeAdding = false;
+		skProvider = null;
+		constraints = null;
+		try {
+			if (agent != null) {
+				try {
+					agent.close();
+				} finally {
+					agent = null;
+				}
+			}
+		} finally {
+			super.releaseKeys();
+		}
+	}
+
+	private class KeyIterator extends UserAuthPublicKeyIterator {
+
+		private Iterable<? extends Map.Entry<PublicKey, String>> agentKeys;
+
+		// If non-null, all the public keys from explicitly given key files. Any
+		// agent key not matching one of these public keys will be ignored in
+		// getIdentities().
+		private Collection<PublicKey> identityFiles;
+
+		public KeyIterator(ClientSession session,
+				SignatureFactoriesManager manager)
+				throws Exception {
+			super(session, manager);
+		}
+
+		private List<PublicKey> getExplicitKeys(
+				Collection<String> explicitFiles) {
+			if (explicitFiles == null) {
+				return null;
+			}
+			return explicitFiles.stream().map(s -> {
+				try {
+					Path p = Paths.get(s + ".pub"); //$NON-NLS-1$
+					if (Files.isRegularFile(p, LinkOption.NOFOLLOW_LINKS)) {
+						return AuthorizedKeyEntry.readAuthorizedKeys(p).get(0)
+								.resolvePublicKey(null,
+										PublicKeyEntryResolver.IGNORING);
+					}
+				} catch (InvalidPathException | IOException
+						| GeneralSecurityException e) {
+					log.warn(format(SshdText.get().cannotReadPublicKey, s), e);
+				}
+				return null;
+			}).filter(Objects::nonNull).collect(Collectors.toList());
+		}
+
+		@Override
+		protected Iterable<KeyAgentIdentity> initializeAgentIdentities(
+				ClientSession session) throws IOException {
+			if (agent == null) {
+				return null;
+			}
+			agentKeys = agent.getIdentities();
+			if (hostConfig != null && hostConfig.isIdentitiesOnly()) {
+				identityFiles = getExplicitKeys(hostConfig.getIdentities());
+			}
+			return () -> new Iterator<>() {
+
+				private final Iterator<? extends Map.Entry<PublicKey, String>> iter = agentKeys
+						.iterator();
+
+				private Map.Entry<PublicKey, String> next;
 
 				@Override
 				public boolean hasNext() {
-					if (value != null) {
-						return true;
-					}
-					PublicKeyIdentity next = null;
-					while (original.hasNext()) {
-						next = original.next();
-						if (!(next instanceof KeyAgentIdentity)) {
-							value = next;
+					while (next == null && iter.hasNext()) {
+						Map.Entry<PublicKey, String> val = iter.next();
+						PublicKey pk = val.getKey();
+						// This checks against all explicit keys for any agent
+						// key, but since identityFiles.size() is typically 1,
+						// it should be fine.
+						if (identityFiles == null || identityFiles.stream()
+								.anyMatch(k -> KeyUtils.compareKeys(k, pk))) {
+							next = val;
 							return true;
 						}
+						if (log.isTraceEnabled()) {
+							log.trace(
+									"Ignoring SSH agent {} key not in explicit IdentityFile in SSH config: {}", //$NON-NLS-1$
+									KeyUtils.getKeyType(pk),
+									KeyUtils.getFingerPrint(pk));
+						}
 					}
-					return false;
+					return next != null;
 				}
 
 				@Override
-				public PublicKeyIdentity next() {
-					if (hasNext()) {
-						PublicKeyIdentity result = value;
-						value = null;
-						return result;
+				public KeyAgentIdentity next() {
+					if (!hasNext()) {
+						throw new NoSuchElementException();
 					}
-					throw new NoSuchElementException();
+					KeyAgentIdentity result = new KeyAgentIdentity(agent,
+							next.getKey(), next.getValue());
+					next = null;
+					return result;
 				}
 			};
 		}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java
index 00ee62d..19ad85c 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java
@@ -30,6 +30,7 @@
 	/***/ public String authenticationCanceled;
 	/***/ public String authenticationOnClosedSession;
 	/***/ public String closeListenerFailed;
+	/***/ public String cannotReadPublicKey;
 	/***/ public String configInvalidPath;
 	/***/ public String configInvalidPattern;
 	/***/ public String configInvalidPositive;
@@ -98,6 +99,8 @@
 	/***/ public String proxySocksUnexpectedMessage;
 	/***/ public String proxySocksUnexpectedVersion;
 	/***/ public String proxySocksUsernameTooLong;
+	/***/ public String pubkeyAuthAddKeyToAgentError;
+	/***/ public String pubkeyAuthAddKeyToAgentQuestion;
 	/***/ public String pubkeyAuthWrongCommand;
 	/***/ public String pubkeyAuthWrongKey;
 	/***/ public String pubkeyAuthWrongSignatureAlgorithm;
@@ -106,9 +109,13 @@
 	/***/ public String serverIdWithNul;
 	/***/ public String sessionCloseFailed;
 	/***/ public String sessionWithoutUsername;
+	/***/ public String sshAgentEdDSAFormatError;
+	/***/ public String sshAgentPayloadLengthError;
 	/***/ public String sshAgentReplyLengthError;
 	/***/ public String sshAgentReplyUnexpected;
 	/***/ public String sshAgentShortReadBuffer;
+	/***/ public String sshAgentUnknownKey;
+	/***/ public String sshAgentWrongKeyLength;
 	/***/ public String sshAgentWrongNumberOfKeys;
 	/***/ public String sshClosingDown;
 	/***/ public String sshCommandTimeout;
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/JGitSshAgentFactory.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/JGitSshAgentFactory.java
index 1ed2ab9..a0ffd54 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/JGitSshAgentFactory.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/JGitSshAgentFactory.java
@@ -17,10 +17,14 @@
 import org.apache.sshd.agent.SshAgent;
 import org.apache.sshd.agent.SshAgentFactory;
 import org.apache.sshd.agent.SshAgentServer;
+import org.apache.sshd.client.config.hosts.HostConfigEntry;
 import org.apache.sshd.common.FactoryManager;
 import org.apache.sshd.common.channel.ChannelFactory;
 import org.apache.sshd.common.session.ConnectionService;
+import org.apache.sshd.common.session.Session;
 import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.internal.transport.sshd.JGitClientSession;
+import org.eclipse.jgit.transport.SshConstants;
 import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory;
 
 /**
@@ -49,18 +53,24 @@
 	@Override
 	public List<ChannelFactory> getChannelForwardingFactories(
 			FactoryManager manager) {
-		// No agent forwarding supported yet.
+		// No agent forwarding supported.
 		return Collections.emptyList();
 	}
 
 	@Override
-	public SshAgent createClient(FactoryManager manager) throws IOException {
-		// sshd 2.8.0 will pass us the session here. At that point, we can get
-		// the HostConfigEntry and extract and handle the IdentityAgent setting.
-		// For now, pass null to let the ConnectorFactory do its default
-		// behavior (Pageant on Windows, SSH_AUTH_SOCK on Unixes with the
-		// jgit-builtin factory).
-		return new SshAgentClient(factory.create(null, homeDir));
+	public SshAgent createClient(Session session, FactoryManager manager)
+			throws IOException {
+		String identityAgent = null;
+		if (session instanceof JGitClientSession) {
+			HostConfigEntry hostConfig = ((JGitClientSession) session)
+					.getHostConfigEntry();
+			identityAgent = hostConfig.getProperty(SshConstants.IDENTITY_AGENT,
+					null);
+		}
+		if (SshConstants.NONE.equals(identityAgent)) {
+			return null;
+		}
+		return new SshAgentClient(factory.create(identityAgent, homeDir));
 	}
 
 	@Override
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/SshAgentClient.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/SshAgentClient.java
index 08483e4..cbcb4d2 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/SshAgentClient.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/SshAgentClient.java
@@ -11,10 +11,12 @@
 
 import java.io.IOException;
 import java.security.KeyPair;
+import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.text.MessageFormat;
 import java.util.AbstractMap;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -22,21 +24,27 @@
 
 import org.apache.sshd.agent.SshAgent;
 import org.apache.sshd.agent.SshAgentConstants;
+import org.apache.sshd.agent.SshAgentKeyConstraint;
 import org.apache.sshd.common.SshException;
 import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.BufferException;
 import org.apache.sshd.common.util.buffer.BufferUtils;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+import org.apache.sshd.common.util.buffer.keys.BufferPublicKeyParser;
+import org.apache.sshd.common.util.io.der.DERParser;
 import org.eclipse.jgit.internal.transport.sshd.SshdText;
 import org.eclipse.jgit.transport.sshd.agent.Connector;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * A client for an SSH2 agent. This client supports only querying identities and
- * signature requests.
+ * A client for an SSH2 agent. This client supports querying identities,
+ * signature requests, and adding keys to an agent (with or without
+ * constraints). Removing keys is not supported, and the older SSH1 protocol is
+ * not supported.
  *
  * @see <a href="https://tools.ietf.org/html/draft-miller-ssh-agent-04">SSH
  *      Agent Protocol, RFC draft</a>
@@ -72,11 +80,18 @@
 			}
 			return false;
 		}
-		boolean connected = connector != null && connector.connect();
-		if (!connected) {
-			if (debugging) {
-				LOG.debug("No SSH agent (SSH_AUTH_SOCK not set)"); //$NON-NLS-1$
+		boolean connected;
+		try {
+			connected = connector != null && connector.connect();
+			if (!connected && debugging) {
+				LOG.debug("No SSH agent"); //$NON-NLS-1$
 			}
+		} catch (IOException e) {
+			// Agent not running?
+			if (debugging) {
+				LOG.debug("No SSH agent", e); //$NON-NLS-1$
+			}
+			throw e;
 		}
 		return connected;
 	}
@@ -127,14 +142,17 @@
 			List<Map.Entry<PublicKey, String>> keys = new ArrayList<>(
 					numberOfKeys);
 			for (int i = 0; i < numberOfKeys; i++) {
-				PublicKey key = reply.getPublicKey();
+				PublicKey key = readKey(reply);
 				String comment = reply.getString();
-				if (tracing) {
-					LOG.trace("Got SSH agent {} key: {} {}", //$NON-NLS-1$
-							KeyUtils.getKeyType(key),
-							KeyUtils.getFingerPrint(key), comment);
+				if (key != null) {
+					if (tracing) {
+						LOG.trace("Got SSH agent {} key: {} {}", //$NON-NLS-1$
+								KeyUtils.getKeyType(key),
+								KeyUtils.getFingerPrint(key), comment);
+					}
+					keys.add(new AbstractMap.SimpleImmutableEntry<>(key,
+							comment));
 				}
-				keys.add(new AbstractMap.SimpleImmutableEntry<>(key, comment));
 			}
 			return keys;
 		} catch (BufferException e) {
@@ -216,6 +234,222 @@
 		}
 	}
 
+	@Override
+	public void addIdentity(KeyPair key, String comment,
+			SshAgentKeyConstraint... constraints) throws IOException {
+		boolean debugging = LOG.isDebugEnabled();
+		if (!open(debugging)) {
+			return;
+		}
+
+		// Neither Pageant 0.76 nor Win32-OpenSSH 8.6 support command
+		// SSH2_AGENTC_ADD_ID_CONSTRAINED. Adding a key with constraints will
+		// fail. The only work-around for users is not to use "confirm" or "time
+		// spec" with AddKeysToAgent, and not to use sk-* keys.
+		//
+		// With a true OpenSSH SSH agent, key constraints work.
+		byte cmd = (constraints != null && constraints.length > 0)
+				? SshAgentConstants.SSH2_AGENTC_ADD_ID_CONSTRAINED
+				: SshAgentConstants.SSH2_AGENTC_ADD_IDENTITY;
+		byte[] message = null;
+		ByteArrayBuffer msg = new ByteArrayBuffer();
+		try {
+			msg.putInt(0);
+			msg.putByte(cmd);
+			String keyType = KeyUtils.getKeyType(key);
+			if (KeyPairProvider.SSH_ED25519.equals(keyType)) {
+				// Apache MINA sshd 2.8.0 lacks support for writing ed25519
+				// private keys to a buffer.
+				putEd25519Key(msg, key);
+			} else {
+				msg.putKeyPair(key);
+			}
+			msg.putString(comment == null ? "" : comment); //$NON-NLS-1$
+			if (constraints != null) {
+				for (SshAgentKeyConstraint constraint : constraints) {
+					constraint.put(msg);
+				}
+			}
+			if (debugging) {
+				LOG.debug(
+						"addIdentity: adding {} key {} to SSH agent; comment {}", //$NON-NLS-1$
+						keyType, KeyUtils.getFingerPrint(key.getPublic()),
+						comment);
+			}
+			message = msg.getCompactData();
+		} finally {
+			// The message contains the private key data, so clear intermediary
+			// data ASAP.
+			msg.clear();
+		}
+		Buffer reply;
+		try {
+			reply = rpc(cmd, message);
+		} finally {
+			Arrays.fill(message, (byte) 0);
+		}
+		int replyLength = reply.available();
+		if (replyLength != 1) {
+			throw new SshException(MessageFormat.format(
+					SshdText.get().sshAgentReplyUnexpected,
+					MessageFormat.format(
+							SshdText.get().sshAgentPayloadLengthError,
+							Integer.valueOf(1), Integer.valueOf(replyLength))));
+
+		}
+		cmd = reply.getByte();
+		if (cmd != SshAgentConstants.SSH_AGENT_SUCCESS) {
+			throw new SshException(
+					MessageFormat.format(SshdText.get().sshAgentReplyUnexpected,
+							SshAgentConstants.getCommandMessageName(cmd)));
+		}
+	}
+
+	/**
+	 * Writes an ed25519 {@link KeyPair} to a {@link Buffer}. OpenSSH specifies
+	 * that it expects the 32 public key bytes, followed by 64 bytes formed by
+	 * concatenating the 32 private key bytes with the 32 public key bytes.
+	 *
+	 * @param msg
+	 *            {@link Buffer} to write to
+	 * @param key
+	 *            {@link KeyPair} to write
+	 * @throws IOException
+	 *             if the private key cannot be written
+	 */
+	private static void putEd25519Key(Buffer msg, KeyPair key)
+			throws IOException {
+		Buffer tmp = new ByteArrayBuffer(36);
+		tmp.putRawPublicKeyBytes(key.getPublic());
+		byte[] publicBytes = tmp.getBytes();
+		msg.putString(KeyPairProvider.SSH_ED25519);
+		msg.putBytes(publicBytes);
+		// Next is the concatenation of the 32 byte private key value with the
+		// 32 bytes of the public key.
+		PrivateKey pk = key.getPrivate();
+		String format = pk.getFormat();
+		if (!"PKCS#8".equalsIgnoreCase(format)) { //$NON-NLS-1$
+			throw new IOException(MessageFormat
+					.format(SshdText.get().sshAgentEdDSAFormatError, format));
+		}
+		byte[] privateBytes = null;
+		byte[] encoded = pk.getEncoded();
+		try {
+			privateBytes = asn1Parse(encoded, 32);
+			byte[] combined = Arrays.copyOf(privateBytes, 64);
+			Arrays.fill(privateBytes, (byte) 0);
+			privateBytes = combined;
+			System.arraycopy(publicBytes, 0, privateBytes, 32, 32);
+			msg.putBytes(privateBytes);
+		} finally {
+			if (privateBytes != null) {
+				Arrays.fill(privateBytes, (byte) 0);
+			}
+			Arrays.fill(encoded, (byte) 0);
+		}
+	}
+
+	/**
+	 * Extracts the private key bytes from an encoded ed25519 private key by
+	 * parsing the bytes as ASN.1 according to RFC 5958 (PKCS #8 encoding):
+	 *
+	 * <pre>
+	 * OneAsymmetricKey ::= SEQUENCE {
+	 *   version Version,
+	 *   privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
+	 *   privateKey PrivateKey,
+	 *   ...
+	 * }
+	 *
+	 * Version ::= INTEGER
+	 * PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
+	 * PrivateKey ::= OCTET STRING
+	 *
+	 * AlgorithmIdentifier  ::=  SEQUENCE  {
+	 *   algorithm   OBJECT IDENTIFIER,
+	 *   parameters  ANY DEFINED BY algorithm OPTIONAL
+	 * }
+	 * </pre>
+	 * <p>
+	 * and RFC 8410: "... when encoding a OneAsymmetricKey object, the private
+	 * key is wrapped in a CurvePrivateKey object and wrapped by the OCTET
+	 * STRING of the 'privateKey' field."
+	 * </p>
+	 *
+	 * <pre>
+	 * CurvePrivateKey ::= OCTET STRING
+	 * </pre>
+	 *
+	 * @param encoded
+	 *            encoded private key to extract the private key bytes from
+	 * @param n
+	 *            number of bytes expected
+	 * @return the extracted private key bytes; of length {@code n}
+	 * @throws IOException
+	 *             if the private key cannot be extracted
+	 * @see <a href="https://tools.ietf.org/html/rfc5958">RFC 5958</a>
+	 * @see <a href="https://tools.ietf.org/html/rfc8410">RFC 8410</a>
+	 */
+	private static byte[] asn1Parse(byte[] encoded, int n) throws IOException {
+		byte[] privateKey = null;
+		try (DERParser byteParser = new DERParser(encoded);
+				DERParser oneAsymmetricKey = byteParser.readObject()
+						.createParser()) {
+			oneAsymmetricKey.readObject(); // skip version
+			oneAsymmetricKey.readObject(); // skip algorithm identifier
+			privateKey = oneAsymmetricKey.readObject().getValue();
+			// The last n bytes of this must be the private key bytes
+			return Arrays.copyOfRange(privateKey,
+					privateKey.length - n, privateKey.length);
+		} finally {
+			if (privateKey != null) {
+				Arrays.fill(privateKey, (byte) 0);
+			}
+		}
+	}
+
+	/**
+	 * A safe version of {@link Buffer#getPublicKey()}. Upon return the
+	 * buffers's read position is always after the key blob; any exceptions
+	 * thrown by trying to read the key are logged and <em>not</em> propagated.
+	 * <p>
+	 * This is needed because an SSH agent might contain and deliver keys that
+	 * we cannot handle (for instance ed448 keys).
+	 * </p>
+	 *
+	 * @param buffer
+	 *            to read the key from
+	 * @return the {@link PublicKey}, or {@code null} if the key could not be
+	 *         read
+	 * @throws BufferException
+	 *             if the length of the key blob cannot be read or is corrupted
+	 */
+	private static PublicKey readKey(Buffer buffer) throws BufferException {
+		int endOfBuffer = buffer.wpos();
+		int keyLength = buffer.getInt();
+		int afterKey = buffer.rpos() + keyLength;
+		if (keyLength <= 0 || afterKey > endOfBuffer) {
+			throw new BufferException(
+					MessageFormat.format(SshdText.get().sshAgentWrongKeyLength,
+							Integer.toString(keyLength),
+							Integer.toString(buffer.rpos()),
+							Integer.toString(endOfBuffer)));
+		}
+		// Limit subsequent reads to the public key blob
+		buffer.wpos(afterKey);
+		try {
+			return buffer.getRawPublicKey(BufferPublicKeyParser.DEFAULT);
+		} catch (Exception e) {
+			LOG.warn(SshdText.get().sshAgentUnknownKey, e);
+			return null;
+		} finally {
+			// Restore real buffer end
+			buffer.wpos(endOfBuffer);
+			// Set the read position to after this key, even if failed
+			buffer.rpos(afterKey);
+		}
+	}
+
 	private Buffer rpc(byte command, byte[] message) throws IOException {
 		return new ByteArrayBuffer(connector.rpc(command, message));
 	}
@@ -230,11 +464,6 @@
 	}
 
 	@Override
-	public void addIdentity(KeyPair key, String comment) throws IOException {
-		throw new UnsupportedOperationException();
-	}
-
-	@Override
 	public void removeIdentity(PublicKey key) throws IOException {
 		throw new UnsupportedOperationException();
 	}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSession.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSession.java
index c270b44..b742f5e 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSession.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSession.java
@@ -51,6 +51,7 @@
 import org.apache.sshd.sftp.common.SftpException;
 import org.eclipse.jgit.annotations.NonNull;
 import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile;
 import org.eclipse.jgit.internal.transport.sshd.JGitSshClient;
 import org.eclipse.jgit.internal.transport.sshd.SshdText;
 import org.eclipse.jgit.transport.FtpChannel;
@@ -138,7 +139,11 @@
 						JGitSshClient.LOCAL_FORWARD_ADDRESS,
 						portForward.getBoundAddress());
 			}
-			resultSession = connect(hostConfig, context, timeout);
+			int timeoutInSec = OpenSshConfigFile.timeSpec(
+					hostConfig.getProperty(SshConstants.CONNECT_TIMEOUT));
+			resultSession = connect(hostConfig, context,
+					timeoutInSec > 0 ? Duration.ofSeconds(timeoutInSec)
+							: timeout);
 			if (proxySession != null) {
 				final PortForwardingTracker tracker = portForward;
 				final ClientSession pSession = proxySession;
diff --git a/org.eclipse.jgit.ssh.jsch.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.ssh.jsch.test/META-INF/MANIFEST.MF
index 52e69bd..4a08940 100644
--- a/org.eclipse.jgit.ssh.jsch.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ssh.jsch.test/META-INF/MANIFEST.MF
@@ -3,18 +3,18 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.ssh.jsch.test
 Bundle-SymbolicName: org.eclipse.jgit.ssh.jsch.test
-Bundle-Version: 6.0.1.qualifier
+Bundle-Version: 6.1.1.qualifier
 Bundle-Vendor: %Bundle-Vendor
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-11
 Import-Package: com.jcraft.jsch;version="[0.1.54,0.2.0)",
- org.eclipse.jgit.errors;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.junit;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.junit.ssh;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lib;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport.ssh.jsch;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.util;version="[6.0.1,6.1.0)",
+ org.eclipse.jgit.errors;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.junit;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.junit.ssh;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lib;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport.ssh.jsch;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.util;version="[6.1.1,6.2.0)",
  org.hamcrest;version="[1.1.0,3.0.0)",
  org.junit;version="[4.13,5.0.0)",
  org.junit.experimental.theories;version="[4.13,5.0.0)",
diff --git a/org.eclipse.jgit.ssh.jsch.test/pom.xml b/org.eclipse.jgit.ssh.jsch.test/pom.xml
index 6f199e6..ca1e742 100644
--- a/org.eclipse.jgit.ssh.jsch.test/pom.xml
+++ b/org.eclipse.jgit.ssh.jsch.test/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.ssh.jsch.test</artifactId>
diff --git a/org.eclipse.jgit.ssh.jsch/META-INF/MANIFEST.MF b/org.eclipse.jgit.ssh.jsch/META-INF/MANIFEST.MF
index ed997fb..332d981 100644
--- a/org.eclipse.jgit.ssh.jsch/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ssh.jsch/META-INF/MANIFEST.MF
@@ -3,19 +3,19 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.ssh.jsch
 Bundle-SymbolicName: org.eclipse.jgit.ssh.jsch;singleton:=true
-Fragment-Host: org.eclipse.jgit;bundle-version="[6.0.1,6.1.0)"
+Fragment-Host: org.eclipse.jgit;bundle-version="[6.1.1,6.2.0)"
 Bundle-Vendor: %Bundle-Vendor
 Bundle-Localization: plugin
 Bundle-ActivationPolicy: lazy
-Bundle-Version: 6.0.1.qualifier
+Bundle-Version: 6.1.1.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-11
-Export-Package: org.eclipse.jgit.transport.ssh.jsch;version="6.0.1"
+Export-Package: org.eclipse.jgit.transport.ssh.jsch;version="6.1.1"
 Import-Package: com.jcraft.jsch;version="[0.1.37,0.2.0)",
- org.eclipse.jgit.errors;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.transport.ssh;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.nls;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.util;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.util.io;version="[6.0.1,6.1.0)",
+ org.eclipse.jgit.errors;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.transport.ssh;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.nls;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.util;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.util.io;version="[6.1.1,6.2.0)",
  org.slf4j;version="[1.7.0,2.0.0)"
diff --git a/org.eclipse.jgit.ssh.jsch/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.ssh.jsch/META-INF/SOURCE-MANIFEST.MF
index 3c3f2b1..b56bb7c 100644
--- a/org.eclipse.jgit.ssh.jsch/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.ssh.jsch/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.ssh.jsch - Sources
 Bundle-SymbolicName: org.eclipse.jgit.ssh.jsch.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 6.0.1.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.ssh.jsch;version="6.0.1.qualifier";roots="."
+Bundle-Version: 6.1.1.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.ssh.jsch;version="6.1.1.qualifier";roots="."
diff --git a/org.eclipse.jgit.ssh.jsch/pom.xml b/org.eclipse.jgit.ssh.jsch/pom.xml
index 2a5f9b8..3b6d66c 100644
--- a/org.eclipse.jgit.ssh.jsch/pom.xml
+++ b/org.eclipse.jgit.ssh.jsch/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.ssh.jsch</artifactId>
diff --git a/org.eclipse.jgit.test/BUILD b/org.eclipse.jgit.test/BUILD
index 9e787fe..e63c02c 100644
--- a/org.eclipse.jgit.test/BUILD
+++ b/org.eclipse.jgit.test/BUILD
@@ -13,6 +13,7 @@
 ) + [PKG + c for c in [
     "api/AbstractRemoteCommandTest.java",
     "diff/AbstractDiffTestCase.java",
+    "internal/diffmergetool/ExternalToolTestCase.java",
     "internal/revwalk/ObjectReachabilityTestCase.java",
     "internal/revwalk/ReachabilityCheckerTestCase.java",
     "internal/storage/file/GcTestCase.java",
@@ -50,6 +51,23 @@
     exclude = HELPERS + DATA + EXCLUDED,
 ))
 
+# Non abstract base classes used for tests by other test classes
+BASE = [
+    PKG + "internal/storage/file/FileRepositoryBuilderTest.java",
+    PKG + "internal/storage/file/RefDirectoryTest.java",
+]
+
+java_library(
+    name = "base",
+    testonly = 1,
+    srcs = BASE,
+    deps = [
+        "//lib:junit",
+        "//org.eclipse.jgit:jgit",
+        "//org.eclipse.jgit.junit:junit",
+    ],
+)
+
 java_library(
     name = "helpers",
     testonly = 1,
diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
index 0469113..581395d 100644
--- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.test
 Bundle-SymbolicName: org.eclipse.jgit.test
-Bundle-Version: 6.0.1.qualifier
+Bundle-Version: 6.1.1.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %Bundle-Vendor
 Bundle-RequiredExecutionEnvironment: JavaSE-11
@@ -16,60 +16,61 @@
  org.apache.commons.compress.compressors.gzip;version="[1.15.0,2.0)",
  org.apache.commons.compress.compressors.xz;version="[1.15.0,2.0)",
  org.assertj.core.api;version="[3.14.0,4.0.0)",
- org.eclipse.jgit.annotations;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.api;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.api.errors;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.archive;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.attributes;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.awtui;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.blame;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.diff;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.dircache;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.errors;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.events;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.fnmatch;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.gitrepo;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.hooks;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.ignore;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.ignore.internal;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.fsck;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.revwalk;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.storage.io;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.storage.pack;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.storage.reftable;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.transport.connectivity;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.transport.http;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.transport.parser;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.internal.transport.ssh;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.junit;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.junit.time;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lfs;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lib;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lib.internal;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.logging;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.merge;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.nls;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.notes;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.patch;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.pgm;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.pgm.internal;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.revplot;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.revwalk;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.revwalk.filter;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.storage.file;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.storage.pack;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.submodule;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport.http;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport.resolver;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.treewalk;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.treewalk.filter;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.util;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.util.io;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.util.sha1;version="[6.0.1,6.1.0)",
+ org.eclipse.jgit.annotations;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.api;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.api.errors;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.archive;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.attributes;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.awtui;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.blame;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.diff;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.dircache;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.errors;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.events;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.fnmatch;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.gitrepo;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.hooks;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.ignore;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.ignore.internal;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.diffmergetool;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.fsck;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.revwalk;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.storage.file;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.storage.io;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.storage.reftable;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.transport.connectivity;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.transport.http;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.transport.parser;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.internal.transport.ssh;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.junit;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.junit.time;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lfs;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lib;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lib.internal;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.logging;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.merge;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.nls;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.notes;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.patch;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.pgm;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.pgm.internal;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.revplot;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.revwalk;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.revwalk.filter;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.storage.file;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.storage.pack;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.submodule;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport.http;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport.resolver;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.treewalk;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.treewalk.filter;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.util;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.util.io;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.util.sha1;version="[6.1.1,6.2.0)",
  org.hamcrest;version="[1.1.0,3.0.0)",
  org.hamcrest.collection;version="[1.1.0,3.0.0)",
  org.junit;version="[4.13,5.0.0)",
diff --git a/org.eclipse.jgit.test/pom.xml b/org.eclipse.jgit.test/pom.xml
index 3cab362..6b66919 100644
--- a/org.eclipse.jgit.test/pom.xml
+++ b/org.eclipse.jgit.test/pom.xml
@@ -19,7 +19,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.test</artifactId>
diff --git a/org.eclipse.jgit.test/tests.bzl b/org.eclipse.jgit.test/tests.bzl
index e201bdb..170bf0c 100644
--- a/org.eclipse.jgit.test/tests.bzl
+++ b/org.eclipse.jgit.test/tests.bzl
@@ -52,6 +52,12 @@
                 "//lib:xz",
                 "//org.eclipse.jgit.archive:jgit-archive",
             ]
+        if src.endswith("FileRepositoryBuilderAfterOpenConfigTest.java") or \
+           src.endswith("RefDirectoryAfterOpenConfigTest.java") or \
+           src.endswith("SnapshottingRefDirectoryTest.java"):
+            additional_deps = [
+                ":base",
+            ]
         heap_size = "-Xmx256m"
         if src.endswith("HugeCommitMessageTest.java"):
             heap_size = "-Xmx512m"
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java
index 867310b..584d149 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java
@@ -32,6 +32,7 @@
 import org.eclipse.jgit.junit.RepositoryTestCase;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.IO;
 import org.junit.Test;
 
@@ -402,7 +403,9 @@
 	public void testAddM1() throws Exception {
 		ApplyResult result = init("M1", false, true);
 		assertEquals(1, result.getUpdatedFiles().size());
-		assertTrue(result.getUpdatedFiles().get(0).canExecute());
+		if (FS.DETECTED.supportsExecute()) {
+			assertTrue(FS.DETECTED.canExecute(result.getUpdatedFiles().get(0)));
+		}
 		checkFile(new File(db.getWorkTree(), "M1"),
 				b.getString(0, b.size(), false));
 	}
@@ -411,7 +414,9 @@
 	public void testModifyM2() throws Exception {
 		ApplyResult result = init("M2", true, true);
 		assertEquals(1, result.getUpdatedFiles().size());
-		assertTrue(result.getUpdatedFiles().get(0).canExecute());
+		if (FS.DETECTED.supportsExecute()) {
+			assertTrue(FS.DETECTED.canExecute(result.getUpdatedFiles().get(0)));
+		}
 		checkFile(new File(db.getWorkTree(), "M2"),
 				b.getString(0, b.size(), false));
 	}
@@ -420,7 +425,10 @@
 	public void testModifyM3() throws Exception {
 		ApplyResult result = init("M3", true, true);
 		assertEquals(1, result.getUpdatedFiles().size());
-		assertFalse(result.getUpdatedFiles().get(0).canExecute());
+		if (FS.DETECTED.supportsExecute()) {
+			assertFalse(
+					FS.DETECTED.canExecute(result.getUpdatedFiles().get(0)));
+		}
 		checkFile(new File(db.getWorkTree(), "M3"),
 				b.getString(0, b.size(), false));
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BranchCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BranchCommandTest.java
index e7ed102..87be813 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BranchCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BranchCommandTest.java
@@ -71,6 +71,7 @@
 
 	private Git setUpRepoWithRemote() throws Exception {
 		Repository remoteRepository = createWorkRepository();
+		addRepoToClose(remoteRepository);
 		try (Git remoteGit = new Git(remoteRepository)) {
 			// commit something
 			writeTrashFile("Test.txt", "Hello world");
@@ -85,6 +86,7 @@
 			rup.forceUpdate();
 
 			Repository localRepository = createWorkRepository();
+			addRepoToClose(localRepository);
 			Git localGit = new Git(localRepository);
 			StoredConfig config = localRepository.getConfig();
 			RemoteConfig rc = new RemoteConfig(config, "origin");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java
index e520732..ab0184b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java
@@ -292,6 +292,7 @@
 	@Test
 	public void testCheckoutRemoteTrackingWithUpstream() throws Exception {
 		Repository db2 = createRepositoryWithRemote();
+		addRepoToClose(db2);
 
 		Git.wrap(db2).checkout().setCreateBranch(true).setName("test")
 				.setStartPoint("origin/test")
@@ -311,6 +312,7 @@
 	@Test
 	public void testCheckoutRemoteTrackingWithoutLocalBranch() throws Exception {
 		Repository db2 = createRepositoryWithRemote();
+		addRepoToClose(db2);
 
 		// checkout remote tracking branch in second repository
 		// (no local branches exist yet in second repository)
@@ -868,7 +870,7 @@
 			coCommand.setName(crudCommit.getName()).call();
 			CheckoutResult result = coCommand.getResult();
 			assertEquals(Status.NONDELETED, result.getStatus());
-			assertEquals("[Test.txt, toBeDeleted.txt]",
+			assertEquals("[toBeDeleted.txt]",
 					result.getRemovedList().toString());
 			assertEquals("[toBeCreated.txt, toBeModified.txt]",
 					result.getModifiedList().toString());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java
index f4f0ecd..0d38197 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java
@@ -175,7 +175,8 @@
 
 			assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());
 			assertTrue(new File(db.getDirectory(), Constants.MERGE_MSG).exists());
-			assertEquals("side\n\nConflicts:\n\ta\n", db.readMergeCommitMsg());
+			assertEquals("side\n\n# Conflicts:\n#\ta\n",
+					db.readMergeCommitMsg());
 			assertTrue(new File(db.getDirectory(), Constants.CHERRY_PICK_HEAD)
 					.exists());
 			assertEquals(sideCommit.getId(), db.readCherryPickHead());
@@ -207,7 +208,7 @@
 		String expected = "<<<<<<< master\na(master)\n=======\na(side)\n>>>>>>> 527460a side\n";
 		assertEquals(expected, read("a"));
 		assertTrue(new File(db.getDirectory(), Constants.MERGE_MSG).exists());
-		assertEquals("side\n\nConflicts:\n\ta\n", db.readMergeCommitMsg());
+		assertEquals("side\n\n# Conflicts:\n#\ta\n", db.readMergeCommitMsg());
 		assertFalse(new File(db.getDirectory(), Constants.CHERRY_PICK_HEAD)
 				.exists());
 		assertEquals(RepositoryState.SAFE, db.getRepositoryState());
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 8084505..35de73e 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
@@ -46,6 +46,7 @@
 import org.eclipse.jgit.lib.ReflogEntry;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.lib.CommitConfig.CleanupMode;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.storage.file.FileBasedConfig;
 import org.eclipse.jgit.submodule.SubmoduleWalk;
@@ -515,6 +516,62 @@
 	}
 
 	@Test
+	public void commitMessageVerbatim() throws Exception {
+		try (Git git = new Git(db)) {
+			writeTrashFile("file1", "file1");
+			git.add().addFilepattern("file1").call();
+			RevCommit committed = git.commit().setMessage("#initial commit")
+					.call();
+
+			assertEquals("#initial commit", committed.getFullMessage());
+		}
+	}
+
+	@Test
+	public void commitMessageStrip() throws Exception {
+		try (Git git = new Git(db)) {
+			writeTrashFile("file1", "file1");
+			git.add().addFilepattern("file1").call();
+			RevCommit committed = git.commit().setMessage(
+					"#Comment\ninitial commit\t\n\n commit body \n \t#another comment")
+					.setCleanupMode(CleanupMode.STRIP).call();
+
+			assertEquals("initial commit\n\n commit body",
+					committed.getFullMessage());
+		}
+	}
+
+	@Test
+	public void commitMessageDefault() throws Exception {
+		try (Git git = new Git(db)) {
+			writeTrashFile("file1", "file1");
+			git.add().addFilepattern("file1").call();
+			RevCommit committed = git.commit().setMessage(
+					"#Comment\ninitial commit\t\n\n commit body \n\n\n \t#another comment  ")
+					.setCleanupMode(CleanupMode.DEFAULT).call();
+
+			assertEquals("initial commit\n\n commit body",
+					committed.getFullMessage());
+		}
+	}
+
+	@Test
+	public void commitMessageDefaultWhitespace() throws Exception {
+		try (Git git = new Git(db)) {
+			writeTrashFile("file1", "file1");
+			git.add().addFilepattern("file1").call();
+			RevCommit committed = git.commit().setMessage(
+					"#Comment\ninitial commit\t\n\n commit body \n\n\n \t#another comment  ")
+					.setCleanupMode(CleanupMode.DEFAULT).setDefaultClean(false)
+					.call();
+
+			assertEquals(
+					"#Comment\ninitial commit\n\n commit body\n\n \t#another comment",
+					committed.getFullMessage());
+		}
+	}
+
+	@Test
 	public void commitEmptyCommits() throws Exception {
 		try (Git git = new Git(db)) {
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java
index b460e3f..ab87fa9 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java
@@ -10,9 +10,10 @@
 package org.eclipse.jgit.api;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import java.io.BufferedWriter;
@@ -100,6 +101,12 @@
 			assertEquals("alice-t1-2-g3e563c5", describe(c4, "alice*"));
 			assertEquals("bob-t2-1-g3e563c5", describe(c4, "bob*"));
 			assertEquals("bob-t2-1-g3e563c5", describe(c4, "a*", "b*", "c*"));
+
+			assertEquals("bob-t2", describe(c4, false, true, 0));
+			assertEquals("bob-t2-1-g3e56", describe(c4, false, true, 1));
+			assertEquals("bob-t2-1-g3e56", describe(c4, false, true, -10));
+			assertEquals("bob-t2-1-g3e563c55927905f21e3bc7c00a3d83a31bf4ed3a",
+					describe(c4, false, true, 50));
 		} else {
 			assertEquals(null, describe(c2));
 			assertEquals(null, describe(c3));
@@ -108,6 +115,18 @@
 			assertEquals("3747db3", describe(c2, false, true));
 			assertEquals("44579eb", describe(c3, false, true));
 			assertEquals("3e563c5", describe(c4, false, true));
+
+			assertEquals("3747db3267", describe(c2, false, true, 10));
+			assertEquals("44579ebe7f", describe(c3, false, true, 10));
+			assertEquals("3e563c5592", describe(c4, false, true, 10));
+
+			assertEquals("3e56", describe(c4, false, true, -10));
+			assertEquals("3e56", describe(c4, false, true, 0));
+			assertEquals("3e56", describe(c4, false, true, 2));
+			assertEquals("3e563c55927905f21e3bc7c00a3d83a31bf4ed3a",
+					describe(c4, false, true, 40));
+			assertEquals("3e563c55927905f21e3bc7c00a3d83a31bf4ed3a",
+					describe(c4, false, true, 42));
 		}
 
 		// test default target
@@ -474,10 +493,15 @@
 		}
 	}
 
+	private String describe(ObjectId c1, boolean longDesc, boolean always,
+			int abbrev) throws GitAPIException, IOException {
+		return git.describe().setTarget(c1).setTags(describeUseAllTags)
+				.setLong(longDesc).setAlways(always).setAbbrev(abbrev).call();
+	}
+
 	private String describe(ObjectId c1, boolean longDesc, boolean always)
 			throws GitAPIException, IOException {
-		return git.describe().setTarget(c1).setTags(describeUseAllTags)
-				.setLong(longDesc).setAlways(always).call();
+		return describe(c1, longDesc, always, OBJECT_ID_ABBREV_STRING_LENGTH);
 	}
 
 	private String describe(ObjectId c1) throws GitAPIException, IOException {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolRepositoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolRepositoryTest.java
index d0dfd1a..b937b1f 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolRepositoryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolRepositoryTest.java
@@ -1,40 +1,11 @@
 /*
- * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com>
+ * Copyright (C) 2015, 2022 Ivan Motsch <ivan.motsch@bsiag.com> and others
  *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
+ * 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.
  *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- *   copyright notice, this list of conditions and the following
- *   disclaimer in the documentation and/or other materials provided
- *   with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- *   names of its contributors may be used to endorse or promote
- *   products derived from this software without specific prior
- *   written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * SPDX-License-Identifier: BSD-3-Clause
  */
 package org.eclipse.jgit.api;
 
@@ -173,15 +144,22 @@
 
 	private DirCache dirCache;
 
+	private boolean isDefaultCrLf() {
+		String eol = mockSystemReader.getProperty("line.separator");
+		return "\r\n".equals(eol);
+	}
+
 	@Test
 	public void testDefaultSetup() throws Exception {
 		// for EOL to work, the text attribute must be set
 		setupGitAndDoHardReset(null, null, null, null, "* text=auto");
 		collectRepositoryState();
 		assertEquals("text=auto", entryCRLF.attrs);
-		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
-		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
-		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
+		// eol=native is the default!
+		String expected = isDefaultCrLf() ? CONTENT_CRLF : CONTENT_LF;
+		checkEntryContent(entryCRLF, expected, CONTENT_LF);
+		checkEntryContent(entryLF, expected, CONTENT_LF);
+		checkEntryContent(entryMixed, expected, CONTENT_LF);
 	}
 
 	public void checkEntryContent(ActualEntry entry, String fileContent,
@@ -199,9 +177,11 @@
 		setupGitAndDoHardReset(AutoCRLF.FALSE, null, null, null, "* text=auto");
 		collectRepositoryState();
 		assertEquals("text=auto", entryCRLF.attrs);
-		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
-		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
-		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
+		// eol=native is the default!
+		String expected = isDefaultCrLf() ? CONTENT_CRLF : CONTENT_LF;
+		checkEntryContent(entryCRLF, expected, CONTENT_LF);
+		checkEntryContent(entryLF, expected, CONTENT_LF);
+		checkEntryContent(entryMixed, expected, CONTENT_LF);
 	}
 
 	@Test
@@ -250,34 +230,24 @@
 
 	@Test
 	public void test_ConfigEOL_native_windows() throws Exception {
-		String origLineSeparator = System.getProperty("line.separator", "\n");
-		System.setProperty("line.separator", "\r\n");
-		try {
-			// for EOL to work, the text attribute must be set
-			setupGitAndDoHardReset(null, EOL.NATIVE, "*.txt text", null, null);
-			collectRepositoryState();
-			assertEquals("text", entryCRLF.attrs);
-			checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
-			checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
-		} finally {
-			System.setProperty("line.separator", origLineSeparator);
-		}
+		mockSystemReader.setWindows();
+		// for EOL to work, the text attribute must be set
+		setupGitAndDoHardReset(null, EOL.NATIVE, "*.txt text", null, null);
+		collectRepositoryState();
+		assertEquals("text", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_CRLF, CONTENT_LF);
+		checkEntryContent(entryLF, CONTENT_CRLF, CONTENT_LF);
 	}
 
 	@Test
 	public void test_ConfigEOL_native_xnix() throws Exception {
-		String origLineSeparator = System.getProperty("line.separator", "\n");
-		System.setProperty("line.separator", "\n");
-		try {
-			// for EOL to work, the text attribute must be set
-			setupGitAndDoHardReset(null, EOL.NATIVE, "*.txt text", null, null);
-			collectRepositoryState();
-			assertEquals("text", entryCRLF.attrs);
-			checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
-			checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
-		} finally {
-			System.setProperty("line.separator", origLineSeparator);
-		}
+		mockSystemReader.setUnix();
+		// for EOL to work, the text attribute must be set
+		setupGitAndDoHardReset(null, EOL.NATIVE, "*.txt text", null, null);
+		collectRepositoryState();
+		assertEquals("text", entryCRLF.attrs);
+		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
+		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
 	}
 
 	@Test
@@ -297,9 +267,10 @@
 		setupGitAndDoHardReset(AutoCRLF.FALSE, EOL.NATIVE, "*.txt text", null, null);
 		collectRepositoryState();
 		assertEquals("text", entryCRLF.attrs);
-		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
-		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
-		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
+		String expected = isDefaultCrLf() ? CONTENT_CRLF : CONTENT_LF;
+		checkEntryContent(entryCRLF, expected, CONTENT_LF);
+		checkEntryContent(entryLF, expected, CONTENT_LF);
+		checkEntryContent(entryMixed, expected, CONTENT_LF);
 	}
 
 	@Test
@@ -524,9 +495,12 @@
 		// info overrides all
 		collectRepositoryState();
 		assertEquals("text", entryCRLF.attrs);
-		checkEntryContent(entryCRLF, CONTENT_LF, CONTENT_LF);
-		checkEntryContent(entryLF, CONTENT_LF, CONTENT_LF);
-		checkEntryContent(entryMixed, CONTENT_LF, CONTENT_LF);
+		// !eol means unspecified, so use the default of core.eol, which is
+		// native.
+		String expected = isDefaultCrLf() ? CONTENT_CRLF : CONTENT_LF;
+		checkEntryContent(entryCRLF, expected, CONTENT_LF);
+		checkEntryContent(entryLF, expected, CONTENT_LF);
+		checkEntryContent(entryMixed, expected, CONTENT_LF);
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchAndPullCommandsRecurseSubmodulesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchAndPullCommandsRecurseSubmodulesTest.java
index 36ba7ce..9ea64ef 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchAndPullCommandsRecurseSubmodulesTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchAndPullCommandsRecurseSubmodulesTest.java
@@ -259,7 +259,8 @@
 		// the commit that was created
 		try (SubmoduleWalk w = SubmoduleWalk.forIndex(git.getRepository())) {
 			assertTrue(w.next());
-			try (Git g = new Git(w.getRepository())) {
+			try (Repository repository = w.getRepository();
+					Git g = new Git(repository)) {
 				g.fetch().setRemote(REMOTE).setRefSpecs(REFSPEC).call();
 				g.reset().setMode(ResetType.HARD).setRef(commit1.name()).call();
 			}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java
index bc4e940..64475f5 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java
@@ -554,7 +554,7 @@
 			git.merge().include(sideBranch)
 					.setStrategy(MergeStrategy.RESOLVE).call();
 
-			assertEquals("Merge branch 'side'\n\nConflicts:\n\ta\n",
+			assertEquals("Merge branch 'side'\n\n# Conflicts:\n#\ta\n",
 					db.readMergeCommitMsg());
 		}
 
@@ -1787,7 +1787,7 @@
 							+ dateFormatter.formatDate(third
 									.getAuthorIdent()) + "\n\n\tthird commit\n",
 					db.readSquashCommitMsg());
-			assertEquals("\nConflicts:\n\tfile2\n", db.readMergeCommitMsg());
+			assertEquals("\n# Conflicts:\n#\tfile2\n", db.readMergeCommitMsg());
 
 			Status stat = git.status().call();
 			assertEquals(Sets.of("file2"), stat.getConflicting());
@@ -1881,6 +1881,7 @@
 	@Test
 	public void testRecursiveMergeWithConflict() throws Exception {
 		try (TestRepository<Repository> db_t = new TestRepository<>(db)) {
+			db.incrementOpen();
 			BranchBuilder master = db_t.branch("master");
 			RevCommit m0 = master.commit()
 					.add("f", "1\n2\n3\n4\n5\n6\n7\n8\n9\n").message("m0")
@@ -2012,7 +2013,7 @@
 			git.merge().include(sideBranch).setStrategy(MergeStrategy.RESOLVE)
 					.setMessage("user message").call();
 
-			assertEquals("user message\n\nConflicts:\n\ta\n",
+			assertEquals("user message\n\n# Conflicts:\n#\ta\n",
 					db.readMergeCommitMsg());
 		}
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java
index 9af77aa..6a84f0a 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java
@@ -567,6 +567,7 @@
 	public void setUp() throws Exception {
 		super.setUp();
 		dbTarget = createWorkRepository();
+		addRepoToClose(dbTarget);
 		source = new Git(db);
 		target = new Git(dbTarget);
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandWithRebaseTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandWithRebaseTest.java
index cce04f4..e2d7923 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandWithRebaseTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandWithRebaseTest.java
@@ -323,6 +323,7 @@
 		dbTarget = createWorkRepository();
 		source = new Git(db);
 		target = new Git(dbTarget);
+		addRepoToClose(dbTarget);
 
 		// put some file in the source repo
 		sourceFile = new File(db.getWorkTree(), "SomeFile.txt");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java
index a786065..6f7aa63 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java
@@ -10,7 +10,9 @@
 package org.eclipse.jgit.api;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -19,10 +21,13 @@
 import java.net.URISyntaxException;
 import java.util.Properties;
 
+import org.eclipse.jgit.api.errors.DetachedHeadException;
 import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.InvalidRefNameException;
 import org.eclipse.jgit.api.errors.JGitInternalException;
 import org.eclipse.jgit.api.errors.TransportException;
 import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.NoRemoteRepositoryException;
 import org.eclipse.jgit.hooks.PrePushHook;
 import org.eclipse.jgit.junit.JGitTestUtil;
 import org.eclipse.jgit.junit.RepositoryTestCase;
@@ -32,6 +37,7 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.StoredConfig;
 import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.transport.PushConfig.PushDefault;
 import org.eclipse.jgit.transport.PushResult;
 import org.eclipse.jgit.transport.RefLeaseSpec;
 import org.eclipse.jgit.transport.RefSpec;
@@ -50,6 +56,7 @@
 
 		// create other repository
 		Repository db2 = createWorkRepository();
+		addRepoToClose(db2);
 		final StoredConfig config2 = db2.getConfig();
 
 		// this tests that this config can be parsed properly
@@ -289,6 +296,770 @@
 	}
 
 	/**
+	 * Check that pushing from a detached HEAD without refspec throws a
+	 * DetachedHeadException.
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void testPushDefaultDetachedHead() throws Exception {
+		try (Git git = new Git(db);
+				Git git2 = new Git(createBareRepository())) {
+			final StoredConfig config = git.getRepository().getConfig();
+			RemoteConfig remoteConfig = new RemoteConfig(config, "test");
+			URIish uri = new URIish(
+					git2.getRepository().getDirectory().toURI().toURL());
+			remoteConfig.addURI(uri);
+			remoteConfig.addFetchRefSpec(
+					new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
+			remoteConfig.update(config);
+			config.save();
+
+			writeTrashFile("f", "content of f");
+			git.add().addFilepattern("f").call();
+			RevCommit commit = git.commit().setMessage("adding f").call();
+
+			git.checkout().setName("not-pushed").setCreateBranch(true).call();
+			git.checkout().setName("branchtopush").setCreateBranch(true).call();
+			git.checkout().setName(commit.getName()).call();
+			String head = git.getRepository().getFullBranch();
+			assertTrue(ObjectId.isId(head));
+			assertEquals(commit.getName(), head);
+			assertThrows(DetachedHeadException.class,
+					() -> git.push().setRemote("test").call());
+		}
+	}
+
+	/**
+	 * Check that push.default=nothing without refspec throws an
+	 * InvalidRefNameException.
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void testPushDefaultNothing() throws Exception {
+		try (Git git = new Git(db);
+				Git git2 = new Git(createBareRepository())) {
+			final StoredConfig config = git.getRepository().getConfig();
+			RemoteConfig remoteConfig = new RemoteConfig(config, "test");
+			URIish uri = new URIish(
+					git2.getRepository().getDirectory().toURI().toURL());
+			remoteConfig.addURI(uri);
+			remoteConfig.addFetchRefSpec(
+					new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
+			remoteConfig.update(config);
+			config.save();
+
+			writeTrashFile("f", "content of f");
+			git.add().addFilepattern("f").call();
+			git.commit().setMessage("adding f").call();
+
+			git.checkout().setName("not-pushed").setCreateBranch(true).call();
+			git.checkout().setName("branchtopush").setCreateBranch(true).call();
+
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/branchtopush"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/not-pushed"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/master"));
+			assertThrows(InvalidRefNameException.class,
+					() -> git.push().setRemote("test")
+							.setPushDefault(PushDefault.NOTHING).call());
+		}
+	}
+
+	/**
+	 * Check that push.default=matching without refspec pushes all matching
+	 * branches.
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void testPushDefaultMatching() throws Exception {
+		try (Git git = new Git(db);
+				Git git2 = new Git(createBareRepository())) {
+			final StoredConfig config = git.getRepository().getConfig();
+			RemoteConfig remoteConfig = new RemoteConfig(config, "test");
+			URIish uri = new URIish(
+					git2.getRepository().getDirectory().toURI().toURL());
+			remoteConfig.addURI(uri);
+			remoteConfig.addFetchRefSpec(
+					new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
+			remoteConfig.update(config);
+			config.save();
+
+			writeTrashFile("f", "content of f");
+			git.add().addFilepattern("f").call();
+			RevCommit commit = git.commit().setMessage("adding f").call();
+
+			git.checkout().setName("not-pushed").setCreateBranch(true).call();
+			git.checkout().setName("branchtopush").setCreateBranch(true).call();
+
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/branchtopush"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/not-pushed"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/master"));
+			// push master and branchtopush
+			git.push().setRemote("test").setRefSpecs(
+					new RefSpec("refs/heads/master:refs/heads/master"),
+					new RefSpec(
+							"refs/heads/branchtopush:refs/heads/branchtopush"))
+					.call();
+			assertEquals(commit.getId(),
+					git2.getRepository().resolve("refs/heads/master"));
+			assertEquals(commit.getId(),
+					git2.getRepository().resolve("refs/heads/branchtopush"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/not-pushed"));
+			// Create two different commits on these two branches
+			writeTrashFile("b", "on branchtopush");
+			git.add().addFilepattern("b").call();
+			RevCommit bCommit = git.commit().setMessage("on branchtopush")
+					.call();
+			git.checkout().setName("master").call();
+			writeTrashFile("m", "on master");
+			git.add().addFilepattern("m").call();
+			RevCommit mCommit = git.commit().setMessage("on master").call();
+			// Now push with mode "matching": should push both branches.
+			Iterable<PushResult> result = git.push().setRemote("test")
+					.setPushDefault(PushDefault.MATCHING)
+					.call();
+			int n = 0;
+			for (PushResult r : result) {
+				n++;
+				assertEquals(1, n);
+				assertEquals(2, r.getRemoteUpdates().size());
+				for (RemoteRefUpdate update : r.getRemoteUpdates()) {
+					assertFalse(update.isMatching());
+					assertTrue(update.getSrcRef()
+							.equals("refs/heads/branchtopush")
+							|| update.getSrcRef().equals("refs/heads/master"));
+					assertEquals(RemoteRefUpdate.Status.OK, update.getStatus());
+				}
+			}
+			assertEquals(bCommit.getId(),
+					git2.getRepository().resolve("refs/heads/branchtopush"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/not-pushed"));
+			assertEquals(mCommit.getId(),
+					git2.getRepository().resolve("refs/heads/master"));
+			assertEquals(bCommit.getId(), git.getRepository()
+					.resolve("refs/remotes/origin/branchtopush"));
+			assertEquals(null, git.getRepository()
+					.resolve("refs/remotes/origin/not-pushed"));
+			assertEquals(mCommit.getId(),
+					git.getRepository().resolve("refs/remotes/origin/master"));
+		}
+	}
+
+	/**
+	 * Check that push.default=upstream without refspec pushes only the current
+	 * branch to the configured upstream.
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void testPushDefaultUpstream() throws Exception {
+		try (Git git = new Git(db);
+				Git git2 = new Git(createBareRepository())) {
+			StoredConfig config = git.getRepository().getConfig();
+			RemoteConfig remoteConfig = new RemoteConfig(config, "test");
+			URIish uri = new URIish(
+					git2.getRepository().getDirectory().toURI().toURL());
+			remoteConfig.addURI(uri);
+			remoteConfig.addFetchRefSpec(
+					new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
+			remoteConfig.update(config);
+			config.save();
+
+			writeTrashFile("f", "content of f");
+			git.add().addFilepattern("f").call();
+			RevCommit commit = git.commit().setMessage("adding f").call();
+
+			git.checkout().setName("not-pushed").setCreateBranch(true).call();
+			git.checkout().setName("branchtopush").setCreateBranch(true).call();
+
+			config = git.getRepository().getConfig();
+			config.setString("branch", "branchtopush", "remote", "test");
+			config.setString("branch", "branchtopush", "merge",
+					"refs/heads/upstreambranch");
+			config.save();
+
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/branchtopush"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/upstreambranch"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/not-pushed"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/master"));
+			git.push().setRemote("test").setPushDefault(PushDefault.UPSTREAM)
+					.call();
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/branchtopush"));
+			assertEquals(commit.getId(),
+					git2.getRepository().resolve("refs/heads/upstreambranch"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/not-pushed"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/master"));
+			assertEquals(commit.getId(), git.getRepository()
+					.resolve("refs/remotes/origin/upstreambranch"));
+			assertEquals(null, git.getRepository()
+					.resolve("refs/remotes/origin/branchtopush"));
+		}
+	}
+
+	/**
+	 * Check that push.default=upstream without refspec throws an
+	 * InvalidRefNameException if the current branch has no upstream.
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void testPushDefaultUpstreamNoTracking() throws Exception {
+		try (Git git = new Git(db);
+				Git git2 = new Git(createBareRepository())) {
+			StoredConfig config = git.getRepository().getConfig();
+			RemoteConfig remoteConfig = new RemoteConfig(config, "test");
+			URIish uri = new URIish(
+					git2.getRepository().getDirectory().toURI().toURL());
+			remoteConfig.addURI(uri);
+			remoteConfig.addFetchRefSpec(
+					new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
+			remoteConfig.update(config);
+			config.save();
+
+			writeTrashFile("f", "content of f");
+			git.add().addFilepattern("f").call();
+			git.commit().setMessage("adding f").call();
+
+			git.checkout().setName("not-pushed").setCreateBranch(true).call();
+			git.checkout().setName("branchtopush").setCreateBranch(true).call();
+
+			config = git.getRepository().getConfig();
+			config.setString("branch", "branchtopush", "remote", "test");
+			config.save();
+
+			assertThrows(InvalidRefNameException.class,
+					() -> git.push().setRemote("test")
+							.setPushDefault(PushDefault.UPSTREAM).call());
+		}
+	}
+
+	/**
+	 * Check that push.default=upstream without refspec throws an
+	 * InvalidRefNameException if the push remote is not the same as the fetch
+	 * remote.
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void testPushDefaultUpstreamTriangular() throws Exception {
+		try (Git git = new Git(db);
+				Git git2 = new Git(createBareRepository())) {
+			StoredConfig config = git.getRepository().getConfig();
+			RemoteConfig remoteConfig = new RemoteConfig(config, "test");
+			URIish uri = new URIish(
+					git2.getRepository().getDirectory().toURI().toURL());
+			remoteConfig.addURI(uri);
+			remoteConfig.addFetchRefSpec(
+					new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
+			remoteConfig.update(config);
+			config.save();
+
+			writeTrashFile("f", "content of f");
+			git.add().addFilepattern("f").call();
+			git.commit().setMessage("adding f").call();
+
+			git.checkout().setName("not-pushed").setCreateBranch(true).call();
+			git.checkout().setName("branchtopush").setCreateBranch(true).call();
+
+			config = git.getRepository().getConfig();
+			// Don't configure a remote; it'll default to "origin".
+			config.setString("branch", "branchtopush", "merge",
+					"upstreambranch");
+			config.save();
+
+			assertThrows(InvalidRefNameException.class,
+					() -> git.push().setRemote("test")
+							.setPushDefault(PushDefault.UPSTREAM).call());
+		}
+	}
+
+	/**
+	 * Check that push.default=simple without refspec pushes only the current
+	 * branch to the configured upstream name.
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void testPushDefaultSimple() throws Exception {
+		try (Git git = new Git(db);
+				Git git2 = new Git(createBareRepository())) {
+			StoredConfig config = git.getRepository().getConfig();
+			RemoteConfig remoteConfig = new RemoteConfig(config, "test");
+			URIish uri = new URIish(
+					git2.getRepository().getDirectory().toURI().toURL());
+			remoteConfig.addURI(uri);
+			remoteConfig.addFetchRefSpec(
+					new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
+			remoteConfig.update(config);
+			config.save();
+
+			writeTrashFile("f", "content of f");
+			git.add().addFilepattern("f").call();
+			RevCommit commit = git.commit().setMessage("adding f").call();
+
+			git.checkout().setName("not-pushed").setCreateBranch(true).call();
+			git.checkout().setName("branchtopush").setCreateBranch(true).call();
+
+			config = git.getRepository().getConfig();
+			config.setString("branch", "branchtopush", "remote", "test");
+			config.setString("branch", "branchtopush", "merge",
+					"refs/heads/branchtopush");
+			config.save();
+
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/branchtopush"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/not-pushed"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/master"));
+			git.push().setRemote("test").setPushDefault(PushDefault.SIMPLE)
+					.call();
+			assertEquals(commit.getId(),
+					git2.getRepository().resolve("refs/heads/branchtopush"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/not-pushed"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/master"));
+			assertEquals(commit.getId(), git.getRepository()
+					.resolve("refs/remotes/origin/branchtopush"));
+		}
+	}
+
+	/**
+	 * Check that push.default=simple without refspec pushes only the current
+	 * branch to a branch with the same name in a triangular workflow.
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void testPushDefaultSimpleTriangular() throws Exception {
+		try (Git git = new Git(db);
+				Git git2 = new Git(createBareRepository())) {
+			StoredConfig config = git.getRepository().getConfig();
+			RemoteConfig remoteConfig = new RemoteConfig(config, "test");
+			URIish uri = new URIish(
+					git2.getRepository().getDirectory().toURI().toURL());
+			remoteConfig.addURI(uri);
+			remoteConfig.addFetchRefSpec(
+					new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
+			remoteConfig.update(config);
+			config.save();
+
+			writeTrashFile("f", "content of f");
+			git.add().addFilepattern("f").call();
+			RevCommit commit = git.commit().setMessage("adding f").call();
+
+			git.checkout().setName("not-pushed").setCreateBranch(true).call();
+			git.checkout().setName("branchtopush").setCreateBranch(true).call();
+
+			config = git.getRepository().getConfig();
+			// Don't set remote, it'll default to "origin". Configure a
+			// different branch name; should be ignored.
+			config.setString("branch", "branchtopush", "merge",
+					"refs/heads/upstreambranch");
+			config.save();
+
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/branchtopush"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/upstreambranch"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/not-pushed"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/master"));
+			git.push().setRemote("test").setPushDefault(PushDefault.SIMPLE)
+					.call();
+			assertEquals(commit.getId(),
+					git2.getRepository().resolve("refs/heads/branchtopush"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/upstreambranch"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/not-pushed"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/master"));
+			assertEquals(commit.getId(), git.getRepository()
+					.resolve("refs/remotes/origin/branchtopush"));
+		}
+	}
+
+	/**
+	 * Check that push.default=simple without refspec throws an
+	 * InvalidRefNameException if the current branch has no upstream.
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void testPushDefaultSimpleNoTracking() throws Exception {
+		try (Git git = new Git(db);
+				Git git2 = new Git(createBareRepository())) {
+			StoredConfig config = git.getRepository().getConfig();
+			RemoteConfig remoteConfig = new RemoteConfig(config, "test");
+			URIish uri = new URIish(
+					git2.getRepository().getDirectory().toURI().toURL());
+			remoteConfig.addURI(uri);
+			remoteConfig.addFetchRefSpec(
+					new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
+			remoteConfig.update(config);
+			config.save();
+
+			writeTrashFile("f", "content of f");
+			git.add().addFilepattern("f").call();
+			git.commit().setMessage("adding f").call();
+
+			git.checkout().setName("not-pushed").setCreateBranch(true).call();
+			git.checkout().setName("branchtopush").setCreateBranch(true).call();
+
+			config = git.getRepository().getConfig();
+			config.setString("branch", "branchtopush", "remote", "test");
+			config.save();
+
+			assertThrows(InvalidRefNameException.class,
+					() -> git.push().setRemote("test")
+							.setPushDefault(PushDefault.SIMPLE).call());
+		}
+	}
+
+	/**
+	 * Check that push.default=simple without refspec throws an
+	 * InvalidRefNameException if the current branch has an upstream with a
+	 * different name.
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void testPushDefaultSimpleDifferentTracking() throws Exception {
+		try (Git git = new Git(db);
+				Git git2 = new Git(createBareRepository())) {
+			StoredConfig config = git.getRepository().getConfig();
+			RemoteConfig remoteConfig = new RemoteConfig(config, "test");
+			URIish uri = new URIish(
+					git2.getRepository().getDirectory().toURI().toURL());
+			remoteConfig.addURI(uri);
+			remoteConfig.addFetchRefSpec(
+					new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
+			remoteConfig.update(config);
+			config.save();
+
+			writeTrashFile("f", "content of f");
+			git.add().addFilepattern("f").call();
+			git.commit().setMessage("adding f").call();
+
+			git.checkout().setName("not-pushed").setCreateBranch(true).call();
+			git.checkout().setName("branchtopush").setCreateBranch(true).call();
+
+			config = git.getRepository().getConfig();
+			config.setString("branch", "branchtopush", "remote", "test");
+			config.setString("branch", "branchtopush", "merge",
+					"refs/heads/upstreambranch");
+			config.save();
+
+			assertThrows(InvalidRefNameException.class,
+					() -> git.push().setRemote("test")
+							.setPushDefault(PushDefault.SIMPLE).call());
+		}
+	}
+
+	/**
+	 * Check that if no PushDefault is set, the value is read from the git
+	 * config.
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void testPushDefaultFromConfig() throws Exception {
+		try (Git git = new Git(db);
+				Git git2 = new Git(createBareRepository())) {
+			StoredConfig config = git.getRepository().getConfig();
+			RemoteConfig remoteConfig = new RemoteConfig(config, "test");
+			URIish uri = new URIish(
+					git2.getRepository().getDirectory().toURI().toURL());
+			remoteConfig.addURI(uri);
+			remoteConfig.addFetchRefSpec(
+					new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
+			remoteConfig.update(config);
+			config.setString("push", null, "default", "upstream");
+			config.save();
+
+			writeTrashFile("f", "content of f");
+			git.add().addFilepattern("f").call();
+			RevCommit commit = git.commit().setMessage("adding f").call();
+
+			git.checkout().setName("not-pushed").setCreateBranch(true).call();
+			git.checkout().setName("branchtopush").setCreateBranch(true).call();
+
+			config = git.getRepository().getConfig();
+			config.setString("branch", "branchtopush", "remote", "test");
+			config.setString("branch", "branchtopush", "merge",
+					"refs/heads/upstreambranch");
+			config.save();
+
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/branchtopush"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/upstreambranch"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/not-pushed"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/master"));
+			PushCommand cmd = git.push();
+			cmd.setRemote("test").setPushDefault(null).call();
+			assertEquals(PushDefault.UPSTREAM, cmd.getPushDefault());
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/branchtopush"));
+			assertEquals(commit.getId(),
+					git2.getRepository().resolve("refs/heads/upstreambranch"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/not-pushed"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/master"));
+			assertEquals(commit.getId(), git.getRepository()
+					.resolve("refs/remotes/origin/upstreambranch"));
+			assertEquals(null, git.getRepository()
+					.resolve("refs/remotes/origin/branchtopush"));
+		}
+	}
+
+	/**
+	 * Check that if no PushDefault is set and none is set in the git config, it
+	 * defaults to "simple".
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void testPushDefaultFromConfigDefault() throws Exception {
+		try (Git git = new Git(db);
+				Git git2 = new Git(createBareRepository())) {
+			StoredConfig config = git.getRepository().getConfig();
+			RemoteConfig remoteConfig = new RemoteConfig(config, "test");
+			URIish uri = new URIish(
+					git2.getRepository().getDirectory().toURI().toURL());
+			remoteConfig.addURI(uri);
+			remoteConfig.addFetchRefSpec(
+					new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
+			remoteConfig.update(config);
+			config.save();
+
+			writeTrashFile("f", "content of f");
+			git.add().addFilepattern("f").call();
+			RevCommit commit = git.commit().setMessage("adding f").call();
+
+			git.checkout().setName("not-pushed").setCreateBranch(true).call();
+			git.checkout().setName("branchtopush").setCreateBranch(true).call();
+
+			config = git.getRepository().getConfig();
+			config.setString("branch", "branchtopush", "remote", "test");
+			config.setString("branch", "branchtopush", "merge",
+					"refs/heads/branchtopush");
+			config.save();
+
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/branchtopush"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/not-pushed"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/master"));
+			PushCommand cmd = git.push();
+			cmd.setRemote("test").setPushDefault(null).call();
+			assertEquals(PushDefault.SIMPLE, cmd.getPushDefault());
+			assertEquals(commit.getId(),
+					git2.getRepository().resolve("refs/heads/branchtopush"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/not-pushed"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/master"));
+			assertEquals(commit.getId(), git.getRepository()
+					.resolve("refs/remotes/origin/branchtopush"));
+		}
+	}
+
+	/**
+	 * Check that branch.<name>.pushRemote overrides anything else.
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void testBranchPushRemote() throws Exception {
+		try (Git git = new Git(db);
+				Git git2 = new Git(createBareRepository())) {
+			StoredConfig config = git.getRepository().getConfig();
+			RemoteConfig remoteConfig = new RemoteConfig(config, "test");
+			URIish uri = new URIish(
+					git2.getRepository().getDirectory().toURI().toURL());
+			remoteConfig.addURI(uri);
+			remoteConfig.addFetchRefSpec(
+					new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
+			remoteConfig.update(config);
+			config.setString("remote", null, "pushDefault", "test");
+			config.save();
+
+			writeTrashFile("f", "content of f");
+			git.add().addFilepattern("f").call();
+			git.commit().setMessage("adding f").call();
+
+			git.checkout().setName("not-pushed").setCreateBranch(true).call();
+			git.checkout().setName("branchtopush").setCreateBranch(true).call();
+
+			config = git.getRepository().getConfig();
+			config.setString("branch", "branchtopush", "remote", "test");
+			config.setString("branch", "branchtopush", "pushremote", "origin");
+			config.setString("branch", "branchtopush", "merge",
+					"refs/heads/branchtopush");
+			config.save();
+
+			assertThrows(InvalidRefNameException.class, () -> git.push()
+					.setPushDefault(PushDefault.UPSTREAM).call());
+		}
+	}
+
+	/**
+	 * Check that remote.pushDefault overrides branch.<name>.remote
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void testRemotePushDefault() throws Exception {
+		try (Git git = new Git(db);
+				Git git2 = new Git(createBareRepository())) {
+			StoredConfig config = git.getRepository().getConfig();
+			RemoteConfig remoteConfig = new RemoteConfig(config, "test");
+			URIish uri = new URIish(
+					git2.getRepository().getDirectory().toURI().toURL());
+			remoteConfig.addURI(uri);
+			remoteConfig.addFetchRefSpec(
+					new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
+			remoteConfig.update(config);
+			config.setString("remote", null, "pushDefault", "origin");
+			config.save();
+
+			writeTrashFile("f", "content of f");
+			git.add().addFilepattern("f").call();
+			git.commit().setMessage("adding f").call();
+
+			git.checkout().setName("not-pushed").setCreateBranch(true).call();
+			git.checkout().setName("branchtopush").setCreateBranch(true).call();
+
+			config = git.getRepository().getConfig();
+			config.setString("branch", "branchtopush", "remote", "test");
+			config.setString("branch", "branchtopush", "merge",
+					"refs/heads/branchtopush");
+			config.save();
+
+			assertThrows(InvalidRefNameException.class, () -> git.push()
+					.setPushDefault(PushDefault.UPSTREAM).call());
+		}
+	}
+
+	/**
+	 * Check that ultimately we fall back to "origin".
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void testDefaultRemote() throws Exception {
+		try (Git git = new Git(db);
+				Git git2 = new Git(createBareRepository())) {
+			StoredConfig config = git.getRepository().getConfig();
+			RemoteConfig remoteConfig = new RemoteConfig(config, "test");
+			URIish uri = new URIish(
+					git2.getRepository().getDirectory().toURI().toURL());
+			remoteConfig.addURI(uri);
+			remoteConfig.addFetchRefSpec(
+					new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
+			remoteConfig.update(config);
+			config.save();
+
+			writeTrashFile("f", "content of f");
+			git.add().addFilepattern("f").call();
+			git.commit().setMessage("adding f").call();
+
+			git.checkout().setName("not-pushed").setCreateBranch(true).call();
+			git.checkout().setName("branchtopush").setCreateBranch(true).call();
+
+			config = git.getRepository().getConfig();
+			config.setString("branch", "branchtopush", "merge",
+					"refs/heads/branchtopush");
+			config.save();
+
+			PushCommand cmd = git.push().setPushDefault(PushDefault.UPSTREAM);
+			TransportException e = assertThrows(TransportException.class,
+					() -> cmd.call());
+			assertEquals(NoRemoteRepositoryException.class,
+					e.getCause().getClass());
+			assertEquals("origin", cmd.getRemote());
+		}
+	}
+
+	/**
+	 * Check that a push without specifying a remote or mode or anything can
+	 * succeed if the git config is correct.
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void testDefaultPush() throws Exception {
+		try (Git git = new Git(db);
+				Git git2 = new Git(createBareRepository())) {
+			StoredConfig config = git.getRepository().getConfig();
+			RemoteConfig remoteConfig = new RemoteConfig(config, "test");
+			URIish uri = new URIish(
+					git2.getRepository().getDirectory().toURI().toURL());
+			remoteConfig.addURI(uri);
+			remoteConfig.addFetchRefSpec(
+					new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
+			remoteConfig.update(config);
+			config.save();
+
+			writeTrashFile("f", "content of f");
+			git.add().addFilepattern("f").call();
+			RevCommit commit = git.commit().setMessage("adding f").call();
+
+			git.checkout().setName("not-pushed").setCreateBranch(true).call();
+			git.checkout().setName("branchtopush").setCreateBranch(true).call();
+
+			config = git.getRepository().getConfig();
+			config.setString("branch", "branchtopush", "remote", "test");
+			config.save();
+
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/branchtopush"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/not-pushed"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/master"));
+			// Should use remote "test", push.default=current
+			PushCommand cmd = git.push();
+			cmd.call();
+			assertEquals("test", cmd.getRemote());
+			assertEquals(PushDefault.CURRENT, cmd.getPushDefault());
+			assertEquals(commit.getId(),
+					git2.getRepository().resolve("refs/heads/branchtopush"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/not-pushed"));
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/master"));
+			assertEquals(commit.getId(), git.getRepository()
+					.resolve("refs/remotes/origin/branchtopush"));
+		}
+	}
+
+	/**
 	 * Check that missing refs don't cause errors during push
 	 *
 	 * @throws Exception
@@ -297,6 +1068,7 @@
 	public void testPushAfterGC() throws Exception {
 		// create other repository
 		Repository db2 = createWorkRepository();
+		addRepoToClose(db2);
 
 		// setup the first repository
 		final StoredConfig config = db.getConfig();
@@ -360,6 +1132,7 @@
 
 		// create other repository
 		Repository db2 = createWorkRepository();
+		addRepoToClose(db2);
 
 		// setup the first repository
 		final StoredConfig config = db.getConfig();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
index 8623902..c64ff0b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
@@ -2927,8 +2927,8 @@
 		}
 	}
 
-	@Test
-	public void testRebaseInteractiveFixupWithBlankLines() throws Exception {
+	private void simpleFixup(String firstMessage, String secondMessage)
+			throws Exception {
 		// create file1 on master
 		writeTrashFile(FILE1, FILE1);
 		git.add().addFilepattern(FILE1).call();
@@ -2938,13 +2938,13 @@
 		// create file2 on master
 		writeTrashFile("file2", "file2");
 		git.add().addFilepattern("file2").call();
-		git.commit().setMessage("Add file2").call();
+		git.commit().setMessage(firstMessage).call();
 		assertTrue(new File(db.getWorkTree(), "file2").exists());
 
 		// update FILE1 on master
 		writeTrashFile(FILE1, "blah");
 		git.add().addFilepattern(FILE1).call();
-		git.commit().setMessage("updated file1 on master\n\nsome text").call();
+		git.commit().setMessage(secondMessage).call();
 
 		git.rebase().setUpstream("HEAD~2")
 				.runInteractively(new InteractiveHandler() {
@@ -2968,9 +2968,31 @@
 		try (RevWalk walk = new RevWalk(db)) {
 			ObjectId headId = db.resolve(Constants.HEAD);
 			RevCommit headCommit = walk.parseCommit(headId);
-			assertEquals("Add file2",
-					headCommit.getFullMessage());
+			assertEquals(firstMessage, headCommit.getFullMessage());
 		}
+
+	}
+
+	@Test
+	public void testRebaseInteractiveFixupWithBlankLines() throws Exception {
+		simpleFixup("Add file2", "updated file1 on master\n\nsome text");
+	}
+
+	@Test
+	public void testRebaseInteractiveFixupWithBlankLines2() throws Exception {
+		simpleFixup("Add file2\n\nBody\n",
+				"updated file1 on master\n\nsome text");
+	}
+
+	@Test
+	public void testRebaseInteractiveFixupWithHash() throws Exception {
+		simpleFixup("#Add file2", "updated file1 on master");
+	}
+
+	@Test
+	public void testRebaseInteractiveFixupWithHash2() throws Exception {
+		simpleFixup("#Add file2\n\nHeader has hash\n",
+				"#updated file1 on master");
 	}
 
 	@Test(expected = InvalidRebaseStepException.class)
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RevertCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RevertCommandTest.java
index cfa8486..1c7b8d1 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RevertCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RevertCommandTest.java
@@ -9,6 +9,7 @@
  */
 package org.eclipse.jgit.api;
 
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -166,7 +167,9 @@
 
 			checkFile(new File(db.getWorkTree(), "a"), "first\n"
 					+ "<<<<<<< master\n" + "second\n" + "third\n" + "=======\n"
-					+ ">>>>>>> " + secondCommit.getId().abbreviate(7).name()
+					+ ">>>>>>> "
+					+ secondCommit.getId()
+							.abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH).name()
 					+ " add second\n");
 			Iterator<RevCommit> history = git.log().call().iterator();
 			RevCommit revertCommit = history.next();
@@ -232,7 +235,7 @@
 			assertTrue(new File(db.getDirectory(), Constants.MERGE_MSG).exists());
 			assertEquals("Revert \"" + sideCommit.getShortMessage()
 					+ "\"\n\nThis reverts commit " + sideCommit.getId().getName()
-					+ ".\n\nConflicts:\n\ta\n",
+					+ ".\n\n# Conflicts:\n#\ta\n",
 					db.readMergeCommitMsg());
 			assertTrue(new File(db.getDirectory(), Constants.REVERT_HEAD)
 					.exists());
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 5311edb..19281f6 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
@@ -21,8 +21,10 @@
 import org.eclipse.jgit.api.errors.NoFilepatternException;
 import org.eclipse.jgit.errors.NoWorkTreeException;
 import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.Sets;
 import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
 import org.eclipse.jgit.util.FS;
 import org.junit.Test;
 
@@ -181,4 +183,31 @@
 		}
 	}
 
+	@Test
+	public void testNestedCommittedGitRepoAndPathFilter() throws Exception {
+		commitFile("file.txt", "file", "master");
+		try (Repository inner = new FileRepositoryBuilder()
+				.setWorkTree(new File(db.getWorkTree(), "subgit")).build()) {
+			inner.create();
+			writeTrashFile("subgit/sub.txt", "sub");
+			try (Git outerGit = new Git(db); Git innerGit = new Git(inner)) {
+				innerGit.add().addFilepattern("sub.txt").call();
+				innerGit.commit().setMessage("Inner commit").call();
+				outerGit.add().addFilepattern("subgit").call();
+				outerGit.commit().setMessage("Outer commit").call();
+				assertTrue(innerGit.status().call().isClean());
+				assertTrue(outerGit.status().call().isClean());
+				writeTrashFile("subgit/sub.txt", "sub2");
+				assertFalse(innerGit.status().call().isClean());
+				assertFalse(outerGit.status().call().isClean());
+				assertTrue(
+						outerGit.status().addPath("file.txt").call().isClean());
+				assertTrue(outerGit.status().addPath("doesntexist").call()
+						.isClean());
+				assertFalse(
+						outerGit.status().addPath("subgit").call().isClean());
+			}
+		}
+	}
+
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/BareSuperprojectWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/BareSuperprojectWriterTest.java
new file mode 100644
index 0000000..c3b9387
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/BareSuperprojectWriterTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2021, Google Inc. 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
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.gitrepo;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.is;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.jgit.gitrepo.BareSuperprojectWriter.BareWriterConfig;
+import org.eclipse.jgit.gitrepo.RepoCommand.RemoteReader;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Test;
+
+public class BareSuperprojectWriterTest extends RepositoryTestCase {
+
+	private static final String SHA1_A = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+
+	@Override
+	public void setUp() throws Exception {
+		super.setUp();
+	}
+
+	@Test
+	public void write_setGitModulesContents() throws Exception {
+		try (Repository bareRepo = createBareRepository()) {
+			RepoProject repoProject = new RepoProject("subprojectX", "path/to",
+					"refs/heads/branch-x", "remote", "");
+			repoProject.setUrl("http://example.com/a");
+
+			RemoteReader mockRemoteReader = mock(RemoteReader.class);
+			when(mockRemoteReader.sha1("http://example.com/a",
+					"refs/heads/branch-x"))
+							.thenReturn(ObjectId.fromString(SHA1_A));
+
+			BareSuperprojectWriter w = new BareSuperprojectWriter(bareRepo,
+					null, "refs/heads/master", author, mockRemoteReader,
+					BareWriterConfig.getDefault(), List.of());
+
+			RevCommit commit = w.write(Arrays.asList(repoProject));
+
+			String contents = readContents(bareRepo, commit, ".gitmodules");
+			List<String> contentLines = Arrays
+					.asList(contents.split("\n"));
+			assertThat(contentLines.get(0),
+					is("[submodule \"subprojectX\"]"));
+			assertThat(contentLines.subList(1, contentLines.size()),
+					containsInAnyOrder(is("\tbranch = refs/heads/branch-x"),
+							is("\tpath = path/to"),
+							is("\turl = http://example.com/a")));
+		}
+	}
+
+	@Test
+	public void write_setExtraContents() throws Exception {
+		try (Repository bareRepo = createBareRepository()) {
+			RepoProject repoProject = new RepoProject("subprojectX", "path/to",
+					"refs/heads/branch-x", "remote", "");
+			repoProject.setUrl("http://example.com/a");
+
+			RemoteReader mockRemoteReader = mock(RemoteReader.class);
+			when(mockRemoteReader.sha1("http://example.com/a",
+					"refs/heads/branch-x"))
+							.thenReturn(ObjectId.fromString(SHA1_A));
+
+			BareSuperprojectWriter w = new BareSuperprojectWriter(bareRepo,
+					null, "refs/heads/master", author, mockRemoteReader,
+					BareWriterConfig.getDefault(),
+					List.of(new BareSuperprojectWriter.ExtraContent("x",
+							"extra-content")));
+
+			RevCommit commit = w.write(Arrays.asList(repoProject));
+
+			String contents = readContents(bareRepo, commit, "x");
+			assertThat(contents, is("extra-content"));
+		}
+	}
+
+	private String readContents(Repository repo, RevCommit commit,
+			String path) throws Exception {
+		String idStr = commit.getId().name() + ":" + path;
+		ObjectId modId = repo.resolve(idStr);
+		try (ObjectReader reader = repo.newObjectReader()) {
+			return new String(
+					reader.open(modId).getCachedBytes(Integer.MAX_VALUE),
+					StandardCharsets.UTF_8);
+
+		}
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java
index 509adc2..3e6d13a 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java
@@ -15,6 +15,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
 import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
@@ -546,24 +547,29 @@
 		// The original file should exist
 		File hello = new File(localDb.getWorkTree(), "foo/hello.txt");
 		assertTrue("The original file should exist", hello.exists());
-		assertFalse("The original file should not be executable",
-				hello.canExecute());
+		if (FS.DETECTED.supportsExecute()) {
+			assertFalse("The original file should not be executable",
+					FS.DETECTED.canExecute(hello));
+		}
 		assertContents(hello.toPath(), "master world");
 		// The dest file should also exist
 		hello = new File(localDb.getWorkTree(), "Hello");
 		assertTrue("The destination file should exist", hello.exists());
-		assertFalse("The destination file should not be executable",
-				hello.canExecute());
+		if (FS.DETECTED.supportsExecute()) {
+			assertFalse("The destination file should not be executable",
+					FS.DETECTED.canExecute(hello));
+		}
 		assertContents(hello.toPath(), "master world");
 	}
 
 	@Test
 	public void testRepoManifestCopyFile_executable() throws Exception {
+		assumeTrue(FS.DETECTED.supportsExecute());
 		try (Git git = new Git(defaultDb)) {
 			git.checkout().setName("master").call();
 			File f = JGitTestUtil.writeTrashFile(defaultDb, "hello.sh",
 					"content of the executable file");
-			f.setExecutable(true);
+			FS.DETECTED.setExecute(f, true);
 			git.add().addFilepattern("hello.sh").call();
 			git.commit().setMessage("Add binary file").call();
 		}
@@ -588,7 +594,8 @@
 		// The original file should exist and be an executable
 		File hello = new File(localDb.getWorkTree(), "foo/hello.sh");
 		assertTrue("The original file should exist", hello.exists());
-		assertTrue("The original file must be executable", hello.canExecute());
+		assertTrue("The original file must be executable",
+				FS.DETECTED.canExecute(hello));
 		try (BufferedReader reader = Files.newBufferedReader(hello.toPath(),
 				UTF_8)) {
 			String content = reader.readLine();
@@ -600,7 +607,7 @@
 		hello = new File(localDb.getWorkTree(), "copy-hello.sh");
 		assertTrue("The destination file should exist", hello.exists());
 		assertTrue("The destination file must be executable",
-				hello.canExecute());
+				FS.DETECTED.canExecute(hello));
 		try (BufferedReader reader = Files.newBufferedReader(hello.toPath(),
 				UTF_8)) {
 			String content = reader.readLine();
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
new file mode 100644
index 0000000..c9ebec7
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalDiffToolTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2020-2021, Simeon Andreev <simeon.danailov.andreev@gmail.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
+ * 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_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;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.junit.Test;
+
+/**
+ * Testing external diff tools.
+ */
+public class ExternalDiffToolTest extends ExternalToolTestCase {
+
+	@Test
+	public void testToolNames() {
+		DiffTools manager = new DiffTools(db);
+		Set<String> actualToolNames = manager.getToolNames();
+		Set<String> expectedToolNames = Collections.emptySet();
+		assertEquals("Incorrect set of external diff tool names",
+				expectedToolNames, actualToolNames);
+	}
+
+	@Test
+	public void testAllTools() {
+		DiffTools manager = new DiffTools(db);
+		Set<String> actualToolNames = manager.getAvailableTools().keySet();
+		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 = new LinkedHashSet<>();
+		expectedToolNames.add(customToolname);
+		assertEquals("Incorrect set of external diff tools", expectedToolNames,
+				actualToolNames);
+	}
+
+	@Test
+	public void testNotAvailableTools() {
+		DiffTools manager = new DiffTools(db);
+		Set<String> actualToolNames = manager.getNotAvailableTools().keySet();
+		Set<String> expectedToolNames = Collections.emptySet();
+		assertEquals("Incorrect set of not available external diff tools",
+				expectedToolNames, actualToolNames);
+	}
+
+	@Test
+	public void testCompare() {
+		DiffTools manager = new DiffTools(db);
+
+		String newPath = "";
+		String oldPath = "";
+		String newId = "";
+		String oldId = "";
+		String toolName = "";
+		BooleanTriState prompt = BooleanTriState.UNSET;
+		BooleanTriState gui = BooleanTriState.UNSET;
+		BooleanTriState trustExitCode = BooleanTriState.UNSET;
+
+		int expectedCompareResult = 0;
+		int compareResult = manager.compare(newPath, oldPath, newId, oldId,
+				toolName, prompt, gui, trustExitCode);
+		assertEquals("Incorrect compare result for external diff tool",
+				expectedCompareResult, compareResult);
+	}
+
+	@Test
+	public void testDefaultTool() throws Exception {
+		FileBasedConfig config = db.getConfig();
+		// the default diff tool is configured without a subsection
+		String subsection = null;
+		config.setString("diff", subsection, "tool", "customTool");
+
+		DiffTools manager = new DiffTools(db);
+		BooleanTriState gui = BooleanTriState.UNSET;
+		String defaultToolName = manager.getDefaultToolName(gui);
+		assertEquals(
+				"Expected configured difftool to be the default external diff tool",
+				"my_default_toolname", defaultToolName);
+
+		gui = BooleanTriState.TRUE;
+		String defaultGuiToolName = manager.getDefaultToolName(gui);
+		assertEquals(
+				"Expected configured difftool to be the default external diff tool",
+				"my_gui_tool", defaultGuiToolName);
+
+		config.setString("diff", subsection, "guitool", "customGuiTool");
+		manager = new DiffTools(db);
+		defaultGuiToolName = manager.getDefaultToolName(gui);
+		assertEquals(
+				"Expected configured difftool to be the default external diff guitool",
+				"my_gui_tool", defaultGuiToolName);
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalToolTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalToolTestCase.java
new file mode 100644
index 0000000..0cc1297
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/diffmergetool/ExternalToolTestCase.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2020-2021, Simeon Andreev <simeon.danailov.andreev@gmail.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
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.diffmergetool;
+
+import java.io.File;
+import java.nio.file.Files;
+
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.FS_POSIX;
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Before;
+
+/**
+ * Base test case for external merge and diff tool tests.
+ */
+public abstract class ExternalToolTestCase extends RepositoryTestCase {
+
+	protected static final String DEFAULT_CONTENT = "line1";
+
+	protected File localFile;
+
+	protected File remoteFile;
+
+	protected File mergedFile;
+
+	protected File baseFile;
+
+	protected File commandResult;
+
+	@Before
+	@Override
+	public void setUp() throws Exception {
+		super.setUp();
+
+		localFile = writeTrashFile("localFile.txt", DEFAULT_CONTENT + "\n");
+		localFile.deleteOnExit();
+		remoteFile = writeTrashFile("remoteFile.txt", DEFAULT_CONTENT + "\n");
+		remoteFile.deleteOnExit();
+		mergedFile = writeTrashFile("mergedFile.txt", "");
+		mergedFile.deleteOnExit();
+		baseFile = writeTrashFile("baseFile.txt", "");
+		baseFile.deleteOnExit();
+		commandResult = writeTrashFile("commandResult.txt", "");
+		commandResult.deleteOnExit();
+	}
+
+	@After
+	@Override
+	public void tearDown() throws Exception {
+		Files.delete(localFile.toPath());
+		Files.delete(remoteFile.toPath());
+		Files.delete(mergedFile.toPath());
+		Files.delete(baseFile.toPath());
+		Files.delete(commandResult.toPath());
+
+		super.tearDown();
+	}
+
+
+	protected static void assumePosixPlatform() {
+		Assume.assumeTrue(
+				"This test can run only in Linux tests",
+				FS.DETECTED instanceof FS_POSIX);
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTest.java
index 4f13140..ab588cb 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTest.java
@@ -15,16 +15,19 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import java.time.Duration;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.LongStream;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
+import org.eclipse.jgit.internal.storage.dfs.DfsBlockCacheConfig.IndexEventConsumer;
 import org.eclipse.jgit.internal.storage.pack.PackExt;
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.junit.TestRng;
@@ -154,6 +157,119 @@
 
 	@SuppressWarnings("resource")
 	@Test
+	public void hasIndexEventConsumerOnlyLoaded() throws Exception {
+		AtomicInteger loaded = new AtomicInteger();
+		IndexEventConsumer indexEventConsumer = new IndexEventConsumer() {
+			@Override
+			public void acceptRequestedEvent(int packExtPos, boolean cacheHit,
+					long loadMicros, long bytes,
+					Duration lastEvictionDuration) {
+				assertEquals(PackExt.INDEX.getPosition(), packExtPos);
+				assertTrue(cacheHit);
+				assertTrue(lastEvictionDuration.isZero());
+				loaded.incrementAndGet();
+			}
+		};
+
+		DfsBlockCache.reconfigure(new DfsBlockCacheConfig().setBlockSize(512)
+				.setBlockLimit(512 * 4)
+				.setIndexEventConsumer(indexEventConsumer));
+		cache = DfsBlockCache.getInstance();
+
+		DfsRepositoryDescription repo = new DfsRepositoryDescription("test");
+		InMemoryRepository r1 = new InMemoryRepository(repo);
+		byte[] content = rng.nextBytes(424242);
+		ObjectId id;
+		try (ObjectInserter ins = r1.newObjectInserter()) {
+			id = ins.insert(OBJ_BLOB, content);
+			ins.flush();
+		}
+
+		try (ObjectReader rdr = r1.newObjectReader()) {
+			byte[] actual = rdr.open(id, OBJ_BLOB).getBytes();
+			assertTrue(Arrays.equals(content, actual));
+		}
+		// All cache entries are hot and cache is at capacity.
+		assertTrue(LongStream.of(cache.getHitCount()).sum() > 0);
+		assertEquals(99, cache.getFillPercentage());
+
+		InMemoryRepository r2 = new InMemoryRepository(repo);
+		content = rng.nextBytes(424242);
+		try (ObjectInserter ins = r2.newObjectInserter()) {
+			ins.insert(OBJ_BLOB, content);
+			ins.flush();
+		}
+		assertTrue(cache.getEvictions()[PackExt.PACK.getPosition()] > 0);
+		assertEquals(1, cache.getEvictions()[PackExt.INDEX.getPosition()]);
+		assertEquals(1, loaded.get());
+	}
+
+	@SuppressWarnings("resource")
+	@Test
+	public void hasIndexEventConsumerLoadedAndEvicted() throws Exception {
+		AtomicInteger loaded = new AtomicInteger();
+		AtomicInteger evicted = new AtomicInteger();
+		IndexEventConsumer indexEventConsumer = new IndexEventConsumer() {
+			@Override
+			public void acceptRequestedEvent(int packExtPos, boolean cacheHit,
+					long loadMicros, long bytes,
+					Duration lastEvictionDuration) {
+				assertEquals(PackExt.INDEX.getPosition(), packExtPos);
+				assertTrue(cacheHit);
+				assertTrue(lastEvictionDuration.isZero());
+				loaded.incrementAndGet();
+			}
+
+			@Override
+			public void acceptEvictedEvent(int packExtPos, long bytes,
+					int totalCacheHitCount, Duration lastEvictionDuration) {
+				assertEquals(PackExt.INDEX.getPosition(), packExtPos);
+				assertTrue(totalCacheHitCount > 0);
+				assertTrue(lastEvictionDuration.isZero());
+				evicted.incrementAndGet();
+			}
+
+			@Override
+			public boolean shouldReportEvictedEvent() {
+				return true;
+			}
+		};
+
+		DfsBlockCache.reconfigure(new DfsBlockCacheConfig().setBlockSize(512)
+				.setBlockLimit(512 * 4)
+				.setIndexEventConsumer(indexEventConsumer));
+		cache = DfsBlockCache.getInstance();
+
+		DfsRepositoryDescription repo = new DfsRepositoryDescription("test");
+		InMemoryRepository r1 = new InMemoryRepository(repo);
+		byte[] content = rng.nextBytes(424242);
+		ObjectId id;
+		try (ObjectInserter ins = r1.newObjectInserter()) {
+			id = ins.insert(OBJ_BLOB, content);
+			ins.flush();
+		}
+
+		try (ObjectReader rdr = r1.newObjectReader()) {
+			byte[] actual = rdr.open(id, OBJ_BLOB).getBytes();
+			assertTrue(Arrays.equals(content, actual));
+		}
+		// All cache entries are hot and cache is at capacity.
+		assertTrue(LongStream.of(cache.getHitCount()).sum() > 0);
+		assertEquals(99, cache.getFillPercentage());
+
+		InMemoryRepository r2 = new InMemoryRepository(repo);
+		content = rng.nextBytes(424242);
+		try (ObjectInserter ins = r2.newObjectInserter()) {
+			ins.insert(OBJ_BLOB, content);
+			ins.flush();
+		}
+		assertTrue(cache.getEvictions()[PackExt.PACK.getPosition()] > 0);
+		assertEquals(1, cache.getEvictions()[PackExt.INDEX.getPosition()]);
+		assertEquals(1, loaded.get());
+		assertEquals(1, evicted.get());
+	}
+
+	@Test
 	public void noConcurrencySerializedReads_oneRepo() throws Exception {
 		InMemoryRepository r1 = createRepoWithBitmap("test");
 		// Reset cache with concurrency Level at 1 i.e. no concurrency.
@@ -267,7 +383,6 @@
 		assertEquals(2, cache.getMissCount()[0]);
 	}
 
-	@SuppressWarnings("resource")
 	@Test
 	public void highConcurrencyParallelReads_oneRepo() throws Exception {
 		InMemoryRepository r1 = createRepoWithBitmap("test");
@@ -290,6 +405,30 @@
 		assertEquals(1, cache.getMissCount()[0]);
 	}
 
+	@Test
+	public void highConcurrencyParallelReads_oneRepoParallelReverseIndex()
+			throws Exception {
+		InMemoryRepository r1 = createRepoWithBitmap("test");
+		resetCache();
+
+		DfsReader reader = (DfsReader) r1.newObjectReader();
+		reader.getOptions().setLoadRevIndexInParallel(true);
+		for (DfsPackFile pack : r1.getObjectDatabase().getPacks()) {
+			// Only load non-garbage pack with bitmap.
+			if (pack.isGarbage()) {
+				continue;
+			}
+			asyncRun(() -> pack.getBitmapIndex(reader));
+			asyncRun(() -> pack.getPackIndex(reader));
+			asyncRun(() -> pack.getBitmapIndex(reader));
+		}
+		waitForExecutorPoolTermination();
+
+		assertEquals(1, cache.getMissCount()[PackExt.BITMAP_INDEX.ordinal()]);
+		assertEquals(1, cache.getMissCount()[PackExt.INDEX.ordinal()]);
+		assertEquals(1, cache.getMissCount()[0]);
+	}
+
 	private void resetCache() {
 		resetCache(32);
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java
index 6357a0b..daf4382 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java
@@ -42,6 +42,8 @@
 import java.util.Map;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.function.Predicate;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 import org.eclipse.jgit.events.ListenerHandle;
 import org.eclipse.jgit.events.RefsChangedListener;
@@ -127,6 +129,7 @@
 		}
 
 		diskRepo = fileRepo;
+		addRepoToClose(diskRepo);
 		setLogAllRefUpdates(true);
 
 		if (!useReftable) {
@@ -1190,7 +1193,8 @@
 		}
 
 		Map<String, Ref> refs = diskRepo.getRefDatabase()
-				.getRefs(RefDatabase.ALL);
+				.getRefsByPrefix(RefDatabase.ALL).stream()
+				.collect(Collectors.toMap(Ref::getName, Function.identity()));
 		Ref actualHead = refs.remove(Constants.HEAD);
 		if (actualHead != null) {
 			String actualLeafName = actualHead.getLeaf().getName();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableStackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableStackTest.java
index 6c74f00..6c79927 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableStackTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableStackTest.java
@@ -14,6 +14,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
 
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -32,10 +33,12 @@
 import org.eclipse.jgit.lib.ObjectIdRef;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.util.FileUtils;
+import org.eclipse.jgit.util.SystemReader;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
+
 public class FileReftableStackTest {
 
 	private static Ref newRef(String name, ObjectId id) {
@@ -115,9 +118,12 @@
 		testCompaction(1024);
 	}
 
-	@SuppressWarnings({ "resource", "unused" })
+	@SuppressWarnings("resource")
 	@Test
 	public void missingReftable() throws Exception {
+		// Can't delete in-use files on Windows.
+		assumeFalse(SystemReader.getInstance().isWindows());
+
 		try (FileReftableStack stack = new FileReftableStack(
 				new File(reftableDir, "refs"), reftableDir, null,
 				() -> new Config())) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileRepositoryBuilderAfterOpenConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileRepositoryBuilderAfterOpenConfigTest.java
new file mode 100644
index 0000000..100bd32
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileRepositoryBuilderAfterOpenConfigTest.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2023 Qualcomm Innovation Center, Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * 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.storage.file;
+
+import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.CoreConfig;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.util.SystemReader;
+import org.junit.Before;
+
+public class FileRepositoryBuilderAfterOpenConfigTest extends FileRepositoryBuilderTest {
+	/** {@inheritDoc} */
+	@Before
+	@Override
+	public void setUp() throws Exception {
+		super.setUp();
+		StoredConfig userConfig = SystemReader.getInstance().getUserConfig();
+		userConfig.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
+				ConfigConstants.CONFIG_KEY_TRUST_PACKED_REFS_STAT,
+				CoreConfig.TrustPackedRefsStat.AFTER_OPEN);
+		userConfig.save();
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java
index bfb233f..48f6e06 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java
@@ -42,6 +42,7 @@
 	@Override
 	@After
 	public void tearDown() throws Exception {
+		tr.close();
 		super.tearDown();
 	}
 
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 1ff2264..3fe8f52 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
@@ -722,6 +722,7 @@
 	 */
 	private FileRepository setUpRepoWithMultiplePackfiles() throws Exception {
 		FileRepository fileRepository = createWorkRepository();
+		addRepoToClose(fileRepository);
 		try (Git git = new Git(fileRepository)) {
 			// Creates 2 objects (C1 = commit, T1 = tree)
 			git.commit().setMessage("First commit").call();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryAfterOpenConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryAfterOpenConfigTest.java
new file mode 100644
index 0000000..42304e2
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryAfterOpenConfigTest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2023 Qualcomm Innovation Center, Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * 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.storage.file;
+
+import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.CoreConfig;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.util.SystemReader;
+
+public class RefDirectoryAfterOpenConfigTest extends RefDirectoryTest {
+	/** {@inheritDoc} */
+	@Override
+	public void refDirectorySetup() throws Exception {
+		StoredConfig userConfig = SystemReader.getInstance().getUserConfig();
+		userConfig.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
+				ConfigConstants.CONFIG_KEY_TRUST_PACKED_REFS_STAT,
+				CoreConfig.TrustPackedRefsStat.AFTER_OPEN);
+		userConfig.save();
+		super.refDirectorySetup();
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
index 38c545e..4b80f18 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
@@ -72,7 +72,10 @@
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
+		refDirectorySetup();
+	}
 
+	public void refDirectorySetup() throws Exception {
 		diskRepo = createBareRepository();
 		refdir = (RefDirectory) diskRepo.getRefDatabase();
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/io/CancellableDigestOutputStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/io/CancellableDigestOutputStreamTest.java
new file mode 100644
index 0000000..09a7c0b
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/io/CancellableDigestOutputStreamTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2022, Tencent.
+ *
+ * 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.storage.io;
+
+import static org.eclipse.jgit.internal.storage.io.CancellableDigestOutputStream.BYTES_TO_WRITE_BEFORE_CANCEL_CHECK;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import java.io.InterruptedIOException;
+
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.util.io.NullOutputStream;
+import org.junit.Test;
+
+public class CancellableDigestOutputStreamTest {
+	private static class CancelledTestMonitor implements ProgressMonitor {
+
+		private boolean cancelled = false;
+
+		public void setCancelled(boolean cancelled) {
+			this.cancelled = cancelled;
+		}
+
+		@Override
+		public void start(int totalTasks) {
+			// not implemented
+		}
+
+		@Override
+		public void beginTask(String title, int totalWork) {
+			// not implemented
+		}
+
+		@Override
+		public void update(int completed) {
+			// not implemented
+		}
+
+		@Override
+		public void endTask() {
+			// not implemented
+		}
+
+		@Override
+		public boolean isCancelled() {
+			return cancelled;
+		}
+	}
+
+	@Test
+	public void testCancelInProcess() throws Exception {
+		CancelledTestMonitor m = new CancelledTestMonitor();
+		try (CancellableDigestOutputStream out = new CancellableDigestOutputStream(
+				m, NullOutputStream.INSTANCE)) {
+			byte[] KB = new byte[1024];
+			int triggerCancelWriteCnt = BYTES_TO_WRITE_BEFORE_CANCEL_CHECK
+					/ KB.length;
+			for (int i = 0; i < triggerCancelWriteCnt + 1; i++) {
+				out.write(KB);
+			}
+			assertTrue(out.length() > BYTES_TO_WRITE_BEFORE_CANCEL_CHECK);
+			m.setCancelled(true);
+
+			for (int i = 0; i < triggerCancelWriteCnt - 1; i++) {
+				out.write(KB);
+			}
+
+			long lastLength = out.length();
+			assertThrows(InterruptedIOException.class, () -> {
+				out.write(1);
+			});
+			assertEquals(lastLength, out.length());
+
+			assertThrows(InterruptedIOException.class, () -> {
+				out.write(new byte[1]);
+			});
+			assertEquals(lastLength, out.length());
+		}
+	}
+
+	@Test
+	public void testTriggerCheckAfterSingleBytes() throws Exception {
+		CancelledTestMonitor m = new CancelledTestMonitor();
+		try (CancellableDigestOutputStream out = new CancellableDigestOutputStream(
+				m, NullOutputStream.INSTANCE)) {
+
+			byte[] bytes = new byte[BYTES_TO_WRITE_BEFORE_CANCEL_CHECK + 1];
+			m.setCancelled(true);
+
+			assertThrows(InterruptedIOException.class, () -> {
+				out.write(bytes);
+			});
+			assertEquals(BYTES_TO_WRITE_BEFORE_CANCEL_CHECK, out.length());
+		}
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFileTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFileTest.java
index 11741b4..d065280 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFileTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFileTest.java
@@ -369,20 +369,21 @@
 
 	@Test
 	public void testListValueSingle() throws Exception {
-		config("Host orcz\nUserKnownHostsFile /foo/bar\n");
+		config("Host orcz\nUserKnownHostsFile ~/foo/bar\n");
 		final HostConfig c = lookup("orcz");
 		assertNotNull(c);
-		assertEquals("/foo/bar", c.getValue("UserKnownHostsFile"));
+		assertEquals(new File(home, "foo/bar").getPath(),
+				c.getValue("UserKnownHostsFile"));
 	}
 
 	@Test
 	public void testListValueMultiple() throws Exception {
 		// Tilde expansion occurs within the parser
-		config("Host orcz\nUserKnownHostsFile \"~/foo/ba z\" /foo/bar \n");
+		config("Host orcz\nUserKnownHostsFile \"~/foo/ba z\" ~/foo/bar \n");
 		final HostConfig c = lookup("orcz");
 		assertNotNull(c);
 		assertArrayEquals(new Object[] { new File(home, "foo/ba z").getPath(),
-				"/foo/bar" },
+				new File(home, "foo/bar").getPath() },
 				c.getValues("UserKnownHostsFile").toArray());
 	}
 
@@ -403,22 +404,23 @@
 
 	@Test
 	public void testIdentityFile() throws Exception {
-		config("Host orcz\nIdentityFile \"~/foo/ba z\"\nIdentityFile /foo/bar");
+		config("Host orcz\nIdentityFile \"~/foo/ba z\"\nIdentityFile ~/foo/bar");
 		final HostConfig h = lookup("orcz");
 		assertNotNull(h);
 		// Does tilde replacement
 		assertArrayEquals(new Object[] { new File(home, "foo/ba z").getPath(),
-				"/foo/bar" },
+				new File(home, "foo/bar").getPath() },
 				h.getValues(SshConstants.IDENTITY_FILE).toArray());
 	}
 
 	@Test
 	public void testMultiIdentityFile() throws Exception {
-		config("IdentityFile \"~/foo/ba z\"\nHost orcz\nIdentityFile /foo/bar\nHOST *\nIdentityFile /foo/baz");
+		config("IdentityFile \"~/foo/ba z\"\nHost orcz\nIdentityFile ~/foo/bar\nHOST *\nIdentityFile ~/foo/baz");
 		final HostConfig h = lookup("orcz");
 		assertNotNull(h);
 		assertArrayEquals(new Object[] { new File(home, "foo/ba z").getPath(),
-				"/foo/bar", "/foo/baz" },
+				new File(home, "foo/bar").getPath(),
+				new File(home, "foo/baz").getPath() },
 				h.getValues(SshConstants.IDENTITY_FILE).toArray());
 	}
 
@@ -434,23 +436,23 @@
 
 	@Test
 	public void testPattern() throws Exception {
-		config("Host repo.or.cz\nIdentityFile ~/foo/bar\nHOST *.or.cz\nIdentityFile /foo/baz");
+		config("Host repo.or.cz\nIdentityFile ~/foo/bar\nHOST *.or.cz\nIdentityFile ~/foo/baz");
 		final HostConfig h = lookup("repo.or.cz");
 		assertNotNull(h);
 		assertIdentity(new File(home, "foo/bar"), h);
 		assertArrayEquals(new Object[] { new File(home, "foo/bar").getPath(),
-				"/foo/baz" },
+				new File(home, "foo/baz").getPath() },
 				h.getValues(SshConstants.IDENTITY_FILE).toArray());
 	}
 
 	@Test
 	public void testMultiHost() throws Exception {
-		config("Host orcz *.or.cz\nIdentityFile ~/foo/bar\nHOST *.or.cz\nIdentityFile /foo/baz");
+		config("Host orcz *.or.cz\nIdentityFile ~/foo/bar\nHOST *.or.cz\nIdentityFile ~/foo/baz");
 		final HostConfig h1 = lookup("repo.or.cz");
 		assertNotNull(h1);
 		assertIdentity(new File(home, "foo/bar"), h1);
 		assertArrayEquals(new Object[] { new File(home, "foo/bar").getPath(),
-				"/foo/baz" },
+				new File(home, "foo/baz").getPath() },
 				h1.getValues(SshConstants.IDENTITY_FILE).toArray());
 		final HostConfig h2 = lookup("orcz");
 		assertNotNull(h2);
@@ -547,18 +549,36 @@
 
 	@Test
 	public void testEnVarSubstitution() throws Exception {
-		config("Host orcz\nIdentityFile /tmp/${TST_VAR}\n"
-				+ "CertificateFile /tmp/${}/foo\nUser ${TST_VAR}\nIdentityAgent /tmp/${TST_VAR/bar");
+		config("Host orcz\nIdentityFile ~/tmp/${TST_VAR}\n"
+				+ "CertificateFile ~/tmp/${}/foo\nUser ${TST_VAR}\nIdentityAgent ~/tmp/${TST_VAR/bar");
 		HostConfig h = lookup("orcz");
 		assertNotNull(h);
-		assertEquals("/tmp/TEST",
+		File tmp = new File(home, "tmp");
+		assertEquals(new File(tmp, "TEST").getPath(),
 				h.getValue(SshConstants.IDENTITY_FILE));
 		// No variable name
-		assertEquals("/tmp/${}/foo", h.getValue(SshConstants.CERTIFICATE_FILE));
+		assertEquals(new File(new File(tmp, "${}"), "foo").getPath(),
+				h.getValue(SshConstants.CERTIFICATE_FILE));
 		// User doesn't get env var substitution:
 		assertUser("${TST_VAR}", h);
 		// Unterminated:
-		assertEquals("/tmp/${TST_VAR/bar",
+		assertEquals(new File(new File(tmp, "${TST_VAR"), "bar").getPath(),
+				h.getValue(SshConstants.IDENTITY_AGENT));
+	}
+
+	@Test
+	public void testIdentityAgentNone() throws Exception {
+		config("Host orcz\nIdentityAgent none\n");
+		HostConfig h = lookup("orcz");
+		assertEquals(SshConstants.NONE,
+				h.getValue(SshConstants.IDENTITY_AGENT));
+	}
+
+	@Test
+	public void testIdentityAgentSshAuthSock() throws Exception {
+		config("Host orcz\nIdentityAgent SSH_AUTH_SOCK\n");
+		HostConfig h = lookup("orcz");
+		assertEquals(SshConstants.ENV_SSH_AUTH_SOCKET,
 				h.getValue(SshConstants.IDENTITY_AGENT));
 	}
 
@@ -607,13 +627,16 @@
 
 	@Test
 	public void testMultipleMatch() throws Exception {
-		config("Host foo.bar\nPort 29418\nIdentityFile /foo\n\n"
-				+ "Host *.bar\nPort 22\nIdentityFile /bar\n"
-				+ "Host foo.bar\nPort 47\nIdentityFile /baz\n");
+		config("Host foo.bar\nPort 29418\nIdentityFile ~/foo\n\n"
+				+ "Host *.bar\nPort 22\nIdentityFile ~/bar\n"
+				+ "Host foo.bar\nPort 47\nIdentityFile ~/baz\n");
 		HostConfig h = lookup("foo.bar");
 		assertNotNull(h);
 		assertPort(29418, h);
-		assertArrayEquals(new Object[] { "/foo", "/bar", "/baz" },
+		assertArrayEquals(
+				new Object[] { new File(home, "foo").getPath(),
+						new File(home, "bar").getPath(),
+						new File(home, "baz").getPath() },
 				h.getValues(SshConstants.IDENTITY_FILE).toArray());
 	}
 
@@ -633,4 +656,61 @@
 		assertNotNull(h);
 		assertPort(22, h);
 	}
+
+	@Test
+	public void testTimeSpec() throws Exception {
+		assertEquals(-1, OpenSshConfigFile.timeSpec(null));
+		assertEquals(-1, OpenSshConfigFile.timeSpec(""));
+		assertEquals(-1, OpenSshConfigFile.timeSpec("  "));
+		assertEquals(-1, OpenSshConfigFile.timeSpec("s"));
+		assertEquals(-1, OpenSshConfigFile.timeSpec("  s"));
+		assertEquals(-1, OpenSshConfigFile.timeSpec(" +s"));
+		assertEquals(-1, OpenSshConfigFile.timeSpec(" -s"));
+		assertEquals(-1, OpenSshConfigFile.timeSpec("1ms"));
+		assertEquals(600, OpenSshConfigFile.timeSpec("600"));
+		assertEquals(600, OpenSshConfigFile.timeSpec("600s"));
+		assertEquals(600, OpenSshConfigFile.timeSpec("  600s"));
+		assertEquals(600, OpenSshConfigFile.timeSpec("  600s  "));
+		assertEquals(600, OpenSshConfigFile.timeSpec("\t600s"));
+		assertEquals(600, OpenSshConfigFile.timeSpec(" \t600  "));
+		assertEquals(-1, OpenSshConfigFile.timeSpec("  600 s  "));
+		assertEquals(-1, OpenSshConfigFile.timeSpec("600 s"));
+		assertEquals(600, OpenSshConfigFile.timeSpec("10m"));
+		assertEquals(5400, OpenSshConfigFile.timeSpec("1h30m"));
+		assertEquals(5400, OpenSshConfigFile.timeSpec("1h 30m"));
+		assertEquals(5400, OpenSshConfigFile.timeSpec("1h \t30m"));
+		assertEquals(5400, OpenSshConfigFile.timeSpec("1h+30m"));
+		assertEquals(5400, OpenSshConfigFile.timeSpec("1h +30m"));
+		assertEquals(-1, OpenSshConfigFile.timeSpec("1h + 30m"));
+		assertEquals(-1, OpenSshConfigFile.timeSpec("1h -30m"));
+		assertEquals(3630, OpenSshConfigFile.timeSpec("1h30s"));
+		assertEquals(5400, OpenSshConfigFile.timeSpec("30m 1h"));
+		assertEquals(3600, OpenSshConfigFile.timeSpec("30m 30m"));
+		assertEquals(60, OpenSshConfigFile.timeSpec("30 30"));
+		assertEquals(0, OpenSshConfigFile.timeSpec("0"));
+		assertEquals(1, OpenSshConfigFile.timeSpec("1"));
+		assertEquals(1, OpenSshConfigFile.timeSpec("1S"));
+		assertEquals(1, OpenSshConfigFile.timeSpec("1s"));
+		assertEquals(60, OpenSshConfigFile.timeSpec("1M"));
+		assertEquals(60, OpenSshConfigFile.timeSpec("1m"));
+		assertEquals(3600, OpenSshConfigFile.timeSpec("1H"));
+		assertEquals(3600, OpenSshConfigFile.timeSpec("1h"));
+		assertEquals(86400, OpenSshConfigFile.timeSpec("1D"));
+		assertEquals(86400, OpenSshConfigFile.timeSpec("1d"));
+		assertEquals(604800, OpenSshConfigFile.timeSpec("1W"));
+		assertEquals(604800, OpenSshConfigFile.timeSpec("1w"));
+		assertEquals(172800, OpenSshConfigFile.timeSpec("2d"));
+		assertEquals(604800, OpenSshConfigFile.timeSpec("1w"));
+		assertEquals(604800 + 172800 + 3 * 3600 + 30 * 60 + 10,
+				OpenSshConfigFile.timeSpec("1w2d3h30m10s"));
+		assertEquals(-1, OpenSshConfigFile.timeSpec("-7"));
+		assertEquals(-1, OpenSshConfigFile.timeSpec("-9d"));
+		assertEquals(Integer.MAX_VALUE, OpenSshConfigFile
+				.timeSpec(Integer.toString(Integer.MAX_VALUE)));
+		assertEquals(-1, OpenSshConfigFile
+				.timeSpec(Long.toString(Integer.MAX_VALUE + 1L)));
+		assertEquals(-1, OpenSshConfigFile
+				.timeSpec(Integer.toString(Integer.MAX_VALUE / 60 + 1) + 'M'));
+		assertEquals(-1, OpenSshConfigFile.timeSpec("1000000000000000000000w"));
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/AbbrevConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/AbbrevConfigTest.java
new file mode 100644
index 0000000..96ace08
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/AbbrevConfigTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2022, Matthias Sohn <matthias.sohn@sap.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
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.lib;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.api.errors.InvalidConfigurationException;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.junit.Test;
+
+public class AbbrevConfigTest extends RepositoryTestCase {
+
+	@Test
+	public void testDefault() throws Exception {
+		assertEquals(7, testCoreAbbrev(null));
+	}
+
+	@Test
+	public void testAuto() throws Exception {
+		assertEquals(7, testCoreAbbrev("auto"));
+	}
+
+	@Test
+	public void testNo() throws Exception {
+		assertEquals(40, testCoreAbbrev("no"));
+	}
+
+	@Test
+	public void testValidMin() throws Exception {
+		assertEquals(4, testCoreAbbrev("4"));
+	}
+
+	@Test
+	public void testValid() throws Exception {
+		assertEquals(22, testCoreAbbrev("22"));
+	}
+
+	@Test
+	public void testValidMax() throws Exception {
+		assertEquals(40, testCoreAbbrev("40"));
+	}
+
+	@Test
+	public void testInvalid() {
+		assertThrows(InvalidConfigurationException.class,
+				() -> testCoreAbbrev("foo"));
+	}
+
+	@Test
+	public void testInvalid2() {
+		assertThrows(InvalidConfigurationException.class,
+				() -> testCoreAbbrev("2k"));
+	}
+
+	@Test
+	public void testInvalidNegative() {
+		assertThrows(InvalidConfigurationException.class,
+				() -> testCoreAbbrev("-1000"));
+	}
+
+	@Test
+	public void testInvalidBelowRange() {
+		assertThrows(InvalidConfigurationException.class,
+				() -> testCoreAbbrev("3"));
+	}
+
+	@Test
+	public void testInvalidBelowRange2() {
+		assertThrows(InvalidConfigurationException.class,
+				() -> testCoreAbbrev("-1"));
+	}
+
+	@Test
+	public void testInvalidAboveRange() {
+		assertThrows(InvalidConfigurationException.class,
+				() -> testCoreAbbrev("41"));
+	}
+
+	@Test
+	public void testInvalidAboveRange2() {
+		assertThrows(InvalidConfigurationException.class,
+				() -> testCoreAbbrev("100000"));
+	}
+
+	@Test
+	public void testToStringNo()
+			throws InvalidConfigurationException, IOException {
+		assertEquals("40", setCoreAbbrev("no").toString());
+	}
+
+	@Test
+	public void testToString()
+			throws InvalidConfigurationException, IOException {
+		assertEquals("7", setCoreAbbrev("auto").toString());
+	}
+
+	@Test
+	public void testToString12()
+			throws InvalidConfigurationException, IOException {
+		assertEquals("12", setCoreAbbrev("12").toString());
+	}
+
+	private int testCoreAbbrev(String value)
+			throws InvalidConfigurationException, IOException {
+		return setCoreAbbrev(value).get();
+	}
+
+	private AbbrevConfig setCoreAbbrev(String value)
+			throws IOException, InvalidConfigurationException {
+		FileBasedConfig config = db.getConfig();
+		config.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
+				ConfigConstants.CONFIG_KEY_ABBREV, value);
+		config.save();
+		return AbbrevConfig.parseFromConfig(db);
+	}
+
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/CommitConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/CommitConfigTest.java
new file mode 100644
index 0000000..d95d781
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/CommitConfigTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2022, Thomas Wolf <thomas.wolf@paranor.ch> 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
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.lib;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.CommitConfig.CleanupMode;
+import org.junit.Test;
+
+public class CommitConfigTest {
+
+	@Test
+	public void testDefaults() throws Exception {
+		CommitConfig cfg = parse("");
+		assertEquals("Unexpected clean-up mode", CleanupMode.DEFAULT,
+				cfg.getCleanupMode());
+	}
+
+	@Test
+	public void testCommitCleanup() throws Exception {
+		String[] values = { "strip", "whitespace", "verbatim", "scissors",
+				"default" };
+		CleanupMode[] expected = { CleanupMode.STRIP, CleanupMode.WHITESPACE,
+				CleanupMode.VERBATIM, CleanupMode.SCISSORS,
+				CleanupMode.DEFAULT };
+		for (int i = 0; i < values.length; i++) {
+			CommitConfig cfg = parse("[commit]\n\tcleanup = " + values[i]);
+			assertEquals("Unexpected clean-up mode", expected[i],
+					cfg.getCleanupMode());
+		}
+	}
+
+	@Test
+	public void testResolve() throws Exception {
+		String[] values = { "strip", "whitespace", "verbatim", "scissors",
+				"default" };
+		CleanupMode[] expected = { CleanupMode.STRIP, CleanupMode.WHITESPACE,
+				CleanupMode.VERBATIM, CleanupMode.SCISSORS,
+				CleanupMode.DEFAULT };
+		for (int i = 0; i < values.length; i++) {
+			CommitConfig cfg = parse("[commit]\n\tcleanup = " + values[i]);
+			for (CleanupMode mode : CleanupMode.values()) {
+				for (int j = 0; j < 2; j++) {
+					CleanupMode resolved = cfg.resolve(mode, j == 0);
+					if (mode != CleanupMode.DEFAULT) {
+						assertEquals("Clean-up mode should be unchanged", mode,
+								resolved);
+					} else if (i + 1 < values.length) {
+						assertEquals("Unexpected clean-up mode", expected[i],
+								resolved);
+					} else {
+						assertEquals("Unexpected clean-up mode",
+								j == 0 ? CleanupMode.STRIP
+										: CleanupMode.WHITESPACE,
+								resolved);
+					}
+				}
+			}
+		}
+	}
+
+	@Test
+	public void testCleanDefaultThrows() throws Exception {
+		assertThrows(IllegalArgumentException.class, () -> CommitConfig
+				.cleanText("Whatever", CleanupMode.DEFAULT, '#'));
+	}
+
+	@Test
+	public void testCleanVerbatim() throws Exception {
+		String message = "\n  \nWhatever  \n\n\n# A comment\n\nMore\t \n\n\n";
+		assertEquals("Unexpected message change", message,
+				CommitConfig.cleanText(message, CleanupMode.VERBATIM, '#'));
+	}
+
+	@Test
+	public void testCleanWhitespace() throws Exception {
+		String message = "\n  \nWhatever  \n\n\n# A comment\n\nMore\t \n\n\n";
+		assertEquals("Unexpected message change",
+				"Whatever\n\n# A comment\n\nMore\n",
+				CommitConfig.cleanText(message, CleanupMode.WHITESPACE, '#'));
+	}
+
+	@Test
+	public void testCleanStrip() throws Exception {
+		String message = "\n  \nWhatever  \n\n\n# A comment\n\nMore\t \n\n\n";
+		assertEquals("Unexpected message change", "Whatever\n\nMore\n",
+				CommitConfig.cleanText(message, CleanupMode.STRIP, '#'));
+	}
+
+	@Test
+	public void testCleanStripCustomChar() throws Exception {
+		String message = "\n  \nWhatever  \n\n\n# Not a comment\n\n   <A comment\nMore\t \n\n\n";
+		assertEquals("Unexpected message change",
+				"Whatever\n\n# Not a comment\n\nMore\n",
+				CommitConfig.cleanText(message, CleanupMode.STRIP, '<'));
+	}
+
+	@Test
+	public void testCleanScissors() throws Exception {
+		String message = "\n  \nWhatever  \n\n\n# Not a comment\n\n   <A comment\nMore\t \n\n\n"
+				+ "# ------------------------ >8 ------------------------\n"
+				+ "More\nMore\n";
+		assertEquals("Unexpected message change",
+				"Whatever\n\n# Not a comment\n\n   <A comment\nMore\n",
+				CommitConfig.cleanText(message, CleanupMode.SCISSORS, '#'));
+	}
+
+	@Test
+	public void testCleanScissorsCustomChar() throws Exception {
+		String message = "\n  \nWhatever  \n\n\n# Not a comment\n\n   <A comment\nMore\t \n\n\n"
+				+ "< ------------------------ >8 ------------------------\n"
+				+ "More\nMore\n";
+		assertEquals("Unexpected message change",
+				"Whatever\n\n# Not a comment\n\n   <A comment\nMore\n",
+				CommitConfig.cleanText(message, CleanupMode.SCISSORS, '<'));
+	}
+
+	@Test
+	public void testCleanScissorsAtTop() throws Exception {
+		String message = "# ------------------------ >8 ------------------------\n"
+				+ "\n  \nWhatever  \n\n\n# Not a comment\n\n   <A comment\nMore\t \n\n\n"
+				+ "More\nMore\n";
+		assertEquals("Unexpected message change", "",
+				CommitConfig.cleanText(message, CleanupMode.SCISSORS, '#'));
+	}
+
+	@Test
+	public void testCleanScissorsNoScissor() throws Exception {
+		String message = "\n  \nWhatever  \n\n\n# A comment\n\nMore\t \n\n\n";
+		assertEquals("Unexpected message change",
+				"Whatever\n\n# A comment\n\nMore\n",
+				CommitConfig.cleanText(message, CleanupMode.SCISSORS, '#'));
+	}
+
+	@Test
+	public void testCleanScissorsNoScissor2() throws Exception {
+		String message = "Text\n"
+				+ "## ------------------------ >8 ------------------------\n"
+				+ "More\nMore\n";
+		assertEquals("Unexpected message change", message,
+				CommitConfig.cleanText(message, CleanupMode.SCISSORS, '#'));
+	}
+
+	@Test
+	public void testCleanScissorsNoScissor3() throws Exception {
+		String message = "Text\n"
+				// Wrong number of dashes
+				+ "# ----------------------- >8 ------------------------\n"
+				+ "More\nMore\n";
+		assertEquals("Unexpected message change", message,
+				CommitConfig.cleanText(message, CleanupMode.SCISSORS, '#'));
+	}
+
+	@Test
+	public void testCleanScissorsAtEnd() throws Exception {
+		String message = "Text\n"
+				+ "# ------------------------ >8 ------------------------\n";
+		assertEquals("Unexpected message change", "Text\n",
+				CommitConfig.cleanText(message, CleanupMode.SCISSORS, '#'));
+	}
+
+	private static CommitConfig parse(String content)
+			throws ConfigInvalidException {
+		Config c = new Config();
+		c.fromText(content);
+		return c.get(CommitConfig.KEY);
+	}
+
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/CommitTemplateConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/CommitTemplateConfigTest.java
index 6dbe30a..42bafb6 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/CommitTemplateConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/CommitTemplateConfigTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 SAP SE and others
+ * Copyright (C) 2021, 2022 SAP SE 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
@@ -47,8 +47,7 @@
 				".tmp", new File(homeDir));
 		tempFileInHomeDirectory.deleteOnExit();
 		JGitTestUtil.write(tempFileInHomeDirectory, templateContent);
-		String expectedTemplatePath = tempFileInHomeDirectory.getPath()
-				.replace(homeDir, "~");
+		String expectedTemplatePath = "~/" + tempFileInHomeDirectory.getName();
 		config = ConfigTest
 				.parse("[commit]\n\ttemplate = " + expectedTemplatePath + "\n");
 		String templatePath = config.get(CommitConfig.KEY)
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
index 9b82c2a..a85a4f4 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
@@ -1488,7 +1488,8 @@
 		String expectedTemplatePath = tempFile.getPath();
 
 		Config config = parse(
-				"[commit]\n\ttemplate = " + expectedTemplatePath + "\n");
+				"[commit]\n\ttemplate = "
+						+ Config.escapeValue(expectedTemplatePath) + "\n");
 
 		String templatePath = config.get(CommitConfig.KEY)
 				.getCommitTemplatePath();
@@ -1537,7 +1538,8 @@
 		JGitTestUtil.write(tempFile, templateContent);
 		String expectedTemplatePath = tempFile.getPath();
 		config = parse("[i18n]\n\tcommitEncoding = utf-8\n"
-				+ "[commit]\n\ttemplate = " + expectedTemplatePath + "\n");
+				+ "[commit]\n\ttemplate = "
+				+ Config.escapeValue(expectedTemplatePath) + "\n");
 		assertEquals(templateContent,
 				config.get(CommitConfig.KEY).getCommitTemplateContent(repo));
 		String commitEncoding = config.get(CommitConfig.KEY)
@@ -1556,7 +1558,8 @@
 		String templateContent = "content of the template";
 		JGitTestUtil.write(tempFile, templateContent);
 		config = parse("[i18n]\n\tcommitEncoding = invalidEcoding\n"
-				+ "[commit]\n\ttemplate = " + tempFile.getPath() + "\n");
+				+ "[commit]\n\ttemplate = "
+				+ Config.escapeValue(tempFile.getPath()) + "\n");
 		config.get(CommitConfig.KEY).getCommitTemplateContent(repo);
 	}
 
@@ -1570,7 +1573,7 @@
 		String templateContent = "content of the template";
 		JGitTestUtil.write(tempFile, templateContent);
 		// commit message encoding
-		String expectedTemplatePath = "/nonExistingTemplate";
+		String expectedTemplatePath = "~/nonExistingTemplate";
 		config = parse("[commit]\n\ttemplate = " + expectedTemplatePath + "\n");
 		String templatePath = config.get(CommitConfig.KEY)
 				.getCommitTemplatePath();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java
index af8a58f..0fafcd6 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java
@@ -2,7 +2,7 @@
  * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
  * Copyright (C) 2008-2011, Shawn O. Pearce <spearce@spearce.org>
  * Copyright (C) 2008-2011, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2010, 2020 Christian Halstrick <christian.halstrick@sap.com> and others
+ * Copyright (C) 2010, 2022 Christian Halstrick <christian.halstrick@sap.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
@@ -242,6 +242,7 @@
 		ListenerHandle handle = null;
 		try (Git git = new Git(db);
 				TestRepository<Repository> db_t = new TestRepository<>(db)) {
+			db.incrementOpen();
 			handle = db.getListenerList()
 					.addWorkingTreeModifiedListener(recorder);
 			BranchBuilder master = db_t.branch("master");
@@ -261,6 +262,7 @@
 			String attributes) throws Exception {
 		try (Git git = new Git(db);
 				TestRepository<Repository> db_t = new TestRepository<>(db)) {
+			db.incrementOpen();
 			BranchBuilder master = db_t.branch("master");
 			master.commit().add("f", inIndex).message("m0").create();
 			if (!StringUtils.isEmptyOrNull(attributes)) {
@@ -313,8 +315,9 @@
 
 	@Test
 	public void testCheckoutWithLFAuto() throws Exception {
-		checkoutLineEndings("first line\nsecond line\n",
-				"first line\nsecond line\n", "f text=auto");
+		String expected = String.format("first line%nsecond line%n");
+		checkoutLineEndings("first line\nsecond line\n", expected,
+				"f text=auto");
 	}
 
 	@Test
@@ -325,9 +328,9 @@
 
 	@Test
 	public void testCheckoutWithLFAutoEolNative() throws Exception {
+		String expected = String.format("first line%nsecond line%n");
 		checkoutLineEndings(
-				"first line\nsecond line\n", "first line\nsecond line\n"
-						.replaceAll("\n", System.lineSeparator()),
+				"first line\nsecond line\n", expected,
 				"f text=auto eol=native");
 	}
 
@@ -2064,6 +2067,7 @@
 	public void testCheckoutWithEmptyIndexDoesntOverwrite() throws Exception {
 		try (Git git = new Git(db);
 				TestRepository<Repository> db_t = new TestRepository<>(db)) {
+			db.incrementOpen();
 			// prepare the commits
 			BranchBuilder master = db_t.branch("master");
 			RevCommit mergeCommit = master.commit()
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0001_PersonIdentTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PersonIdentTest.java
similarity index 75%
rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0001_PersonIdentTest.java
rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PersonIdentTest.java
index e9bab7c..97da175 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0001_PersonIdentTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PersonIdentTest.java
@@ -13,12 +13,14 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import java.time.Instant;
+import java.time.ZoneId;
 import java.util.Date;
 import java.util.TimeZone;
 
 import org.junit.Test;
 
-public class T0001_PersonIdentTest {
+public class PersonIdentTest {
 
 	@Test
 	public void test001_NewIdent() {
@@ -42,6 +44,34 @@
 				p.toExternalString());
 	}
 
+	@Test
+	public void testNewIdentInstant() {
+		PersonIdent p = new PersonIdent("A U Thor", "author@example.com",
+				Instant.ofEpochMilli(1142878501000L),
+				ZoneId.of("America/New_York"));
+		assertEquals("A U Thor", p.getName());
+		assertEquals("author@example.com", p.getEmailAddress());
+		assertEquals(Instant.ofEpochMilli(1142878501000L),
+				p.getWhenAsInstant());
+		assertEquals("A U Thor <author@example.com> 1142878501 -0500",
+				p.toExternalString());
+		assertEquals(ZoneId.of("GMT-05:00"), p.getZoneId());
+	}
+
+	@Test
+	public void testNewIdentInstant2() {
+		final PersonIdent p = new PersonIdent("A U Thor", "author@example.com",
+				Instant.ofEpochMilli(1142878501000L),
+				ZoneId.of("Asia/Kolkata"));
+		assertEquals("A U Thor", p.getName());
+		assertEquals("author@example.com", p.getEmailAddress());
+		assertEquals(Instant.ofEpochMilli(1142878501000L),
+				p.getWhenAsInstant());
+		assertEquals("A U Thor <author@example.com> 1142878501 +0530",
+				p.toExternalString());
+		assertEquals(ZoneId.of("GMT+05:30"), p.getZoneId());
+	}
+
 	@SuppressWarnings("unused")
 	@Test(expected = IllegalArgumentException.class)
 	public void nullForNameShouldThrowIllegalArgumentException() {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeMessageFormatterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeMessageFormatterTest.java
index dedb56c..a2576cc 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeMessageFormatterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeMessageFormatterTest.java
@@ -13,6 +13,7 @@
 
 import java.io.IOException;
 import java.util.Arrays;
+import java.util.List;
 
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectIdRef;
@@ -157,8 +158,8 @@
 	public void testFormatWithConflictsNoFooter() {
 		String originalMessage = "Header Line\n\nCommit body\n";
 		String message = formatter.formatWithConflicts(originalMessage,
-				Arrays.asList(new String[] { "path1" }));
-		assertEquals("Header Line\n\nCommit body\n\nConflicts:\n\tpath1\n",
+				List.of("path1"), '#');
+		assertEquals("Header Line\n\nCommit body\n\n# Conflicts:\n#\tpath1\n",
 				message);
 	}
 
@@ -166,8 +167,17 @@
 	public void testFormatWithConflictsNoFooterNoLineBreak() {
 		String originalMessage = "Header Line\n\nCommit body";
 		String message = formatter.formatWithConflicts(originalMessage,
-				Arrays.asList(new String[] { "path1" }));
-		assertEquals("Header Line\n\nCommit body\n\nConflicts:\n\tpath1\n",
+				List.of("path1"), '#');
+		assertEquals("Header Line\n\nCommit body\n\n# Conflicts:\n#\tpath1\n",
+				message);
+	}
+
+	@Test
+	public void testFormatWithConflictsCustomCharacter() {
+		String originalMessage = "Header Line\n\nCommit body";
+		String message = formatter.formatWithConflicts(originalMessage,
+				List.of("path1"), ';');
+		assertEquals("Header Line\n\nCommit body\n\n; Conflicts:\n;\tpath1\n",
 				message);
 	}
 
@@ -176,9 +186,9 @@
 		String originalMessage = "Header Line\n\nCommit body\n\nChangeId:"
 				+ " I123456789123456789123456789123456789\nBug:1234567\n";
 		String message = formatter.formatWithConflicts(originalMessage,
-				Arrays.asList(new String[] { "path1" }));
+				List.of("path1"), '#');
 		assertEquals(
-				"Header Line\n\nCommit body\n\nConflicts:\n\tpath1\n\n"
+				"Header Line\n\nCommit body\n\n# Conflicts:\n#\tpath1\n\n"
 						+ "ChangeId: I123456789123456789123456789123456789\nBug:1234567\n",
 				message);
 	}
@@ -188,9 +198,9 @@
 		String originalMessage = "Header Line\n\nCommit body\nBug:1234567\nMore Body\n\nChangeId:"
 				+ " I123456789123456789123456789123456789\nBug:1234567\n";
 		String message = formatter.formatWithConflicts(originalMessage,
-				Arrays.asList(new String[] { "path1" }));
+				List.of("path1"), '#');
 		assertEquals(
-				"Header Line\n\nCommit body\nBug:1234567\nMore Body\n\nConflicts:\n\tpath1\n\n"
+				"Header Line\n\nCommit body\nBug:1234567\nMore Body\n\n# Conflicts:\n#\tpath1\n\n"
 						+ "ChangeId: I123456789123456789123456789123456789\nBug:1234567\n",
 				message);
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergerTest.java
index dd8573d..cbacaed 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergerTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergerTest.java
@@ -1810,6 +1810,7 @@
 	private String readBlob(ObjectId treeish, String path) throws Exception {
 		try (TestRepository<?> tr = new TestRepository<>(db);
 				RevWalk rw = tr.getRevWalk()) {
+			db.incrementOpen();
 			RevTree tree = rw.parseTree(treeish);
 			RevObject obj = tr.get(tree, path);
 			if (obj == null) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java
index ea994f0..f446d07 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java
@@ -217,7 +217,6 @@
 				assertEqualsFile(modulesGitDir, subRepo.getDirectory());
 				assertEqualsFile(new File(db.getWorkTree(), path),
 						subRepo.getWorkTree());
-				subRepo.close();
 				assertFalse(gen.next());
 			}
 		}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java
index 054eb9c..bb62a0d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java
@@ -80,6 +80,7 @@
 		// Then we clone a new repo from that bundle and do a simple test. This
 		// makes sure we could read the bundle we created.
 		Repository newRepo = createBareRepository();
+		addRepoToClose(newRepo);
 		FetchResult fetchResult = fetchFromBundle(newRepo, bundle);
 		Ref advertisedRef = fetchResult
 				.getAdvertisedRef("refs/heads/firstcommit");
@@ -116,6 +117,7 @@
 		// makes sure
 		// we could read the bundle we created.
 		Repository newRepo = createBareRepository();
+		addRepoToClose(newRepo);
 		FetchResult fetchResult = fetchFromBundle(newRepo, bundle);
 		Ref advertisedRef = fetchResult.getAdvertisedRef("refs/heads/aa");
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PackParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PackParserTest.java
index 60b8098..93bedb3 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PackParserTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PackParserTest.java
@@ -110,6 +110,7 @@
 	public void testTinyThinPack() throws Exception {
 		RevBlob a;
 		try (TestRepository d = new TestRepository<Repository>(db)) {
+			db.incrementOpen();
 			a = d.blob("a");
 		}
 
@@ -132,6 +133,7 @@
 	public void testPackWithDuplicateBlob() throws Exception {
 		final byte[] data = Constants.encode("0123456789abcdefg");
 		try (TestRepository<Repository> d = new TestRepository<>(db)) {
+			db.incrementOpen();
 			assertTrue(db.getObjectDatabase().has(d.blob(data)));
 		}
 
@@ -151,6 +153,7 @@
 	public void testPackWithTrailingGarbage() throws Exception {
 		RevBlob a;
 		try (TestRepository d = new TestRepository<Repository>(db)) {
+			db.incrementOpen();
 			a = d.blob("a");
 		}
 
@@ -180,6 +183,7 @@
 	public void testMaxObjectSizeFullBlob() throws Exception {
 		final byte[] data = Constants.encode("0123456789");
 		try (TestRepository d = new TestRepository<Repository>(db)) {
+			db.incrementOpen();
 			d.blob(data);
 		}
 
@@ -213,6 +217,7 @@
 	public void testMaxObjectSizeDeltaBlock() throws Exception {
 		RevBlob a;
 		try (TestRepository d = new TestRepository<Repository>(db)) {
+			db.incrementOpen();
 			a = d.blob("a");
 		}
 
@@ -246,6 +251,7 @@
 	public void testMaxObjectSizeDeltaResultSize() throws Exception {
 		RevBlob a;
 		try (TestRepository d = new TestRepository<Repository>(db)) {
+			db.incrementOpen();
 			a = d.blob("0123456789");
 		}
 
@@ -278,6 +284,7 @@
 	public void testNonMarkingInputStream() throws Exception {
 		RevBlob a;
 		try (TestRepository d = new TestRepository<Repository>(db)) {
+			db.incrementOpen();
 			a = d.blob("a");
 		}
 
@@ -318,6 +325,7 @@
 	public void testDataAfterPackFooterSingleRead() throws Exception {
 		RevBlob a;
 		try (TestRepository d = new TestRepository<Repository>(db)) {
+			db.incrementOpen();
 			a = d.blob("a");
 		}
 
@@ -379,6 +387,7 @@
 		final byte[] data = Constants.encode("a");
 		RevBlob b;
 		try (TestRepository d = new TestRepository<Repository>(db)) {
+			db.incrementOpen();
 			b = d.blob(data);
 		}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConfigTest.java
index 6109d6c..cbc1d54 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConfigTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017, David Pursehouse <david.pursehouse@gmail.com> and others
+ * Copyright (C) 2017, 2022 David Pursehouse <david.pursehouse@gmail.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
@@ -14,10 +14,13 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.transport.PushConfig.PushDefault;
 import org.eclipse.jgit.transport.PushConfig.PushRecurseSubmodulesMode;
 import org.junit.Test;
 
 public class PushConfigTest {
+
 	@Test
 	public void pushRecurseSubmoduleMatch() throws Exception {
 		assertTrue(PushRecurseSubmodulesMode.CHECK.matchConfigValue("check"));
@@ -52,4 +55,59 @@
 		assertEquals("check", PushRecurseSubmodulesMode.CHECK.toConfigValue());
 		assertEquals("false", PushRecurseSubmodulesMode.NO.toConfigValue());
 	}
+
+	@Test
+	public void pushDefaultMatch() throws Exception {
+		assertTrue(PushDefault.NOTHING.matchConfigValue("nothing"));
+		assertTrue(PushDefault.NOTHING.matchConfigValue("NOTHING"));
+		assertTrue(PushDefault.CURRENT.matchConfigValue("current"));
+		assertTrue(PushDefault.CURRENT.matchConfigValue("CURRENT"));
+		assertTrue(PushDefault.UPSTREAM.matchConfigValue("upstream"));
+		assertTrue(PushDefault.UPSTREAM.matchConfigValue("UPSTREAM"));
+		assertTrue(PushDefault.UPSTREAM.matchConfigValue("tracking"));
+		assertTrue(PushDefault.UPSTREAM.matchConfigValue("TRACKING"));
+		assertTrue(PushDefault.SIMPLE.matchConfigValue("simple"));
+		assertTrue(PushDefault.SIMPLE.matchConfigValue("SIMPLE"));
+		assertTrue(PushDefault.MATCHING.matchConfigValue("matching"));
+		assertTrue(PushDefault.MATCHING.matchConfigValue("MATCHING"));
+	}
+
+	@Test
+	public void pushDefaultNoMatch() throws Exception {
+		assertFalse(PushDefault.NOTHING.matchConfigValue("n"));
+		assertFalse(PushDefault.CURRENT.matchConfigValue(""));
+		assertFalse(PushDefault.UPSTREAM.matchConfigValue("track"));
+	}
+
+	@Test
+	public void pushDefaultToConfigValue() throws Exception {
+		assertEquals("nothing", PushDefault.NOTHING.toConfigValue());
+		assertEquals("current", PushDefault.CURRENT.toConfigValue());
+		assertEquals("upstream", PushDefault.UPSTREAM.toConfigValue());
+		assertEquals("simple", PushDefault.SIMPLE.toConfigValue());
+		assertEquals("matching", PushDefault.MATCHING.toConfigValue());
+	}
+
+	@Test
+	public void testEmptyConfig() throws Exception {
+		PushConfig cfg = parse("");
+		assertEquals(PushRecurseSubmodulesMode.NO, cfg.getRecurseSubmodules());
+		assertEquals(PushDefault.SIMPLE, cfg.getPushDefault());
+	}
+
+	@Test
+	public void testConfig() throws Exception {
+		PushConfig cfg = parse(
+				"[push]\n\tdefault = tracking\n\trecurseSubmodules = on-demand\n");
+		assertEquals(PushRecurseSubmodulesMode.ON_DEMAND,
+				cfg.getRecurseSubmodules());
+		assertEquals(PushDefault.UPSTREAM, cfg.getPushDefault());
+	}
+
+	private static PushConfig parse(String content) throws Exception {
+		Config c = new Config();
+		c.fromText(content);
+		return c.get(PushConfig::new);
+	}
+
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java
index 6928859..2e8b30f 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java
@@ -14,14 +14,19 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 
+import org.eclipse.jgit.api.errors.AbortedByHookException;
 import org.eclipse.jgit.errors.NotSupportedException;
 import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.hooks.PrePushHook;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectIdRef;
 import org.eclipse.jgit.lib.ProgressMonitor;
@@ -31,6 +36,7 @@
 import org.eclipse.jgit.lib.TextProgressMonitor;
 import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
 import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
+import org.eclipse.jgit.util.io.NullOutputStream;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -220,7 +226,17 @@
 						.fromString("0000000000000000000000000000000000000001"));
 		final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
 				ObjectId.fromString("ac7e7e44c1885efb472ad54a78327d66bfc4ecef"));
-		testOneUpdateStatus(rru, ref, Status.REJECTED_REMOTE_CHANGED, null);
+		try (ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+				PrintStream out = new PrintStream(bytes, true,
+						StandardCharsets.UTF_8);
+				PrintStream err = new PrintStream(NullOutputStream.INSTANCE)) {
+			MockPrePushHook hook = new MockPrePushHook(db, out, err);
+			testOneUpdateStatus(rru, ref, Status.REJECTED_REMOTE_CHANGED, null,
+					hook);
+			out.flush();
+			String result = new String(bytes.toString(StandardCharsets.UTF_8));
+			assertEquals("", result);
+		}
 	}
 
 	/**
@@ -256,10 +272,22 @@
 		refUpdates.add(rruOk);
 		refUpdates.add(rruReject);
 		advertisedRefs.add(refToChange);
-		executePush();
-		assertEquals(Status.OK, rruOk.getStatus());
-		assertTrue(rruOk.isFastForward());
-		assertEquals(Status.NON_EXISTING, rruReject.getStatus());
+		try (ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+				PrintStream out = new PrintStream(bytes, true,
+						StandardCharsets.UTF_8);
+				PrintStream err = new PrintStream(NullOutputStream.INSTANCE)) {
+			MockPrePushHook hook = new MockPrePushHook(db, out, err);
+			executePush(hook);
+			assertEquals(Status.OK, rruOk.getStatus());
+			assertTrue(rruOk.isFastForward());
+			assertEquals(Status.NON_EXISTING, rruReject.getStatus());
+			out.flush();
+			String result = new String(bytes.toString(StandardCharsets.UTF_8));
+			assertEquals(
+					"null 0000000000000000000000000000000000000000 "
+							+ "refs/heads/master 2c349335b7f797072cf729c4f3bb0914ecb6dec9\n",
+					result);
+		}
 	}
 
 	/**
@@ -346,10 +374,18 @@
 			final Ref advertisedRef, final Status expectedStatus,
 			Boolean fastForward) throws NotSupportedException,
 			TransportException {
+		return testOneUpdateStatus(rru, advertisedRef, expectedStatus,
+				fastForward, null);
+	}
+
+	private PushResult testOneUpdateStatus(final RemoteRefUpdate rru,
+			final Ref advertisedRef, final Status expectedStatus,
+			Boolean fastForward, PrePushHook hook)
+			throws NotSupportedException, TransportException {
 		refUpdates.add(rru);
 		if (advertisedRef != null)
 			advertisedRefs.add(advertisedRef);
-		final PushResult result = executePush();
+		final PushResult result = executePush(hook);
 		assertEquals(expectedStatus, rru.getStatus());
 		if (fastForward != null)
 			assertEquals(fastForward, Boolean.valueOf(rru.isFastForward()));
@@ -358,7 +394,12 @@
 
 	private PushResult executePush() throws NotSupportedException,
 			TransportException {
-		process = new PushProcess(transport, refUpdates);
+		return executePush(null);
+	}
+
+	private PushResult executePush(PrePushHook hook)
+			throws NotSupportedException, TransportException {
+		process = new PushProcess(transport, refUpdates, hook);
 		return process.execute(new TextProgressMonitor());
 	}
 
@@ -416,4 +457,20 @@
 			}
 		}
 	}
+
+	private static class MockPrePushHook extends PrePushHook {
+
+		private final PrintStream output;
+
+		public MockPrePushHook(Repository repo, PrintStream out,
+				PrintStream err) {
+			super(repo, out, err);
+			output = out;
+		}
+
+		@Override
+		protected void doRun() throws AbortedByHookException, IOException {
+			output.print(getStdinArgs());
+		}
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java
index d1e5446..a91bc95 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java
@@ -73,11 +73,14 @@
 		super.setUp();
 
 		src = createBareRepository();
+		addRepoToClose(src);
 		dst = createBareRepository();
+		addRepoToClose(dst);
 
 		// Fill dst with a some common history.
 		//
 		try (TestRepository<Repository> d = new TestRepository<>(dst)) {
+			dst.incrementOpen();
 			a = d.blob("a");
 			A = d.commit(d.tree(d.file("a", a)));
 			B = d.commit().parent(A).create();
@@ -106,9 +109,6 @@
 				dst.getDirectory()) {
 			@Override
 			ReceivePack createReceivePack(Repository db) {
-				db.close();
-				dst.incrementOpen();
-
 				final ReceivePack rp = super.createReceivePack(dst);
 				rp.setAdvertiseRefsHook(new HidePrivateHook());
 				return rp;
@@ -136,8 +136,6 @@
 				dst.getDirectory()) {
 			@Override
 			ReceivePack createReceivePack(Repository db) {
-				dst.incrementOpen();
-
 				ReceivePack rp = super.createReceivePack(dst);
 				rp.setAdvertiseRefsHook(new AdvertiseRefsHook() {
 					@Override
@@ -173,9 +171,6 @@
 		return new TransportLocal(src, uriOf(dst), dst.getDirectory()) {
 			@Override
 			ReceivePack createReceivePack(Repository db) {
-				db.close();
-				dst.incrementOpen();
-
 				final ReceivePack rp = super.createReceivePack(dst);
 				rp.setCheckReceivedObjects(true);
 				rp.setCheckReferencedObjectsAreReachable(true);
@@ -211,6 +206,7 @@
 		// Now use b but in a different commit than what is hidden.
 		//
 		try (TestRepository<Repository> s = new TestRepository<>(src)) {
+			src.incrementOpen();
 			RevCommit N = s.commit().parent(B).add("q", b).create();
 			s.update(R_MASTER, N);
 
@@ -228,7 +224,6 @@
 			try (TransportLocal t = newTransportLocalWithStrictValidation()) {
 				t.setPushThin(true);
 				r = t.push(PM, Collections.singleton(u));
-				dst.close();
 			}
 
 			assertNotNull("have result", r);
@@ -290,6 +285,7 @@
 	public void testUsingHiddenDeltaBaseFails() throws Exception {
 		byte[] delta = { 0x1, 0x1, 0x1, 'c' };
 		try (TestRepository<Repository> s = new TestRepository<>(src)) {
+			src.incrementOpen();
 			RevCommit N = s.commit().parent(B)
 					.add("q",
 							s.blob(BinaryDelta.apply(
@@ -348,6 +344,7 @@
 		// Try to use the 'b' blob that is hidden.
 		//
 		try (TestRepository<Repository> s = new TestRepository<>(src)) {
+			src.incrementOpen();
 			RevCommit N = s.commit().parent(B).add("q", s.blob("b")).create();
 
 			// But don't include it in the pack.
@@ -401,6 +398,7 @@
 		// Try to use the 'n' blob that is not on the server.
 		//
 		try (TestRepository<Repository> s = new TestRepository<>(src)) {
+			src.incrementOpen();
 			RevBlob n = s.blob("n");
 			RevCommit N = s.commit().parent(B).add("q", n).create();
 
@@ -491,6 +489,7 @@
 				.toString();
 
 		try (TestRepository<Repository> s = new TestRepository<>(src)) {
+			src.incrementOpen();
 			RevBlob blob = s.blob(fakeGitmodules);
 			RevCommit N = s.commit().parent(B).add(".gitmodules", blob)
 					.create();
@@ -517,6 +516,7 @@
 	@Test
 	public void testUsingUnknownTreeFails() throws Exception {
 		try (TestRepository<Repository> s = new TestRepository<>(src)) {
+			src.incrementOpen();
 			RevCommit N = s.commit().parent(B).add("q", s.blob("a")).create();
 			RevTree t = s.parseBody(N).getTree();
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java
index 5569bca..b56308c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java
@@ -466,4 +466,18 @@
 		assertTrue(a.matchSource("refs/heads/master"));
 		assertNull(a.getDestination());
 	}
+
+	@Test
+	public void matching() {
+		RefSpec a = new RefSpec(":");
+		assertTrue(a.isMatching());
+		assertFalse(a.isForceUpdate());
+	}
+
+	@Test
+	public void matchingForced() {
+		RefSpec a = new RefSpec("+:");
+		assertTrue(a.isMatching());
+		assertTrue(a.isForceUpdate());
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FilterCommandsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FilterCommandsTest.java
index 36f94fb..89d31c3 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FilterCommandsTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FilterCommandsTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016, Christian Halstrick <christian.halstrick@sap.com> and others
+ * Copyright (C) 2016, 2022 Christian Halstrick <christian.halstrick@sap.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
@@ -10,12 +10,17 @@
 package org.eclipse.jgit.util;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.HashSet;
+import java.util.Set;
 
 import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.MergeResult;
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.attributes.FilterCommand;
 import org.eclipse.jgit.attributes.FilterCommandFactory;
@@ -86,6 +91,14 @@
 		secondCommit = git.commit().setMessage("Second commit").call();
 	}
 
+	@Override
+	public void tearDown() throws Exception {
+		Set<String> existingFilters = new HashSet<>(
+				FilterCommandRegistry.getRegisteredFilterCommands());
+		existingFilters.forEach(FilterCommandRegistry::unregister);
+		super.tearDown();
+	}
+
 	@Test
 	public void testBuiltinCleanFilter()
 			throws IOException, GitAPIException {
@@ -217,4 +230,133 @@
 		config.save();
 	}
 
+	@Test
+	public void testBranchSwitch() throws Exception {
+		String builtinCommandPrefix = "jgit://builtin/test/";
+		FilterCommandRegistry.register(builtinCommandPrefix + "smudge",
+				new TestCommandFactory('s'));
+		FilterCommandRegistry.register(builtinCommandPrefix + "clean",
+				new TestCommandFactory('c'));
+		StoredConfig config = git.getRepository().getConfig();
+		config.setString("filter", "test", "smudge",
+				builtinCommandPrefix + "smudge");
+		config.setString("filter", "test", "clean",
+				builtinCommandPrefix + "clean");
+		config.save();
+		// We're on the test branch
+		File aFile = writeTrashFile("a.txt", "a");
+		writeTrashFile(".gitattributes", "a.txt filter=test");
+		File cFile = writeTrashFile("cc/c.txt", "C");
+		writeTrashFile("cc/.gitattributes", "c.txt filter=test");
+		git.add().addFilepattern(".").call();
+		git.commit().setMessage("On test").call();
+		git.checkout().setName("master").call();
+		git.branchCreate().setName("other").call();
+		git.checkout().setName("other").call();
+		writeTrashFile("b.txt", "b");
+		writeTrashFile(".gitattributes", "b.txt filter=test");
+		git.add().addFilepattern(".").call();
+		git.commit().setMessage("On other").call();
+		git.checkout().setName("test").call();
+		checkFile(aFile, "scsa");
+		checkFile(cFile, "scsC");
+	}
+
+	@Test
+	public void testCheckoutSingleFile() throws Exception {
+		String builtinCommandPrefix = "jgit://builtin/test/";
+		FilterCommandRegistry.register(builtinCommandPrefix + "smudge",
+				new TestCommandFactory('s'));
+		FilterCommandRegistry.register(builtinCommandPrefix + "clean",
+				new TestCommandFactory('c'));
+		StoredConfig config = git.getRepository().getConfig();
+		config.setString("filter", "test", "smudge",
+				builtinCommandPrefix + "smudge");
+		config.setString("filter", "test", "clean",
+				builtinCommandPrefix + "clean");
+		config.save();
+		// We're on the test branch
+		File aFile = writeTrashFile("a.txt", "a");
+		File attributes = writeTrashFile(".gitattributes", "a.txt filter=test");
+		git.add().addFilepattern(".").call();
+		git.commit().setMessage("On test").call();
+		git.checkout().setName("master").call();
+		git.branchCreate().setName("other").call();
+		git.checkout().setName("other").call();
+		writeTrashFile("b.txt", "b");
+		writeTrashFile(".gitattributes", "b.txt filter=test");
+		git.add().addFilepattern(".").call();
+		git.commit().setMessage("On other").call();
+		git.checkout().setName("master").call();
+		assertFalse(aFile.exists());
+		assertFalse(attributes.exists());
+		git.checkout().setStartPoint("test").addPath("a.txt").call();
+		checkFile(aFile, "scsa");
+	}
+
+	@Test
+	public void testCheckoutSingleFile2() throws Exception {
+		String builtinCommandPrefix = "jgit://builtin/test/";
+		FilterCommandRegistry.register(builtinCommandPrefix + "smudge",
+				new TestCommandFactory('s'));
+		FilterCommandRegistry.register(builtinCommandPrefix + "clean",
+				new TestCommandFactory('c'));
+		StoredConfig config = git.getRepository().getConfig();
+		config.setString("filter", "test", "smudge",
+				builtinCommandPrefix + "smudge");
+		config.setString("filter", "test", "clean",
+				builtinCommandPrefix + "clean");
+		config.save();
+		// We're on the test branch
+		File aFile = writeTrashFile("a.txt", "a");
+		File attributes = writeTrashFile(".gitattributes", "a.txt filter=test");
+		git.add().addFilepattern(".").call();
+		git.commit().setMessage("On test").call();
+		git.checkout().setName("master").call();
+		git.branchCreate().setName("other").call();
+		git.checkout().setName("other").call();
+		writeTrashFile("b.txt", "b");
+		writeTrashFile(".gitattributes", "b.txt filter=test");
+		git.add().addFilepattern(".").call();
+		git.commit().setMessage("On other").call();
+		git.checkout().setName("master").call();
+		assertFalse(aFile.exists());
+		assertFalse(attributes.exists());
+		writeTrashFile(".gitattributes", "");
+		git.checkout().setStartPoint("test").addPath("a.txt").call();
+		checkFile(aFile, "scsa");
+	}
+
+	@Test
+	public void testMerge() throws Exception {
+		String builtinCommandPrefix = "jgit://builtin/test/";
+		FilterCommandRegistry.register(builtinCommandPrefix + "smudge",
+				new TestCommandFactory('s'));
+		FilterCommandRegistry.register(builtinCommandPrefix + "clean",
+				new TestCommandFactory('c'));
+		StoredConfig config = git.getRepository().getConfig();
+		config.setString("filter", "test", "smudge",
+				builtinCommandPrefix + "smudge");
+		config.setString("filter", "test", "clean",
+				builtinCommandPrefix + "clean");
+		config.save();
+		// We're on the test branch. Set up two branches that are expected to
+		// merge cleanly.
+		File aFile = writeTrashFile("a.txt", "a");
+		writeTrashFile(".gitattributes", "a.txt filter=test");
+		git.add().addFilepattern(".").call();
+		RevCommit aCommit = git.commit().setMessage("On test").call();
+		git.checkout().setName("master").call();
+		assertFalse(aFile.exists());
+		git.branchCreate().setName("other").call();
+		git.checkout().setName("other").call();
+		writeTrashFile("b/b.txt", "b");
+		writeTrashFile("b/.gitattributes", "b.txt filter=test");
+		git.add().addFilepattern(".").call();
+		git.commit().setMessage("On other").call();
+		MergeResult result = git.merge().include(aCommit).call();
+		assertEquals(MergeResult.MergeStatus.MERGED, result.getMergeStatus());
+		checkFile(aFile, "scsa");
+	}
+
 }
diff --git a/org.eclipse.jgit.ui/META-INF/MANIFEST.MF b/org.eclipse.jgit.ui/META-INF/MANIFEST.MF
index 57a8bee..7baa215 100644
--- a/org.eclipse.jgit.ui/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ui/META-INF/MANIFEST.MF
@@ -4,14 +4,14 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.ui
 Bundle-SymbolicName: org.eclipse.jgit.ui
-Bundle-Version: 6.0.1.qualifier
+Bundle-Version: 6.1.1.qualifier
 Bundle-Vendor: %Bundle-Vendor
 Bundle-RequiredExecutionEnvironment: JavaSE-11
-Export-Package: org.eclipse.jgit.awtui;version="6.0.1"
-Import-Package: org.eclipse.jgit.errors;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.lib;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.nls;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.revplot;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.revwalk;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.transport;version="[6.0.1,6.1.0)",
- org.eclipse.jgit.util;version="[6.0.1,6.1.0)"
+Export-Package: org.eclipse.jgit.awtui;version="6.1.1"
+Import-Package: org.eclipse.jgit.errors;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.lib;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.nls;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.revplot;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.revwalk;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.transport;version="[6.1.1,6.2.0)",
+ org.eclipse.jgit.util;version="[6.1.1,6.2.0)"
diff --git a/org.eclipse.jgit.ui/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.ui/META-INF/SOURCE-MANIFEST.MF
index 335c157..75f3d21 100644
--- a/org.eclipse.jgit.ui/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.ui/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.ui - Sources
 Bundle-SymbolicName: org.eclipse.jgit.ui.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 6.0.1.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.ui;version="6.0.1.qualifier";roots="."
+Bundle-Version: 6.1.1.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.ui;version="6.1.1.qualifier";roots="."
diff --git a/org.eclipse.jgit.ui/pom.xml b/org.eclipse.jgit.ui/pom.xml
index c4a3e2a..125e120 100644
--- a/org.eclipse.jgit.ui/pom.xml
+++ b/org.eclipse.jgit.ui/pom.xml
@@ -19,7 +19,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.ui</artifactId>
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
new file mode 100644
index 0000000..811c360
--- /dev/null
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -0,0 +1,292 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<component id="org.eclipse.jgit" version="2">
+    <resource path="src/org/eclipse/jgit/api/GarbageCollectCommand.java" type="org.eclipse.jgit.api.GarbageCollectCommand">
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.13.3"/>
+                <message_argument value="setPackKeptObjects(boolean)"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/errors/NoRemoteRepositoryException.java" type="org.eclipse.jgit.errors.NoRemoteRepositoryException">
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.13.1"/>
+                <message_argument value="NoRemoteRepositoryException(URIish, String, Throwable)"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/errors/PackMismatchException.java" type="org.eclipse.jgit.errors.PackMismatchException">
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.9.1"/>
+                <message_argument value="isPermanent()"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.9.1"/>
+                <message_argument value="setPermanent(boolean)"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/lib/ConfigConstants.java" type="org.eclipse.jgit.lib.ConfigConstants">
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.13.2"/>
+                <message_argument value="CONFIG_KEY_BITMAP_EXCLUDED_REFS_PREFIXES"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.13.2"/>
+                <message_argument value="CONFIG_KEY_PRESERVE_OLD_PACKS"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.13.2"/>
+                <message_argument value="CONFIG_KEY_PRUNE_PRESERVED"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.13.2"/>
+                <message_argument value="CONFIG_KEY_SKIPHASH"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.13.2"/>
+                <message_argument value="SHA1_IMPLEMENTATION"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.13.3"/>
+                <message_argument value="CONFIG_KEY_PACK_KEPT_OBJECTS"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.13.3"/>
+                <message_argument value="CONFIG_REPACK_SECTION"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="6.1.1"/>
+                <message_argument value="CONFIG_KEY_TRUST_PACKED_REFS_STAT"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/lib/CoreConfig.java" type="org.eclipse.jgit.lib.CoreConfig$TrustPackedRefsStat">
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="6.1.1"/>
+                <message_argument value="TrustPackedRefsStat"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/lib/ObjectDatabase.java" type="org.eclipse.jgit.lib.ObjectDatabase">
+        <filter id="336695337">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.lib.ObjectDatabase"/>
+                <message_argument value="getApproximateObjectCount()"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/lib/Repository.java" type="org.eclipse.jgit.lib.Repository">
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.13.2"/>
+                <message_argument value="getReflogReader(Ref)"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/lib/TypedConfigGetter.java" type="org.eclipse.jgit.lib.TypedConfigGetter">
+        <filter id="403767336">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.lib.TypedConfigGetter"/>
+                <message_argument value="UNSET_INT"/>
+            </message_arguments>
+        </filter>
+        <filter id="403804204">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.lib.TypedConfigGetter"/>
+                <message_argument value="getIntInRange(Config, String, String, String, int, int, int)"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/merge/ResolveMerger.java" type="org.eclipse.jgit.merge.ResolveMerger">
+        <filter id="338792546">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.merge.ResolveMerger"/>
+                <message_argument value="addCheckoutMetadata(String, Attributes)"/>
+            </message_arguments>
+        </filter>
+        <filter id="338792546">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.merge.ResolveMerger"/>
+                <message_argument value="addToCheckout(String, DirCacheEntry, Attributes)"/>
+            </message_arguments>
+        </filter>
+        <filter id="338792546">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.merge.ResolveMerger"/>
+                <message_argument value="processEntry(CanonicalTreeParser, CanonicalTreeParser, CanonicalTreeParser, DirCacheBuildIterator, WorkingTreeIterator, boolean, Attributes)"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/storage/pack/PackConfig.java" type="org.eclipse.jgit.storage.pack.PackConfig">
+        <filter id="336658481">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.storage.pack.PackConfig"/>
+                <message_argument value="DEFAULT_BITMAP_EXCLUDED_REFS_PREFIXES"/>
+            </message_arguments>
+        </filter>
+        <filter id="336658481">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.storage.pack.PackConfig"/>
+                <message_argument value="DEFAULT_PACK_KEPT_OBJECTS"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.13.2"/>
+                <message_argument value="DEFAULT_BITMAP_EXCLUDED_REFS_PREFIXES"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.13.2"/>
+                <message_argument value="getBitmapExcludedRefsPrefixes()"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.13.2"/>
+                <message_argument value="setBitmapExcludedRefsPrefixes(String[])"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.13.3"/>
+                <message_argument value="DEFAULT_PACK_KEPT_OBJECTS"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.13.3"/>
+                <message_argument value="isPackKeptObjects()"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.13.3"/>
+                <message_argument value="setPackKeptObjects(boolean)"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/transport/AwsRequestSignerV4.java" type="org.eclipse.jgit.transport.AwsRequestSignerV4">
+        <filter id="1109393411">
+            <message_arguments>
+                <message_argument value="5.13.1"/>
+                <message_argument value="org.eclipse.jgit.transport.AwsRequestSignerV4"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/transport/BasePackPushConnection.java" type="org.eclipse.jgit.transport.BasePackPushConnection">
+        <filter id="338792546">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.transport.BasePackPushConnection"/>
+                <message_argument value="noRepository()"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/transport/PushConfig.java" type="org.eclipse.jgit.transport.PushConfig">
+        <filter id="338722907">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.transport.PushConfig"/>
+                <message_argument value="PushConfig()"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/util/HttpSupport.java" type="org.eclipse.jgit.util.HttpSupport">
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.13.1"/>
+                <message_argument value="urlEncode(String, boolean)"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/util/sha1/SHA1.java" type="org.eclipse.jgit.util.sha1.SHA1">
+        <filter id="337764418">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.util.sha1.SHA1"/>
+            </message_arguments>
+        </filter>
+        <filter id="421650549">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.util.sha1.SHA1"/>
+                <message_argument value="digest()"/>
+            </message_arguments>
+        </filter>
+        <filter id="421650549">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.util.sha1.SHA1"/>
+                <message_argument value="digest(MutableObjectId)"/>
+            </message_arguments>
+        </filter>
+        <filter id="421650549">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.util.sha1.SHA1"/>
+                <message_argument value="hasCollision()"/>
+            </message_arguments>
+        </filter>
+        <filter id="421650549">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.util.sha1.SHA1"/>
+                <message_argument value="reset()"/>
+            </message_arguments>
+        </filter>
+        <filter id="421650549">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.util.sha1.SHA1"/>
+                <message_argument value="setDetectCollision(boolean)"/>
+            </message_arguments>
+        </filter>
+        <filter id="421650549">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.util.sha1.SHA1"/>
+                <message_argument value="toObjectId()"/>
+            </message_arguments>
+        </filter>
+        <filter id="421650549">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.util.sha1.SHA1"/>
+                <message_argument value="update(byte)"/>
+            </message_arguments>
+        </filter>
+        <filter id="421650549">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.util.sha1.SHA1"/>
+                <message_argument value="update(byte[])"/>
+            </message_arguments>
+        </filter>
+        <filter id="421650549">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.util.sha1.SHA1"/>
+                <message_argument value="update(byte[], int, int)"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/util/sha1/SHA1.java" type="org.eclipse.jgit.util.sha1.SHA1$Sha1Implementation">
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.13.2"/>
+                <message_argument value="Sha1Implementation"/>
+            </message_arguments>
+        </filter>
+    </resource>
+</component>
diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF
index 87b58e1..808d5d3 100644
--- a/org.eclipse.jgit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit/META-INF/MANIFEST.MF
@@ -3,12 +3,12 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit
 Bundle-SymbolicName: org.eclipse.jgit
-Bundle-Version: 6.0.1.qualifier
+Bundle-Version: 6.1.1.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %Bundle-Vendor
 Eclipse-ExtensibleAPI: true
-Export-Package: org.eclipse.jgit.annotations;version="6.0.1",
- org.eclipse.jgit.api;version="6.0.1";
+Export-Package: org.eclipse.jgit.annotations;version="6.1.1",
+ org.eclipse.jgit.api;version="6.1.1";
   uses:="org.eclipse.jgit.transport,
    org.eclipse.jgit.notes,
    org.eclipse.jgit.dircache,
@@ -23,18 +23,18 @@
    org.eclipse.jgit.revwalk.filter,
    org.eclipse.jgit.blame,
    org.eclipse.jgit.merge",
- org.eclipse.jgit.api.errors;version="6.0.1";
+ org.eclipse.jgit.api.errors;version="6.1.1";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.errors",
- org.eclipse.jgit.attributes;version="6.0.1";
+ org.eclipse.jgit.attributes;version="6.1.1";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.treewalk",
- org.eclipse.jgit.blame;version="6.0.1";
+ org.eclipse.jgit.blame;version="6.1.1";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.diff",
- org.eclipse.jgit.diff;version="6.0.1";
+ org.eclipse.jgit.diff;version="6.1.1";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.attributes,
    org.eclipse.jgit.revwalk,
@@ -42,44 +42,48 @@
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.util",
- org.eclipse.jgit.dircache;version="6.0.1";
+ org.eclipse.jgit.dircache;version="6.1.1";
   uses:="org.eclipse.jgit.events,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.attributes,
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.util",
- org.eclipse.jgit.errors;version="6.0.1";
+ org.eclipse.jgit.errors;version="6.1.1";
   uses:="org.eclipse.jgit.transport,
    org.eclipse.jgit.dircache,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.internal.storage.pack",
- org.eclipse.jgit.events;version="6.0.1";
+ org.eclipse.jgit.events;version="6.1.1";
   uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.fnmatch;version="6.0.1",
- org.eclipse.jgit.gitrepo;version="6.0.1";
+ org.eclipse.jgit.fnmatch;version="6.1.1",
+ org.eclipse.jgit.gitrepo;version="6.1.1";
   uses:="org.xml.sax.helpers,
    org.eclipse.jgit.api,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
    org.xml.sax",
- org.eclipse.jgit.gitrepo.internal;version="6.0.1";x-internal:=true,
- org.eclipse.jgit.hooks;version="6.0.1";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.ignore;version="6.0.1",
- org.eclipse.jgit.ignore.internal;version="6.0.1";
+ org.eclipse.jgit.gitrepo.internal;version="6.1.1";x-internal:=true,
+ org.eclipse.jgit.hooks;version="6.1.1";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.ignore;version="6.1.1",
+ org.eclipse.jgit.ignore.internal;version="6.1.1";
   x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal;version="6.0.1";
+ org.eclipse.jgit.internal;version="6.1.1";
   x-friends:="org.eclipse.jgit.test,
    org.eclipse.jgit.http.test",
- org.eclipse.jgit.internal.fsck;version="6.0.1";
+ org.eclipse.jgit.internal.diffmergetool;version="6.1.1";
+  x-friends:="org.eclipse.jgit.test,
+   org.eclipse.jgit.pgm.test,
+   org.eclipse.jgit.pgm",
+ org.eclipse.jgit.internal.fsck;version="6.1.1";
   x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.revwalk;version="6.0.1";
+ org.eclipse.jgit.internal.revwalk;version="6.1.1";
   x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.storage.dfs;version="6.0.1";
+ org.eclipse.jgit.internal.storage.dfs;version="6.1.1";
   x-friends:="org.eclipse.jgit.test,
    org.eclipse.jgit.http.server,
    org.eclipse.jgit.http.test,
    org.eclipse.jgit.lfs.test",
- org.eclipse.jgit.internal.storage.file;version="6.0.1";
+ org.eclipse.jgit.internal.storage.file;version="6.1.1";
   x-friends:="org.eclipse.jgit.test,
    org.eclipse.jgit.junit,
    org.eclipse.jgit.junit.http,
@@ -88,32 +92,32 @@
    org.eclipse.jgit.pgm,
    org.eclipse.jgit.pgm.test,
    org.eclipse.jgit.ssh.apache",
- org.eclipse.jgit.internal.storage.io;version="6.0.1";
+ org.eclipse.jgit.internal.storage.io;version="6.1.1";
   x-friends:="org.eclipse.jgit.junit,
    org.eclipse.jgit.test,
    org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.pack;version="6.0.1";
+ org.eclipse.jgit.internal.storage.pack;version="6.1.1";
   x-friends:="org.eclipse.jgit.junit,
    org.eclipse.jgit.test,
    org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.reftable;version="6.0.1";
+ org.eclipse.jgit.internal.storage.reftable;version="6.1.1";
   x-friends:="org.eclipse.jgit.http.test,
    org.eclipse.jgit.junit,
    org.eclipse.jgit.test,
    org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.submodule;version="6.0.1";x-internal:=true,
- org.eclipse.jgit.internal.transport.connectivity;version="6.0.1";
+ org.eclipse.jgit.internal.submodule;version="6.1.1";x-internal:=true,
+ org.eclipse.jgit.internal.transport.connectivity;version="6.1.1";
   x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.transport.http;version="6.0.1";
+ org.eclipse.jgit.internal.transport.http;version="6.1.1";
   x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.transport.parser;version="6.0.1";
+ org.eclipse.jgit.internal.transport.parser;version="6.1.1";
   x-friends:="org.eclipse.jgit.http.server,
    org.eclipse.jgit.test",
- org.eclipse.jgit.internal.transport.ssh;version="6.0.1";
+ org.eclipse.jgit.internal.transport.ssh;version="6.1.1";
   x-friends:="org.eclipse.jgit.ssh.apache,
    org.eclipse.jgit.ssh.jsch,
    org.eclipse.jgit.test",
- org.eclipse.jgit.lib;version="6.0.1";
+ org.eclipse.jgit.lib;version="6.1.1";
   uses:="org.eclipse.jgit.transport,
    org.eclipse.jgit.util.sha1,
    org.eclipse.jgit.dircache,
@@ -127,10 +131,11 @@
    org.eclipse.jgit.util,
    org.eclipse.jgit.submodule,
    org.eclipse.jgit.util.time",
- org.eclipse.jgit.lib.internal;version="6.0.1";
-  x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.logging;version="6.0.1",
- org.eclipse.jgit.merge;version="6.0.1";
+ org.eclipse.jgit.lib.internal;version="6.1.1";
+  x-friends:="org.eclipse.jgit.test,
+   org.eclipse.jgit.pgm",
+ org.eclipse.jgit.logging;version="6.1.1",
+ org.eclipse.jgit.merge;version="6.1.1";
   uses:="org.eclipse.jgit.dircache,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
@@ -139,40 +144,40 @@
    org.eclipse.jgit.util,
    org.eclipse.jgit.api,
    org.eclipse.jgit.attributes",
- org.eclipse.jgit.nls;version="6.0.1",
- org.eclipse.jgit.notes;version="6.0.1";
+ org.eclipse.jgit.nls;version="6.1.1",
+ org.eclipse.jgit.notes;version="6.1.1";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.merge",
- org.eclipse.jgit.patch;version="6.0.1";
+ org.eclipse.jgit.patch;version="6.1.1";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.diff",
- org.eclipse.jgit.revplot;version="6.0.1";
+ org.eclipse.jgit.revplot;version="6.1.1";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk",
- org.eclipse.jgit.revwalk;version="6.0.1";
+ org.eclipse.jgit.revwalk;version="6.1.1";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.diff,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.revwalk.filter,
    org.eclipse.jgit.treewalk",
- org.eclipse.jgit.revwalk.filter;version="6.0.1";
+ org.eclipse.jgit.revwalk.filter;version="6.1.1";
   uses:="org.eclipse.jgit.revwalk,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.util",
- org.eclipse.jgit.storage.file;version="6.0.1";
+ org.eclipse.jgit.storage.file;version="6.1.1";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.util",
- org.eclipse.jgit.storage.pack;version="6.0.1";
+ org.eclipse.jgit.storage.pack;version="6.1.1";
   uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.submodule;version="6.0.1";
+ org.eclipse.jgit.submodule;version="6.1.1";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.diff,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.util",
- org.eclipse.jgit.transport;version="6.0.1";
+ org.eclipse.jgit.transport;version="6.1.1";
   uses:="javax.crypto,
    org.eclipse.jgit.util.io,
    org.eclipse.jgit.lib,
@@ -185,21 +190,21 @@
    org.eclipse.jgit.transport.resolver,
    org.eclipse.jgit.storage.pack,
    org.eclipse.jgit.errors",
- org.eclipse.jgit.transport.http;version="6.0.1";
+ org.eclipse.jgit.transport.http;version="6.1.1";
   uses:="javax.net.ssl",
- org.eclipse.jgit.transport.resolver;version="6.0.1";
+ org.eclipse.jgit.transport.resolver;version="6.1.1";
   uses:="org.eclipse.jgit.transport,
    org.eclipse.jgit.lib",
- org.eclipse.jgit.treewalk;version="6.0.1";
+ org.eclipse.jgit.treewalk;version="6.1.1";
   uses:="org.eclipse.jgit.dircache,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.attributes,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.util",
- org.eclipse.jgit.treewalk.filter;version="6.0.1";
+ org.eclipse.jgit.treewalk.filter;version="6.1.1";
   uses:="org.eclipse.jgit.treewalk",
- org.eclipse.jgit.util;version="6.0.1";
+ org.eclipse.jgit.util;version="6.1.1";
   uses:="org.eclipse.jgit.transport,
    org.eclipse.jgit.hooks,
    org.eclipse.jgit.revwalk,
@@ -212,12 +217,12 @@
    org.eclipse.jgit.treewalk,
    javax.net.ssl,
    org.eclipse.jgit.util.time",
- org.eclipse.jgit.util.io;version="6.0.1";
+ org.eclipse.jgit.util.io;version="6.1.1";
   uses:="org.eclipse.jgit.attributes,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.treewalk",
- org.eclipse.jgit.util.sha1;version="6.0.1",
- org.eclipse.jgit.util.time;version="6.0.1"
+ org.eclipse.jgit.util.sha1;version="6.1.1",
+ org.eclipse.jgit.util.time;version="6.1.1"
 Bundle-RequiredExecutionEnvironment: JavaSE-11
 Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)",
  javax.crypto,
diff --git a/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
index 302c454..33e6156 100644
--- a/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit - Sources
 Bundle-SymbolicName: org.eclipse.jgit.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 6.0.1.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit;version="6.0.1.qualifier";roots="."
+Bundle-Version: 6.1.1.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit;version="6.1.1.qualifier";roots="."
diff --git a/org.eclipse.jgit/pom.xml b/org.eclipse.jgit/pom.xml
index e782272..f0e92b3 100644
--- a/org.eclipse.jgit/pom.xml
+++ b/org.eclipse.jgit/pom.xml
@@ -20,7 +20,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>6.0.1-SNAPSHOT</version>
+    <version>6.1.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit</artifactId>
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
index c887c6d..5534a0a 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -359,6 +359,8 @@
 inMemoryBufferLimitExceeded=In-memory buffer limit exceeded
 inputDidntMatchLength=Input did not match supplied length. {0} bytes are missing.
 inputStreamMustSupportMark=InputStream must support mark()
+integerValueNotInRange=Integer value {0}.{1} = {2} not in range {3}..{4}
+integerValueNotInRangeSubSection=Integer value {0}.{1}.{2} = {3} not in range {4}..{5}
 integerValueOutOfRange=Integer value {0}.{1} out of range
 internalRevisionError=internal revision error
 internalServerError=internal server error
@@ -370,6 +372,7 @@
 invalidBooleanValue=Invalid boolean value: {0}.{1}={2}
 invalidChannel=Invalid channel {0}
 invalidCommitParentNumber=Invalid commit parent number
+invalidCoreAbbrev=Invalid value {0} of option core.abbrev
 invalidDepth=Invalid depth: {0}
 invalidEncoding=Invalid encoding from git config i18n.commitEncoding: {0}
 invalidEncryption=Invalid encryption
@@ -571,6 +574,11 @@
 pushCertificateInvalidFieldValue=Push certificate has missing or invalid value for {0}: {1}
 pushCertificateInvalidHeader=Push certificate has invalid header format
 pushCertificateInvalidSignature=Push certificate has invalid signature format
+pushDefaultNothing=No refspec given and push.default=nothing; no upstream branch can be determined
+pushDefaultNoUpstream=No upstream branch found for local branch ''{0}''
+pushDefaultSimple=push.default=simple requires local branch name ''{0}'' to be equal to upstream tracked branch name ''{1}''
+pushDefaultTriangularUpstream=push.default=upstream cannot be used when the push remote ''{0}'' is different from the fetch remote ''{1}''
+pushDefaultUnknown=Unknown push.default={0}; cannot push
 pushIsNotSupportedForBundleTransport=Push is not supported for bundle transport
 pushNotPermitted=push not permitted
 pushOptionsNotSupported=Push options not supported; received {0}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java
index 7922f9e..f88179a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java
@@ -9,6 +9,8 @@
  */
 package org.eclipse.jgit.api;
 
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
+
 import java.io.IOException;
 import java.text.MessageFormat;
 import java.util.LinkedList;
@@ -124,7 +126,7 @@
 				final RevCommit srcParent = getParentCommit(srcCommit, revWalk);
 
 				String ourName = calculateOurName(headRef);
-				String cherryPickName = srcCommit.getId().abbreviate(7).name()
+				String cherryPickName = srcCommit.getId().abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH).name()
 						+ " " + srcCommit.getShortMessage(); //$NON-NLS-1$
 
 				Merger merger = strategy.newMerger(repo);
@@ -183,7 +185,7 @@
 					if (unmergedPaths != null) {
 						message = new MergeMessageFormatter()
 							.formatWithConflicts(srcCommit.getFullMessage(),
-										unmergedPaths);
+										unmergedPaths, '#');
 					} else {
 						message = srcCommit.getFullMessage();
 					}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
index 37f1d48..7a591aa 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
@@ -19,6 +19,7 @@
 import java.util.LinkedList;
 import java.util.List;
 
+import org.eclipse.jgit.annotations.NonNull;
 import org.eclipse.jgit.api.errors.AbortedByHookException;
 import org.eclipse.jgit.api.errors.CanceledException;
 import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
@@ -46,6 +47,8 @@
 import org.eclipse.jgit.hooks.PreCommitHook;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.CommitConfig;
+import org.eclipse.jgit.lib.CommitConfig.CleanupMode;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.GpgConfig;
@@ -133,6 +136,12 @@
 
 	private CredentialsProvider credentialsProvider;
 
+	private @NonNull CleanupMode cleanupMode = CleanupMode.VERBATIM;
+
+	private boolean cleanDefaultIsStrip = true;
+
+	private Character commentChar;
+
 	/**
 	 * Constructor for CommitCommand
 	 *
@@ -200,7 +209,7 @@
 				throw new WrongRepositoryStateException(
 						JGitText.get().commitAmendOnInitialNotPossible);
 
-			if (headId != null)
+			if (headId != null) {
 				if (amend) {
 					RevCommit previousCommit = rw.parseCommit(headId);
 					for (RevCommit p : previousCommit.getParents())
@@ -210,7 +219,7 @@
 				} else {
 					parents.add(0, headId);
 				}
-
+			}
 			if (!noVerify) {
 				message = Hooks
 						.commitMsg(repo,
@@ -219,6 +228,19 @@
 						.setCommitMessage(message).call();
 			}
 
+			CommitConfig config = null;
+			if (CleanupMode.DEFAULT.equals(cleanupMode)) {
+				config = repo.getConfig().get(CommitConfig.KEY);
+				cleanupMode = config.resolve(cleanupMode, cleanDefaultIsStrip);
+			}
+			char comments;
+			if (commentChar == null) {
+				comments = '#'; // TODO use git config core.commentChar
+			} else {
+				comments = commentChar.charValue();
+			}
+			message = CommitConfig.cleanText(message, cleanupMode, comments);
+
 			RevCommit revCommit;
 			DirCache index = repo.lockDirCache();
 			try (ObjectInserter odi = repo.newObjectInserter()) {
@@ -658,6 +680,57 @@
 	}
 
 	/**
+	 * Sets the {@link CleanupMode} to apply to the commit message. If not
+	 * called, {@link CommitCommand} applies {@link CleanupMode#VERBATIM}.
+	 *
+	 * @param mode
+	 *            {@link CleanupMode} to set
+	 * @return {@code this}
+	 * @since 6.1
+	 */
+	public CommitCommand setCleanupMode(@NonNull CleanupMode mode) {
+		checkCallable();
+		this.cleanupMode = mode;
+		return this;
+	}
+
+	/**
+	 * Sets the default clean mode if {@link #setCleanupMode(CleanupMode)
+	 * setCleanupMode(CleanupMode.DEFAULT)} is set and git config
+	 * {@code commit.cleanup = default} or is not set.
+	 *
+	 * @param strip
+	 *            if {@code true}, default to {@link CleanupMode#STRIP};
+	 *            otherwise default to {@link CleanupMode#WHITESPACE}
+	 * @return {@code this}
+	 * @since 6.1
+	 */
+	public CommitCommand setDefaultClean(boolean strip) {
+		checkCallable();
+		this.cleanDefaultIsStrip = strip;
+		return this;
+	}
+
+	/**
+	 * Sets the comment character to apply when cleaning a commit message. If
+	 * {@code null} (the default) and the {@link #setCleanupMode(CleanupMode)
+	 * clean-up mode} is {@link CleanupMode#STRIP} or
+	 * {@link CleanupMode#SCISSORS}, the value of git config
+	 * {@code core.commentChar} will be used.
+	 *
+	 * @param commentChar
+	 *            the comment character, or {@code null} to use the value from
+	 *            the git config
+	 * @return {@code this}
+	 * @since 6.1
+	 */
+	public CommitCommand setCommentCharacter(Character commentChar) {
+		checkCallable();
+		this.commentChar = commentChar;
+		return this;
+	}
+
+	/**
 	 * Set whether to allow to create an empty commit
 	 *
 	 * @param allowEmpty
@@ -806,7 +879,7 @@
 	 * command line.
 	 *
 	 * @param amend
-	 *            whether to ammend the tip of the current branch
+	 *            whether to amend the tip of the current branch
 	 * @return {@code this}
 	 */
 	public CommitCommand setAmend(boolean amend) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
index 1e524fa..805a886 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
@@ -11,6 +11,7 @@
 
 import static org.eclipse.jgit.lib.Constants.R_REFS;
 import static org.eclipse.jgit.lib.Constants.R_TAGS;
+import static org.eclipse.jgit.lib.TypedConfigGetter.UNSET_INT;
 
 import java.io.IOException;
 import java.text.MessageFormat;
@@ -33,6 +34,7 @@
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.fnmatch.FileNameMatcher;
 import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.AbbrevConfig;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
@@ -89,6 +91,11 @@
 	private boolean always;
 
 	/**
+	 * The prefix length to use when abbreviating a commit hash.
+	 */
+	private int abbrev = UNSET_INT;
+
+	/**
 	 * Constructor for DescribeCommand.
 	 *
 	 * @param repo
@@ -205,12 +212,33 @@
 		return this;
 	}
 
+	/**
+	 * Sets the prefix length to use when abbreviating an object SHA-1.
+	 *
+	 * @param abbrev
+	 *            minimum length of the abbreviated string. Must be in the range
+	 *            [{@value AbbrevConfig#MIN_ABBREV},
+	 *            {@value Constants#OBJECT_ID_STRING_LENGTH}].
+	 * @return {@code this}
+	 * @since 6.1
+	 */
+	public DescribeCommand setAbbrev(int abbrev) {
+		if (abbrev == 0) {
+			this.abbrev = 0;
+		} else {
+			this.abbrev = AbbrevConfig.capAbbrev(abbrev);
+		}
+		return this;
+	}
+
 	private String longDescription(Ref tag, int depth, ObjectId tip)
 			throws IOException {
-		return String.format(
-				"%s-%d-g%s", formatRefName(tag.getName()), //$NON-NLS-1$
-				Integer.valueOf(depth), w.getObjectReader().abbreviate(tip)
-						.name());
+		if (abbrev == 0) {
+			return formatRefName(tag.getName());
+		}
+		return String.format("%s-%d-g%s", formatRefName(tag.getName()), //$NON-NLS-1$
+				Integer.valueOf(depth),
+				w.getObjectReader().abbreviate(tip, abbrev).name());
 	}
 
 	/**
@@ -302,6 +330,9 @@
 			if (target == null) {
 				setTarget(Constants.HEAD);
 			}
+			if (abbrev == UNSET_INT) {
+				abbrev = AbbrevConfig.parseFromConfig(repo).get();
+			}
 
 			Collection<Ref> tagList = repo.getRefDatabase()
 					.getRefsByPrefix(useAll ? R_REFS : R_TAGS);
@@ -413,7 +444,12 @@
 
 			// if all the nodes are dominated by all the tags, the walk stops
 			if (candidates.isEmpty()) {
-				return always ? w.getObjectReader().abbreviate(target).name() : null;
+				return always
+						? w.getObjectReader()
+								.abbreviate(target,
+										AbbrevConfig.capAbbrev(abbrev))
+								.name()
+						: null;
 			}
 
 			Candidate best = Collections.min(candidates,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java
index ef56d80..ce068b6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java
@@ -405,7 +405,7 @@
 							failingPaths, null);
 				}
 				String mergeMessageWithConflicts = new MergeMessageFormatter()
-						.formatWithConflicts(mergeMessage, unmergedPaths);
+						.formatWithConflicts(mergeMessage, unmergedPaths, '#');
 				repo.writeMergeCommitMsg(mergeMessageWithConflicts);
 				return new MergeResult(null, merger.getBaseCommitId(),
 						new ObjectId[] { headCommit.getId(),
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java
index aa5a634..08353df 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010, Chris Aniszczyk <caniszczyk@gmail.com> and others
+ * Copyright (C) 2010, 2022 Chris Aniszczyk <caniszczyk@gmail.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
@@ -21,7 +21,9 @@
 import java.util.List;
 import java.util.Map;
 
+import org.eclipse.jgit.api.errors.DetachedHeadException;
 import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.InvalidRefNameException;
 import org.eclipse.jgit.api.errors.InvalidRemoteException;
 import org.eclipse.jgit.api.errors.JGitInternalException;
 import org.eclipse.jgit.errors.NotSupportedException;
@@ -29,11 +31,16 @@
 import org.eclipse.jgit.errors.TooLargePackException;
 import org.eclipse.jgit.errors.TransportException;
 import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.BranchConfig;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.PushConfig;
+import org.eclipse.jgit.transport.PushConfig.PushDefault;
 import org.eclipse.jgit.transport.PushResult;
 import org.eclipse.jgit.transport.RefLeaseSpec;
 import org.eclipse.jgit.transport.RefSpec;
@@ -52,7 +59,7 @@
 public class PushCommand extends
 		TransportCommand<PushCommand, Iterable<PushResult>> {
 
-	private String remote = Constants.DEFAULT_REMOTE_NAME;
+	private String remote;
 
 	private final List<RefSpec> refSpecs;
 
@@ -71,6 +78,10 @@
 
 	private List<String> pushOptions;
 
+	// Legacy behavior as default. Use setPushDefault(null) to determine the
+	// value from the git config.
+	private PushDefault pushDefault = PushDefault.CURRENT;
+
 	/**
 	 * <p>
 	 * Constructor for PushCommand.
@@ -98,19 +109,20 @@
 			InvalidRemoteException,
 			org.eclipse.jgit.api.errors.TransportException {
 		checkCallable();
+		setCallable(false);
 
 		ArrayList<PushResult> pushResults = new ArrayList<>(3);
 
 		try {
+			Config config = repo.getConfig();
+			remote = determineRemote(config, remote);
 			if (refSpecs.isEmpty()) {
-				RemoteConfig config = new RemoteConfig(repo.getConfig(),
+				RemoteConfig rc = new RemoteConfig(config,
 						getRemote());
-				refSpecs.addAll(config.getPushRefSpecs());
-			}
-			if (refSpecs.isEmpty()) {
-				Ref head = repo.exactRef(Constants.HEAD);
-				if (head != null && head.isSymbolic())
-					refSpecs.add(new RefSpec(head.getLeaf().getName()));
+				refSpecs.addAll(rc.getPushRefSpecs());
+				if (refSpecs.isEmpty()) {
+					determineDefaultRefSpecs(config);
+				}
 			}
 
 			if (force) {
@@ -118,8 +130,8 @@
 					refSpecs.set(i, refSpecs.get(i).setForceUpdate(true));
 			}
 
-			final List<Transport> transports;
-			transports = Transport.openAll(repo, remote, Transport.Operation.PUSH);
+			List<Transport> transports = Transport.openAll(repo, remote,
+					Transport.Operation.PUSH);
 			for (@SuppressWarnings("resource") // Explicitly closed in finally
 					final Transport transport : transports) {
 				transport.setPushThin(thin);
@@ -171,6 +183,102 @@
 		return pushResults;
 	}
 
+	private String determineRemote(Config config, String remoteName)
+			throws IOException {
+		if (remoteName != null) {
+			return remoteName;
+		}
+		Ref head = repo.exactRef(Constants.HEAD);
+		String effectiveRemote = null;
+		BranchConfig branchCfg = null;
+		if (head != null && head.isSymbolic()) {
+			String currentBranch = head.getLeaf().getName();
+			branchCfg = new BranchConfig(config,
+					Repository.shortenRefName(currentBranch));
+			effectiveRemote = branchCfg.getPushRemote();
+		}
+		if (effectiveRemote == null) {
+			effectiveRemote = config.getString(
+					ConfigConstants.CONFIG_REMOTE_SECTION, null,
+					ConfigConstants.CONFIG_KEY_PUSH_DEFAULT);
+			if (effectiveRemote == null && branchCfg != null) {
+				effectiveRemote = branchCfg.getRemote();
+			}
+		}
+		if (effectiveRemote == null) {
+			effectiveRemote = Constants.DEFAULT_REMOTE_NAME;
+		}
+		return effectiveRemote;
+	}
+
+	private String getCurrentBranch()
+			throws IOException, DetachedHeadException {
+		Ref head = repo.exactRef(Constants.HEAD);
+		if (head != null && head.isSymbolic()) {
+			return head.getLeaf().getName();
+		}
+		throw new DetachedHeadException();
+	}
+
+	private void determineDefaultRefSpecs(Config config)
+			throws IOException, GitAPIException {
+		if (pushDefault == null) {
+			pushDefault = config.get(PushConfig::new).getPushDefault();
+		}
+		switch (pushDefault) {
+		case CURRENT:
+			refSpecs.add(new RefSpec(getCurrentBranch()));
+			break;
+		case MATCHING:
+			refSpecs.add(new RefSpec(":")); //$NON-NLS-1$
+			break;
+		case NOTHING:
+			throw new InvalidRefNameException(
+					JGitText.get().pushDefaultNothing);
+		case SIMPLE:
+		case UPSTREAM:
+			String currentBranch = getCurrentBranch();
+			BranchConfig branchCfg = new BranchConfig(config,
+					Repository.shortenRefName(currentBranch));
+			String fetchRemote = branchCfg.getRemote();
+			if (fetchRemote == null) {
+				fetchRemote = Constants.DEFAULT_REMOTE_NAME;
+			}
+			boolean isTriangular = !fetchRemote.equals(remote);
+			if (isTriangular) {
+				if (PushDefault.UPSTREAM.equals(pushDefault)) {
+					throw new InvalidRefNameException(MessageFormat.format(
+							JGitText.get().pushDefaultTriangularUpstream,
+							remote, fetchRemote));
+				}
+				// Strange, but consistent with C git: "simple" doesn't even
+				// check whether there is a configured upstream, and if so, that
+				// it is equal to the local branch name. It just becomes
+				// "current".
+				refSpecs.add(new RefSpec(currentBranch));
+			} else {
+				String trackedBranch = branchCfg.getMerge();
+				if (branchCfg.isRemoteLocal() || trackedBranch == null
+						|| !trackedBranch.startsWith(Constants.R_HEADS)) {
+					throw new InvalidRefNameException(MessageFormat.format(
+							JGitText.get().pushDefaultNoUpstream,
+							currentBranch));
+				}
+				if (PushDefault.SIMPLE.equals(pushDefault)
+						&& !trackedBranch.equals(currentBranch)) {
+					throw new InvalidRefNameException(MessageFormat.format(
+							JGitText.get().pushDefaultSimple, currentBranch,
+							trackedBranch));
+				}
+				refSpecs.add(new RefSpec(currentBranch + ':' + trackedBranch));
+			}
+			break;
+		default:
+			throw new InvalidRefNameException(MessageFormat
+					.format(JGitText.get().pushDefaultUnknown, pushDefault));
+		}
+	}
+
 	/**
 	 * The remote (uri or name) used for the push operation. If no remote is
 	 * set, the default value of <code>Constants.DEFAULT_REMOTE_NAME</code> will
@@ -336,9 +444,37 @@
 	}
 
 	/**
+	 * Retrieves the {@link PushDefault} currently set.
+	 *
+	 * @return the {@link PushDefault}, or {@code null} if not set
+	 * @since 6.1
+	 */
+	public PushDefault getPushDefault() {
+		return pushDefault;
+	}
+
+	/**
+	 * Sets an explicit {@link PushDefault}. The default used if this is not
+	 * called is {@link PushDefault#CURRENT} for compatibility reasons with
+	 * earlier JGit versions.
+	 *
+	 * @param pushDefault
+	 *            {@link PushDefault} to set; if {@code null} the value defined
+	 *            in the git config will be used.
+	 *
+	 * @return {@code this}
+	 * @since 6.1
+	 */
+	public PushCommand setPushDefault(PushDefault pushDefault) {
+		checkCallable();
+		this.pushDefault = pushDefault;
+		return this;
+	}
+
+	/**
 	 * Push all branches under refs/heads/*.
 	 *
-	 * @return {code this}
+	 * @return {@code this}
 	 */
 	public PushCommand setPushAll() {
 		refSpecs.add(Transport.REFSPEC_PUSH_ALL);
@@ -348,7 +484,7 @@
 	/**
 	 * Push all tags under refs/tags/*.
 	 *
-	 * @return {code this}
+	 * @return {@code this}
 	 */
 	public PushCommand setPushTags() {
 		refSpecs.add(Transport.REFSPEC_TAGS);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
index a26ffc2..2b0d8ce 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
@@ -29,6 +29,7 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import org.eclipse.jgit.annotations.NonNull;
 import org.eclipse.jgit.api.RebaseResult.Status;
 import org.eclipse.jgit.api.ResetCommand.ResetType;
 import org.eclipse.jgit.api.errors.CheckoutConflictException;
@@ -52,6 +53,8 @@
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.AbbreviatedObjectId;
 import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.CommitConfig;
+import org.eclipse.jgit.lib.CommitConfig.CleanupMode;
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.NullProgressMonitor;
@@ -205,6 +208,8 @@
 
 	private InteractiveHandler interactiveHandler;
 
+	private CommitConfig commitConfig;
+
 	private boolean stopAfterInitialization = false;
 
 	private RevCommit newHead;
@@ -246,6 +251,7 @@
 		lastStepWasForward = false;
 		checkCallable();
 		checkParameters();
+		commitConfig = repo.getConfig().get(CommitConfig.KEY);
 		try {
 			switch (operation) {
 			case ABORT:
@@ -441,11 +447,16 @@
 			return null; // continue rebase process on pick command
 		case REWORD:
 			String oldMessage = commitToPick.getFullMessage();
-			String newMessage = interactiveHandler
-					.modifyCommitMessage(oldMessage);
+			CleanupMode mode = commitConfig.resolve(CleanupMode.DEFAULT, true);
+			boolean[] doChangeId = { false };
+			String newMessage = editCommitMessage(doChangeId, oldMessage, mode);
 			try (Git git = new Git(repo)) {
-				newHead = git.commit().setMessage(newMessage).setAmend(true)
-						.setNoVerify(true).call();
+				newHead = git.commit()
+						.setMessage(newMessage)
+						.setAmend(true)
+						.setNoVerify(true)
+						.setInsertChangeId(doChangeId[0])
+						.call();
 			}
 			return null;
 		case EDIT:
@@ -460,17 +471,49 @@
 			resetSoftToParent();
 			List<RebaseTodoLine> steps = repo.readRebaseTodo(
 					rebaseState.getPath(GIT_REBASE_TODO), false);
-			RebaseTodoLine nextStep = steps.isEmpty() ? null : steps.get(0);
+			boolean isLast = steps.isEmpty();
+			if (!isLast) {
+				switch (steps.get(0).getAction()) {
+				case FIXUP:
+				case SQUASH:
+					break;
+				default:
+					isLast = true;
+					break;
+				}
+			}
 			File messageFixupFile = rebaseState.getFile(MESSAGE_FIXUP);
 			File messageSquashFile = rebaseState.getFile(MESSAGE_SQUASH);
-			if (isSquash && messageFixupFile.exists())
+			if (isSquash && messageFixupFile.exists()) {
 				messageFixupFile.delete();
-			newHead = doSquashFixup(isSquash, commitToPick, nextStep,
+			}
+			newHead = doSquashFixup(isSquash, commitToPick, isLast,
 					messageFixupFile, messageSquashFile);
 		}
 		return null;
 	}
 
+	private String editCommitMessage(boolean[] doChangeId, String message,
+			@NonNull CleanupMode mode) {
+		String newMessage;
+		CommitConfig.CleanupMode cleanup;
+		if (interactiveHandler instanceof InteractiveHandler2) {
+			InteractiveHandler2.ModifyResult modification = ((InteractiveHandler2) interactiveHandler)
+					.editCommitMessage(message, mode, '#');
+			newMessage = modification.getMessage();
+			cleanup = modification.getCleanupMode();
+			if (CleanupMode.DEFAULT.equals(cleanup)) {
+				cleanup = mode;
+			}
+			doChangeId[0] = modification.shouldAddChangeId();
+		} else {
+			newMessage = interactiveHandler.modifyCommitMessage(message);
+			cleanup = CommitConfig.CleanupMode.STRIP;
+			doChangeId[0] = false;
+		}
+		return CommitConfig.cleanText(newMessage, cleanup, '#');
+	}
+
 	private RebaseResult cherryPickCommit(RevCommit commitToPick)
 			throws IOException, GitAPIException, NoMessageException,
 			UnmergedPathsException, ConcurrentRefUpdateException,
@@ -707,7 +750,7 @@
 	}
 
 	private RevCommit doSquashFixup(boolean isSquash, RevCommit commitToPick,
-			RebaseTodoLine nextStep, File messageFixup, File messageSquash)
+			boolean isLast, File messageFixup, File messageSquash)
 			throws IOException, GitAPIException {
 
 		if (!messageSquash.exists()) {
@@ -717,24 +760,20 @@
 
 			initializeSquashFixupFile(MESSAGE_SQUASH,
 					previousCommit.getFullMessage());
-			if (!isSquash)
-				initializeSquashFixupFile(MESSAGE_FIXUP,
-					previousCommit.getFullMessage());
+			if (!isSquash) {
+				rebaseState.createFile(MESSAGE_FIXUP,
+						previousCommit.getFullMessage());
+			}
 		}
-		String currSquashMessage = rebaseState
-				.readFile(MESSAGE_SQUASH);
+		String currSquashMessage = rebaseState.readFile(MESSAGE_SQUASH);
 
 		int count = parseSquashFixupSequenceCount(currSquashMessage) + 1;
 
 		String content = composeSquashMessage(isSquash,
 				commitToPick, currSquashMessage, count);
 		rebaseState.createFile(MESSAGE_SQUASH, content);
-		if (messageFixup.exists())
-			rebaseState.createFile(MESSAGE_FIXUP, content);
 
-		return squashIntoPrevious(
-				!messageFixup.exists(),
-				nextStep);
+		return squashIntoPrevious(!messageFixup.exists(), isLast);
 	}
 
 	private void resetSoftToParent() throws IOException,
@@ -756,26 +795,30 @@
 	}
 
 	private RevCommit squashIntoPrevious(boolean sequenceContainsSquash,
-			RebaseTodoLine nextStep)
+			boolean isLast)
 			throws IOException, GitAPIException {
 		RevCommit retNewHead;
-		String commitMessage = rebaseState
-				.readFile(MESSAGE_SQUASH);
-
+		String commitMessage;
+		if (!isLast || sequenceContainsSquash) {
+			commitMessage = rebaseState.readFile(MESSAGE_SQUASH);
+		} else {
+			commitMessage = rebaseState.readFile(MESSAGE_FIXUP);
+		}
 		try (Git git = new Git(repo)) {
-			if (nextStep == null || ((nextStep.getAction() != Action.FIXUP)
-					&& (nextStep.getAction() != Action.SQUASH))) {
-				// this is the last step in this sequence
+			if (isLast) {
+				boolean[] doChangeId = { false };
 				if (sequenceContainsSquash) {
-					commitMessage = interactiveHandler
-							.modifyCommitMessage(commitMessage);
+					commitMessage = editCommitMessage(doChangeId, commitMessage,
+							CleanupMode.STRIP);
 				}
 				retNewHead = git.commit()
-						.setMessage(stripCommentLines(commitMessage))
-						.setAmend(true).setNoVerify(true).call();
+						.setMessage(commitMessage)
+						.setAmend(true)
+						.setNoVerify(true)
+						.setInsertChangeId(doChangeId[0])
+						.call();
 				rebaseState.getFile(MESSAGE_SQUASH).delete();
 				rebaseState.getFile(MESSAGE_FIXUP).delete();
-
 			} else {
 				// Next step is either Squash or Fixup
 				retNewHead = git.commit().setMessage(commitMessage)
@@ -785,21 +828,6 @@
 		return retNewHead;
 	}
 
-	private static String stripCommentLines(String commitMessage) {
-		StringBuilder result = new StringBuilder();
-		for (String line : commitMessage.split("\n")) { //$NON-NLS-1$
-			if (!line.trim().startsWith("#")) //$NON-NLS-1$
-				result.append(line).append("\n"); //$NON-NLS-1$
-		}
-		if (!commitMessage.endsWith("\n")) { //$NON-NLS-1$
-			int bufferSize = result.length();
-			if (bufferSize > 0 && result.charAt(bufferSize - 1) == '\n') {
-				result.deleteCharAt(bufferSize - 1);
-			}
-		}
-		return result.toString();
-	}
-
 	@SuppressWarnings("nls")
 	private static String composeSquashMessage(boolean isSquash,
 			RevCommit commitToPick, String currSquashMessage, int count) {
@@ -1625,26 +1653,106 @@
 	}
 
 	/**
-	 * Allows configure rebase interactive process and modify commit message
+	 * Allows to configure the interactive rebase process steps and to modify
+	 * commit messages.
 	 */
 	public interface InteractiveHandler {
+
 		/**
-		 * Given list of {@code steps} should be modified according to user
-		 * rebase configuration
+		 * Callback API to modify the initial list of interactive rebase steps.
+		 *
 		 * @param steps
-		 *            initial configuration of rebase interactive
+		 *            initial configuration of interactive rebase
 		 */
 		void prepareSteps(List<RebaseTodoLine> steps);
 
 		/**
-		 * Used for editing commit message on REWORD
+		 * Used for editing commit message on REWORD or SQUASH.
 		 *
-		 * @param commit
+		 * @param message
+		 *            existing commit message
 		 * @return new commit message
 		 */
-		String modifyCommitMessage(String commit);
+		String modifyCommitMessage(String message);
 	}
 
+	/**
+	 * Extends {@link InteractiveHandler} with an enhanced callback for editing
+	 * commit messages.
+	 *
+	 * @since 6.1
+	 */
+	public interface InteractiveHandler2 extends InteractiveHandler {
+
+		/**
+		 * Callback API for editing a commit message on REWORD or SQUASH.
+		 * <p>
+		 * The callback gets the comment character currently set, and the
+		 * clean-up mode. It can use this information when presenting the
+		 * message to the user, and it also has the possibility to clean the
+		 * message itself (in which case the returned {@link ModifyResult}
+		 * should have {@link CleanupMode#VERBATIM} set lest JGit cleans the
+		 * message again). It can also override the initial clean-up mode by
+		 * returning clean-up mode other than {@link CleanupMode#DEFAULT}. If it
+		 * does return {@code DEFAULT}, the passed-in {@code mode} will be
+		 * applied.
+		 * </p>
+		 *
+		 * @param message
+		 *            existing commit message
+		 * @param mode
+		 *            {@link CleanupMode} currently set
+		 * @param commentChar
+		 *            comment character used
+		 * @return a {@link ModifyResult}
+		 */
+		@NonNull
+		ModifyResult editCommitMessage(@NonNull String message,
+				@NonNull CleanupMode mode, char commentChar);
+
+		@Override
+		default String modifyCommitMessage(String message) {
+			// Should actually not be called; but do something reasonable anyway
+			ModifyResult result = editCommitMessage(
+					message == null ? "" : message, CleanupMode.STRIP, //$NON-NLS-1$
+					'#');
+			return result.getMessage();
+		}
+
+		/**
+		 * Describes the result of editing a commit message: the new message,
+		 * and how it should be cleaned.
+		 */
+		interface ModifyResult {
+
+			/**
+			 * Retrieves the new commit message.
+			 *
+			 * @return the message
+			 */
+			@NonNull
+			String getMessage();
+
+			/**
+			 * Tells how the message returned by {@link #getMessage()} should be
+			 * cleaned.
+			 *
+			 * @return the {@link CleanupMode}
+			 */
+			@NonNull
+			CleanupMode getCleanupMode();
+
+			/**
+			 * Tells whether a Gerrit Change-Id should be computed and added to
+			 * the commit message, as with
+			 * {@link CommitCommand#setInsertChangeId(boolean)}.
+			 *
+			 * @return {@code true} if a Change-Id should be handled,
+			 *         {@code false} otherwise
+			 */
+			boolean shouldAddChangeId();
+		}
+	}
 
 	PersonIdent parseAuthor(byte[] raw) {
 		if (raw.length == 0)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java
index 22ef4d0..db88ad8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java
@@ -9,6 +9,8 @@
  */
 package org.eclipse.jgit.api;
 
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
+
 import java.io.IOException;
 import java.text.MessageFormat;
 import java.util.LinkedList;
@@ -128,8 +130,9 @@
 				revWalk.parseHeaders(srcParent);
 
 				String ourName = calculateOurName(headRef);
-				String revertName = srcCommit.getId().abbreviate(7).name()
-						+ " " + srcCommit.getShortMessage(); //$NON-NLS-1$
+				String revertName = srcCommit.getId()
+						.abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH).name() + " " //$NON-NLS-1$
+						+ srcCommit.getShortMessage();
 
 				ResolveMerger merger = (ResolveMerger) strategy.newMerger(repo);
 				merger.setWorkingTreeIterator(new FileTreeIterator(repo));
@@ -183,8 +186,8 @@
 								merger.getMergeResults(), failingPaths, null);
 					if (!merger.failed() && !unmergedPaths.isEmpty()) {
 						String message = new MergeMessageFormatter()
-						.formatWithConflicts(newMessage,
-								merger.getUnmergedPaths());
+								.formatWithConflicts(newMessage,
+										merger.getUnmergedPaths(), '#');
 						repo.writeRevertHead(srcCommit.getId());
 						repo.writeMergeCommitMsg(message);
 					}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java
index 35fd899..f7a1f4e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java
@@ -9,6 +9,8 @@
  */
 package org.eclipse.jgit.api;
 
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
+
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
@@ -302,7 +304,8 @@
 				builder.setParentId(headCommit);
 				builder.setTreeId(cache.writeTree(inserter));
 				builder.setMessage(MessageFormat.format(indexMessage, branch,
-						headCommit.abbreviate(7).name(),
+						headCommit.abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH)
+								.name(),
 						headCommit.getShortMessage()));
 				ObjectId indexCommit = inserter.insert(builder);
 
@@ -319,7 +322,10 @@
 					builder.setParentIds(new ObjectId[0]);
 					builder.setTreeId(untrackedDirCache.writeTree(inserter));
 					builder.setMessage(MessageFormat.format(MSG_UNTRACKED,
-							branch, headCommit.abbreviate(7).name(),
+							branch,
+							headCommit
+									.abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH)
+									.name(),
 							headCommit.getShortMessage()));
 					untrackedCommit = inserter.insert(builder);
 				}
@@ -339,7 +345,8 @@
 					builder.addParentId(untrackedCommit);
 				builder.setMessage(MessageFormat.format(
 						workingDirectoryMessage, branch,
-						headCommit.abbreviate(7).name(),
+						headCommit.abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH)
+								.name(),
 						headCommit.getShortMessage()));
 				builder.setTreeId(cache.writeTree(inserter));
 				commitId = inserter.insert(builder);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java
index 638dd82..7ec7859 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java
@@ -1,43 +1,11 @@
 /*
- * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com>
+ * Copyright (C) 2015, 2022 Ivan Motsch <ivan.motsch@bsiag.com> and others
  *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
+ * 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.
  *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- *   copyright notice, this list of conditions and the following
- *   disclaimer in the documentation and/or other materials provided
- *   with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- *   names of its contributors may be used to endorse or promote
- *   products derived from this software without specific prior
- *   written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * SPDX-License-Identifier: BSD-3-Clause
  */
 package org.eclipse.jgit.attributes;
 
@@ -46,6 +14,7 @@
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
+import java.util.function.Supplier;
 
 import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.attributes.Attribute.State;
@@ -84,6 +53,8 @@
 
 	private final TreeWalk treeWalk;
 
+	private final Supplier<CanonicalTreeParser> attributesTree;
+
 	private final AttributesNode globalNode;
 
 	private final AttributesNode infoNode;
@@ -98,22 +69,41 @@
 	 * @param treeWalk
 	 *            a {@link org.eclipse.jgit.treewalk.TreeWalk}
 	 * @throws java.io.IOException
+	 * @deprecated since 6.1, use {@link #AttributesHandler(TreeWalk, Supplier)}
+	 *             instead
 	 */
+	@Deprecated
 	public AttributesHandler(TreeWalk treeWalk) throws IOException {
+		this(treeWalk, () -> treeWalk.getTree(CanonicalTreeParser.class));
+	}
+
+	/**
+	 * Create an {@link org.eclipse.jgit.attributes.AttributesHandler} with
+	 * default rules as well as merged rules from global, info and worktree root
+	 * attributes
+	 *
+	 * @param treeWalk
+	 *            a {@link org.eclipse.jgit.treewalk.TreeWalk}
+	 * @param attributesTree
+	 *            the tree to read .gitattributes from
+	 * @throws java.io.IOException
+	 * @since 6.1
+	 */
+	public AttributesHandler(TreeWalk treeWalk,
+			Supplier<CanonicalTreeParser> attributesTree) throws IOException {
 		this.treeWalk = treeWalk;
-		AttributesNodeProvider attributesNodeProvider =treeWalk.getAttributesNodeProvider();
+		this.attributesTree = attributesTree;
+		AttributesNodeProvider attributesNodeProvider = treeWalk
+				.getAttributesNodeProvider();
 		this.globalNode = attributesNodeProvider != null
 				? attributesNodeProvider.getGlobalAttributesNode() : null;
 		this.infoNode = attributesNodeProvider != null
 				? attributesNodeProvider.getInfoAttributesNode() : null;
 
 		AttributesNode rootNode = attributesNode(treeWalk,
-				rootOf(
-						treeWalk.getTree(WorkingTreeIterator.class)),
-				rootOf(
-						treeWalk.getTree(DirCacheIterator.class)),
-				rootOf(treeWalk
-						.getTree(CanonicalTreeParser.class)));
+				rootOf(treeWalk.getTree(WorkingTreeIterator.class)),
+				rootOf(treeWalk.getTree(DirCacheIterator.class)),
+				rootOf(attributesTree.get()));
 
 		expansions.put(BINARY_RULE_KEY, BINARY_RULE_ATTRIBUTES);
 		for (AttributesNode node : new AttributesNode[] { globalNode, rootNode,
@@ -152,7 +142,7 @@
 				isDirectory,
 				treeWalk.getTree(WorkingTreeIterator.class),
 				treeWalk.getTree(DirCacheIterator.class),
-				treeWalk.getTree(CanonicalTreeParser.class),
+				attributesTree.get(),
 				attributes);
 
 		// Gets the attributes located in the global attribute file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
index 49da95c..1a5f74f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
@@ -18,6 +18,7 @@
 import static org.eclipse.jgit.diff.DiffEntry.ChangeType.RENAME;
 import static org.eclipse.jgit.diff.DiffEntry.Side.NEW;
 import static org.eclipse.jgit.diff.DiffEntry.Side.OLD;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
 import static org.eclipse.jgit.lib.Constants.encode;
 import static org.eclipse.jgit.lib.Constants.encodeASCII;
 import static org.eclipse.jgit.lib.FileMode.GITLINK;
@@ -90,7 +91,7 @@
 
 	private int context = 3;
 
-	private int abbreviationLength = 7;
+	private int abbreviationLength = OBJECT_ID_ABBREV_STRING_LENGTH;
 
 	private DiffAlgorithm diffAlgorithm;
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
index c904a78..3d50a82 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
@@ -4,7 +4,8 @@
  * Copyright (C) 2008, Roger C. Soares <rogersoares@intelinet.com.br>
  * Copyright (C) 2006, Shawn O. Pearce <spearce@spearce.org>
  * Copyright (C) 2010, Chrisian Halstrick <christian.halstrick@sap.com>
- * Copyright (C) 2019-2020, Andre Bossert <andre.bossert@siemens.com>
+ * Copyright (C) 2019, 2020, Andre Bossert <andre.bossert@siemens.com>
+ * Copyright (C) 2017, 2022, Thomas Wolf <thomas.wolf@paranor.ch> 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
@@ -299,7 +300,7 @@
 		walk = new NameConflictTreeWalk(repo);
 		builder = dc.builder();
 
-		addTree(walk, headCommitTree);
+		walk.setHead(addTree(walk, headCommitTree));
 		addTree(walk, mergeCommitTree);
 		int dciPos = walk.addTree(new DirCacheBuildIterator(builder));
 		walk.addTree(workingTree);
@@ -315,13 +316,6 @@
 		}
 	}
 
-	private void addTree(TreeWalk tw, ObjectId id) throws MissingObjectException, IncorrectObjectTypeException, IOException {
-		if (id == null)
-			tw.addTree(new EmptyTreeIterator());
-		else
-			tw.addTree(id);
-	}
-
 	/**
 	 * Scan index and merge tree (no HEAD). Used e.g. for initial checkout when
 	 * there is no head yet.
@@ -341,7 +335,7 @@
 		builder = dc.builder();
 
 		walk = new NameConflictTreeWalk(repo);
-		addTree(walk, mergeCommitTree);
+		walk.setHead(addTree(walk, mergeCommitTree));
 		int dciPos = walk.addTree(new DirCacheBuildIterator(builder));
 		walk.addTree(workingTree);
 		workingTree.setDirCacheIterator(walk, dciPos);
@@ -356,6 +350,14 @@
 		conflicts.removeAll(removed);
 	}
 
+	private int addTree(TreeWalk tw, ObjectId id) throws MissingObjectException,
+			IncorrectObjectTypeException, IOException {
+		if (id == null) {
+			return tw.addTree(new EmptyTreeIterator());
+		}
+		return tw.addTree(id);
+	}
+
 	/**
 	 * Processing an entry in the context of {@link #prescanOneTree()} when only
 	 * one tree is given
@@ -382,17 +384,14 @@
 						// failOnConflict is false. Putting something to conflicts
 						// would mean we delete it. Instead we want the mergeCommit
 						// content to be checked out.
-						update(m.getEntryPathString(), m.getEntryObjectId(),
-								m.getEntryFileMode());
+						update(m);
 					}
 				} else
-					update(m.getEntryPathString(), m.getEntryObjectId(),
-						m.getEntryFileMode());
+					update(m);
 			} else if (f == null || !m.idEqual(i)) {
 				// The working tree file is missing or the merge content differs
 				// from index content
-				update(m.getEntryPathString(), m.getEntryObjectId(),
-						m.getEntryFileMode());
+				update(m);
 			} else if (i.getDirCacheEntry() != null) {
 				// The index contains a file (and not a folder)
 				if (f.isModified(i.getDirCacheEntry(), true,
@@ -400,8 +399,7 @@
 						|| i.getDirCacheEntry().getStage() != 0)
 					// The working tree file is dirty or the index contains a
 					// conflict
-					update(m.getEntryPathString(), m.getEntryObjectId(),
-							m.getEntryFileMode());
+					update(m);
 				else {
 					// update the timestamp of the index with the one from the
 					// file if not set, as we are sure to be in sync here.
@@ -802,7 +800,7 @@
 				if (f != null && isModifiedSubtree_IndexWorkingtree(name)) {
 					conflict(name, dce, h, m); // 1
 				} else {
-					update(name, mId, mMode); // 2
+					update(1, name, mId, mMode); // 2
 				}
 
 				break;
@@ -828,7 +826,7 @@
 				// are found later
 				break;
 			case 0xD0F: // 19
-				update(name, mId, mMode);
+				update(1, name, mId, mMode);
 				break;
 			case 0xDF0: // conflict without a rule
 			case 0x0FD: // 15
@@ -839,7 +837,7 @@
 					if (isModifiedSubtree_IndexWorkingtree(name))
 						conflict(name, dce, h, m); // 8
 					else
-						update(name, mId, mMode); // 7
+						update(1, name, mId, mMode); // 7
 				} else
 					conflict(name, dce, h, m); // 9
 				break;
@@ -859,7 +857,7 @@
 				break;
 			case 0x0DF: // 16 17
 				if (!isModifiedSubtree_IndexWorkingtree(name))
-					update(name, mId, mMode);
+					update(1, name, mId, mMode);
 				else
 					conflict(name, dce, h, m);
 				break;
@@ -929,7 +927,7 @@
 				// At least one of Head, Index, Merge is not empty
 				// -> only Merge contains something for this path. Use it!
 				// Potentially update the file
-				update(name, mId, mMode); // 1
+				update(1, name, mId, mMode); // 1
 			else if (m == null)
 				// Nothing in Merge
 				// Something in Head
@@ -947,7 +945,7 @@
 				// find in Merge. Potentially updates the file.
 				if (equalIdAndMode(hId, hMode, mId, mMode)) {
 					if (initialCheckout || force) {
-						update(name, mId, mMode);
+						update(1, name, mId, mMode);
 					} else {
 						keep(name, dce, f);
 					}
@@ -1131,7 +1129,7 @@
 
 						// TODO check that we don't overwrite some unsaved
 						// file content
-						update(name, mId, mMode);
+						update(1, name, mId, mMode);
 					} else if (dce != null
 							&& (f != null && f.isModified(dce, true,
 									this.walk.getObjectReader()))) {
@@ -1150,7 +1148,7 @@
 						// -> Standard case when switching between branches:
 						// Nothing new in index but something different in
 						// Merge. Update index and file
-						update(name, mId, mMode);
+						update(1, name, mId, mMode);
 					}
 				} else {
 					// Head differs from index or merge is same as index
@@ -1237,12 +1235,17 @@
 		removed.add(path);
 	}
 
-	private void update(String path, ObjectId mId, FileMode mode)
-			throws IOException {
+	private void update(CanonicalTreeParser tree) throws IOException {
+		update(0, tree.getEntryPathString(), tree.getEntryObjectId(),
+				tree.getEntryFileMode());
+	}
+
+	private void update(int index, String path, ObjectId mId,
+			FileMode mode) throws IOException {
 		if (!FileMode.TREE.equals(mode)) {
 			updated.put(path, new CheckoutMetadata(
-					walk.getEolStreamType(CHECKOUT_OP),
-					walk.getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE)));
+					walk.getCheckoutEolStreamType(index),
+					walk.getSmudgeCommand(index)));
 
 			DirCacheEntry entry = new DirCacheEntry(path, DirCacheEntry.STAGE_0);
 			entry.setObjectId(mId);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/BareSuperprojectWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/BareSuperprojectWriter.java
new file mode 100644
index 0000000..e6626ae
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/BareSuperprojectWriter.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2021, Google Inc. 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
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.gitrepo;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.lib.Constants.R_TAGS;
+
+import java.io.IOException;
+import java.net.URI;
+import java.text.MessageFormat;
+import java.util.List;
+
+import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheBuilder;
+import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.gitrepo.RepoCommand.ManifestErrorException;
+import org.eclipse.jgit.gitrepo.RepoCommand.RemoteFile;
+import org.eclipse.jgit.gitrepo.RepoCommand.RemoteReader;
+import org.eclipse.jgit.gitrepo.RepoCommand.RemoteUnavailableException;
+import org.eclipse.jgit.gitrepo.RepoProject.CopyFile;
+import org.eclipse.jgit.gitrepo.RepoProject.LinkFile;
+import org.eclipse.jgit.gitrepo.internal.RepoText;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.RefUpdate.Result;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.util.FileUtils;
+
+/**
+ * Writes .gitmodules and gitlinks of parsed manifest projects into a bare
+ * repository.
+ *
+ * To write on a regular repository, see {@link RegularSuperprojectWriter}.
+ */
+class BareSuperprojectWriter {
+	private static final int LOCK_FAILURE_MAX_RETRIES = 5;
+
+	// Retry exponentially with delays in this range
+	private static final int LOCK_FAILURE_MIN_RETRY_DELAY_MILLIS = 50;
+
+	private static final int LOCK_FAILURE_MAX_RETRY_DELAY_MILLIS = 5000;
+
+	private final Repository repo;
+
+	private final URI targetUri;
+
+	private final String targetBranch;
+
+	private final RemoteReader callback;
+
+	private final BareWriterConfig config;
+
+	private final PersonIdent author;
+
+	private List<ExtraContent> extraContents;
+
+	static class BareWriterConfig {
+		boolean ignoreRemoteFailures = false;
+
+		boolean recordRemoteBranch = true;
+
+		boolean recordSubmoduleLabels = true;
+
+		boolean recordShallowSubmodules = true;
+
+		static BareWriterConfig getDefault() {
+			return new BareWriterConfig();
+		}
+
+		private BareWriterConfig() {
+		}
+	}
+
+	static class ExtraContent {
+		final String path;
+
+		final String content;
+
+		ExtraContent(String path, String content) {
+			this.path = path;
+			this.content = content;
+		}
+	}
+
+	BareSuperprojectWriter(Repository repo, URI targetUri,
+			String targetBranch,
+			PersonIdent author, RemoteReader callback,
+			BareWriterConfig config,
+			List<ExtraContent> extraContents) {
+		assert (repo.isBare());
+		this.repo = repo;
+		this.targetUri = targetUri;
+		this.targetBranch = targetBranch;
+		this.author = author;
+		this.callback = callback;
+		this.config = config;
+		this.extraContents = extraContents;
+	}
+
+	RevCommit write(List<RepoProject> repoProjects)
+			throws GitAPIException {
+		DirCache index = DirCache.newInCore();
+		ObjectInserter inserter = repo.newObjectInserter();
+
+		try (RevWalk rw = new RevWalk(repo)) {
+			prepareIndex(repoProjects, index, inserter);
+			ObjectId treeId = index.writeTree(inserter);
+			long prevDelay = 0;
+			for (int i = 0; i < LOCK_FAILURE_MAX_RETRIES - 1; i++) {
+				try {
+					return commitTreeOnCurrentTip(inserter, rw, treeId);
+				} catch (ConcurrentRefUpdateException e) {
+					prevDelay = FileUtils.delay(prevDelay,
+							LOCK_FAILURE_MIN_RETRY_DELAY_MILLIS,
+							LOCK_FAILURE_MAX_RETRY_DELAY_MILLIS);
+					Thread.sleep(prevDelay);
+					repo.getRefDatabase().refresh();
+				}
+			}
+			// In the last try, just propagate the exceptions
+			return commitTreeOnCurrentTip(inserter, rw, treeId);
+		} catch (IOException | InterruptedException e) {
+			throw new ManifestErrorException(e);
+		}
+	}
+
+	private void prepareIndex(List<RepoProject> projects, DirCache index,
+			ObjectInserter inserter) throws IOException, GitAPIException {
+		Config cfg = new Config();
+		StringBuilder attributes = new StringBuilder();
+		DirCacheBuilder builder = index.builder();
+		for (RepoProject proj : projects) {
+			String name = proj.getName();
+			String path = proj.getPath();
+			String url = proj.getUrl();
+			ObjectId objectId;
+			if (ObjectId.isId(proj.getRevision())) {
+				objectId = ObjectId.fromString(proj.getRevision());
+			} else {
+				objectId = callback.sha1(url, proj.getRevision());
+				if (objectId == null && !config.ignoreRemoteFailures) {
+					throw new RemoteUnavailableException(url);
+				}
+				if (config.recordRemoteBranch) {
+					// "branch" field is only for non-tag references.
+					// Keep tags in "ref" field as hint for other tools.
+					String field = proj.getRevision().startsWith(R_TAGS) ? "ref" //$NON-NLS-1$
+							: "branch"; //$NON-NLS-1$
+					cfg.setString("submodule", name, field, //$NON-NLS-1$
+							proj.getRevision());
+				}
+
+				if (config.recordShallowSubmodules
+						&& proj.getRecommendShallow() != null) {
+					// The shallow recommendation is losing information.
+					// As the repo manifests stores the recommended
+					// depth in the 'clone-depth' field, while
+					// git core only uses a binary 'shallow = true/false'
+					// hint, we'll map any depth to 'shallow = true'
+					cfg.setBoolean("submodule", name, "shallow", //$NON-NLS-1$ //$NON-NLS-2$
+							true);
+				}
+			}
+			if (config.recordSubmoduleLabels) {
+				StringBuilder rec = new StringBuilder();
+				rec.append("/"); //$NON-NLS-1$
+				rec.append(path);
+				for (String group : proj.getGroups()) {
+					rec.append(" "); //$NON-NLS-1$
+					rec.append(group);
+				}
+				rec.append("\n"); //$NON-NLS-1$
+				attributes.append(rec.toString());
+			}
+
+			URI submodUrl = URI.create(url);
+			if (targetUri != null) {
+				submodUrl = RepoCommand.relativize(targetUri, submodUrl);
+			}
+			cfg.setString("submodule", name, "path", path); //$NON-NLS-1$ //$NON-NLS-2$
+			cfg.setString("submodule", name, "url", //$NON-NLS-1$ //$NON-NLS-2$
+					submodUrl.toString());
+
+			// create gitlink
+			if (objectId != null) {
+				DirCacheEntry dcEntry = new DirCacheEntry(path);
+				dcEntry.setObjectId(objectId);
+				dcEntry.setFileMode(FileMode.GITLINK);
+				builder.add(dcEntry);
+
+				for (CopyFile copyfile : proj.getCopyFiles()) {
+					RemoteFile rf = callback.readFileWithMode(url,
+							proj.getRevision(), copyfile.src);
+					objectId = inserter.insert(Constants.OBJ_BLOB,
+							rf.getContents());
+					dcEntry = new DirCacheEntry(copyfile.dest);
+					dcEntry.setObjectId(objectId);
+					dcEntry.setFileMode(rf.getFileMode());
+					builder.add(dcEntry);
+				}
+				for (LinkFile linkfile : proj.getLinkFiles()) {
+					String link;
+					if (linkfile.dest.contains("/")) { //$NON-NLS-1$
+						link = FileUtils.relativizeGitPath(
+								linkfile.dest.substring(0,
+										linkfile.dest.lastIndexOf('/')),
+								proj.getPath() + "/" + linkfile.src); //$NON-NLS-1$
+					} else {
+						link = proj.getPath() + "/" + linkfile.src; //$NON-NLS-1$
+					}
+
+					objectId = inserter.insert(Constants.OBJ_BLOB,
+							link.getBytes(UTF_8));
+					dcEntry = new DirCacheEntry(linkfile.dest);
+					dcEntry.setObjectId(objectId);
+					dcEntry.setFileMode(FileMode.SYMLINK);
+					builder.add(dcEntry);
+				}
+			}
+		}
+		String content = cfg.toText();
+
+		// create a new DirCacheEntry for .gitmodules file.
+		DirCacheEntry dcEntry = new DirCacheEntry(
+				Constants.DOT_GIT_MODULES);
+		ObjectId objectId = inserter.insert(Constants.OBJ_BLOB,
+				content.getBytes(UTF_8));
+		dcEntry.setObjectId(objectId);
+		dcEntry.setFileMode(FileMode.REGULAR_FILE);
+		builder.add(dcEntry);
+
+		if (config.recordSubmoduleLabels) {
+			// create a new DirCacheEntry for .gitattributes file.
+			DirCacheEntry dcEntryAttr = new DirCacheEntry(
+					Constants.DOT_GIT_ATTRIBUTES);
+			ObjectId attrId = inserter.insert(Constants.OBJ_BLOB,
+					attributes.toString().getBytes(UTF_8));
+			dcEntryAttr.setObjectId(attrId);
+			dcEntryAttr.setFileMode(FileMode.REGULAR_FILE);
+			builder.add(dcEntryAttr);
+		}
+
+		for (ExtraContent ec : extraContents) {
+			DirCacheEntry extraDcEntry = new DirCacheEntry(ec.path);
+
+			ObjectId oid = inserter.insert(Constants.OBJ_BLOB,
+					ec.content.getBytes(UTF_8));
+			extraDcEntry.setObjectId(oid);
+			extraDcEntry.setFileMode(FileMode.REGULAR_FILE);
+			builder.add(extraDcEntry);
+		}
+
+		builder.finish();
+	}
+
+	private RevCommit commitTreeOnCurrentTip(ObjectInserter inserter,
+			RevWalk rw, ObjectId treeId)
+			throws IOException, ConcurrentRefUpdateException {
+		ObjectId headId = repo.resolve(targetBranch + "^{commit}"); //$NON-NLS-1$
+		if (headId != null
+				&& rw.parseCommit(headId).getTree().getId().equals(treeId)) {
+			// No change. Do nothing.
+			return rw.parseCommit(headId);
+		}
+
+		CommitBuilder commit = new CommitBuilder();
+		commit.setTreeId(treeId);
+		if (headId != null) {
+			commit.setParentIds(headId);
+		}
+		commit.setAuthor(author);
+		commit.setCommitter(author);
+		commit.setMessage(RepoText.get().repoCommitMessage);
+
+		ObjectId commitId = inserter.insert(commit);
+		inserter.flush();
+
+		RefUpdate ru = repo.updateRef(targetBranch);
+		ru.setNewObjectId(commitId);
+		ru.setExpectedOldObjectId(headId != null ? headId : ObjectId.zeroId());
+		Result rc = ru.update(rw);
+		switch (rc) {
+		case NEW:
+		case FORCED:
+		case FAST_FORWARD:
+			// Successful. Do nothing.
+			break;
+		case REJECTED:
+		case LOCK_FAILURE:
+			throw new ConcurrentRefUpdateException(MessageFormat.format(
+					JGitText.get().cannotLock, targetBranch), ru.getRef(), rc);
+		default:
+			throw new JGitInternalException(
+					MessageFormat.format(JGitText.get().updatingRefFailed,
+							targetBranch, commitId.name(), rc));
+		}
+
+		return rw.parseCommit(commitId);
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RegularSuperprojectWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RegularSuperprojectWriter.java
new file mode 100644
index 0000000..afab994
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RegularSuperprojectWriter.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2021, Google Inc. 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
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.gitrepo;
+
+import static org.eclipse.jgit.lib.Constants.DEFAULT_REMOTE_NAME;
+import static org.eclipse.jgit.lib.Constants.R_REMOTES;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.SubmoduleAddCommand;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.gitrepo.RepoCommand.ManifestErrorException;
+import org.eclipse.jgit.gitrepo.RepoProject.CopyFile;
+import org.eclipse.jgit.gitrepo.RepoProject.LinkFile;
+import org.eclipse.jgit.gitrepo.internal.RepoText;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+/**
+ * Writes .gitmodules and gitlinks of parsed manifest projects into a regular
+ * repository (using git submodule commands)
+ *
+ * To write on a bare repository, use {@link BareSuperprojectWriter}
+ */
+class RegularSuperprojectWriter {
+
+	private Repository repo;
+
+	private ProgressMonitor monitor;
+
+	RegularSuperprojectWriter(Repository repo, ProgressMonitor monitor) {
+		this.repo = repo;
+		this.monitor = monitor;
+	}
+
+	RevCommit write(List<RepoProject> repoProjects)
+			throws GitAPIException {
+		try (Git git = new Git(repo)) {
+			for (RepoProject proj : repoProjects) {
+				addSubmodule(proj.getName(), proj.getUrl(), proj.getPath(),
+						proj.getRevision(), proj.getCopyFiles(),
+						proj.getLinkFiles(), git);
+			}
+			return git.commit().setMessage(RepoText.get().repoCommitMessage)
+					.call();
+		} catch (IOException e) {
+			throw new ManifestErrorException(e);
+		}
+	}
+
+	private void addSubmodule(String name, String url, String path,
+			String revision, List<CopyFile> copyfiles, List<LinkFile> linkfiles,
+			Git git) throws GitAPIException, IOException {
+		assert (!repo.isBare());
+		assert (git != null);
+		if (!linkfiles.isEmpty()) {
+			throw new UnsupportedOperationException(
+					JGitText.get().nonBareLinkFilesNotSupported);
+		}
+
+		SubmoduleAddCommand add = git.submoduleAdd().setName(name).setPath(path)
+				.setURI(url);
+		if (monitor != null) {
+			add.setProgressMonitor(monitor);
+		}
+
+		Repository subRepo = add.call();
+		if (revision != null) {
+			try (Git sub = new Git(subRepo)) {
+				sub.checkout().setName(findRef(revision, subRepo)).call();
+			}
+			subRepo.close();
+			git.add().addFilepattern(path).call();
+		}
+		for (CopyFile copyfile : copyfiles) {
+			copyfile.copy();
+			git.add().addFilepattern(copyfile.dest).call();
+		}
+	}
+
+	private static String findRef(String ref, Repository repo)
+			throws IOException {
+		if (!ObjectId.isId(ref)) {
+			Ref r = repo.exactRef(R_REMOTES + DEFAULT_REMOTE_NAME + "/" + ref); //$NON-NLS-1$
+			if (r != null) {
+				return r.getName();
+			}
+		}
+		return ref;
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
index e0a8224..6e943e5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
@@ -9,11 +9,6 @@
  */
 package org.eclipse.jgit.gitrepo;
 
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.eclipse.jgit.lib.Constants.DEFAULT_REMOTE_NAME;
-import static org.eclipse.jgit.lib.Constants.R_REMOTES;
-import static org.eclipse.jgit.lib.Constants.R_TAGS;
-
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -31,34 +26,21 @@
 import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.GitCommand;
-import org.eclipse.jgit.api.SubmoduleAddCommand;
-import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.api.errors.InvalidRefNameException;
-import org.eclipse.jgit.api.errors.JGitInternalException;
-import org.eclipse.jgit.dircache.DirCache;
-import org.eclipse.jgit.dircache.DirCacheBuilder;
-import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.gitrepo.BareSuperprojectWriter.ExtraContent;
 import org.eclipse.jgit.gitrepo.ManifestParser.IncludedFileReader;
-import org.eclipse.jgit.gitrepo.RepoProject.CopyFile;
-import org.eclipse.jgit.gitrepo.RepoProject.LinkFile;
 import org.eclipse.jgit.gitrepo.internal.RepoText;
 import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.lib.CommitBuilder;
-import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RefDatabase;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.RefUpdate.Result;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.treewalk.TreeWalk;
 import org.eclipse.jgit.util.FileUtils;
 
@@ -80,12 +62,7 @@
  * @since 3.4
  */
 public class RepoCommand extends GitCommand<RevCommit> {
-	private static final int LOCK_FAILURE_MAX_RETRIES = 5;
 
-	// Retry exponentially with delays in this range
-	private static final int LOCK_FAILURE_MIN_RETRY_DELAY_MILLIS = 50;
-
-	private static final int LOCK_FAILURE_MAX_RETRY_DELAY_MILLIS = 5000;
 
 	private String manifestPath;
 	private String baseUri;
@@ -93,17 +70,18 @@
 	private String groupsParam;
 	private String branch;
 	private String targetBranch = Constants.HEAD;
-	private boolean recordRemoteBranch = true;
-	private boolean recordSubmoduleLabels = true;
-	private boolean recordShallowSubmodules = true;
 	private PersonIdent author;
 	private RemoteReader callback;
 	private InputStream inputStream;
 	private IncludedFileReader includedReader;
-	private boolean ignoreRemoteFailures = false;
+
+	private BareSuperprojectWriter.BareWriterConfig bareWriterConfig = BareSuperprojectWriter.BareWriterConfig
+			.getDefault();
 
 	private ProgressMonitor monitor;
 
+	private final List<ExtraContent> extraContents = new ArrayList<>();
+
 	/**
 	 * A callback to get ref sha1 of a repository from its uri.
 	 *
@@ -269,14 +247,14 @@
 	}
 
 	@SuppressWarnings("serial")
-	private static class ManifestErrorException extends GitAPIException {
+	static class ManifestErrorException extends GitAPIException {
 		ManifestErrorException(Throwable cause) {
 			super(RepoText.get().invalidManifest, cause);
 		}
 	}
 
 	@SuppressWarnings("serial")
-	private static class RemoteUnavailableException extends GitAPIException {
+	static class RemoteUnavailableException extends GitAPIException {
 		RemoteUnavailableException(String uri) {
 			super(MessageFormat.format(RepoText.get().errorRemoteUnavailable, uri));
 		}
@@ -421,7 +399,7 @@
 	 * @since 4.2
 	 */
 	public RepoCommand setRecordRemoteBranch(boolean enable) {
-		this.recordRemoteBranch = enable;
+		this.bareWriterConfig.recordRemoteBranch = enable;
 		return this;
 	}
 
@@ -436,7 +414,7 @@
 	 * @since 4.4
 	 */
 	public RepoCommand setRecordSubmoduleLabels(boolean enable) {
-		this.recordSubmoduleLabels = enable;
+		this.bareWriterConfig.recordSubmoduleLabels = enable;
 		return this;
 	}
 
@@ -451,7 +429,7 @@
 	 * @since 4.4
 	 */
 	public RepoCommand setRecommendShallow(boolean enable) {
-		this.recordShallowSubmodules = enable;
+		this.bareWriterConfig.recordShallowSubmodules = enable;
 		return this;
 	}
 
@@ -485,7 +463,7 @@
 	 * @since 4.3
 	 */
 	public RepoCommand setIgnoreRemoteFailures(boolean ignore) {
-		this.ignoreRemoteFailures = ignore;
+		this.bareWriterConfig.ignoreRemoteFailures = ignore;
 		return this;
 	}
 
@@ -534,6 +512,22 @@
 		return this;
 	}
 
+	/**
+	 * Create a file with the given content in the destination repository
+	 *
+	 * @param path
+	 *            where to create the file in the destination repository
+	 * @param contents
+	 *            content for the create file
+	 * @return this command
+	 *
+	 * @since 6.1
+	 */
+	public RepoCommand addToDestination(String path, String contents) {
+		this.extraContents.add(new ExtraContent(path, contents));
+		return this;
+	}
+
 	/** {@inheritDoc} */
 	@Override
 	public RevCommit call() throws GitAPIException {
@@ -570,240 +564,18 @@
 		}
 
 		if (repo.isBare()) {
-			if (author == null)
-				author = new PersonIdent(repo);
-			if (callback == null)
-				callback = new DefaultRemoteReader();
 			List<RepoProject> renamedProjects = renameProjects(filteredProjects);
-
-			DirCache index = DirCache.newInCore();
-			ObjectInserter inserter = repo.newObjectInserter();
-
-			try (RevWalk rw = new RevWalk(repo)) {
-				prepareIndex(renamedProjects, index, inserter);
-				ObjectId treeId = index.writeTree(inserter);
-				long prevDelay = 0;
-				for (int i = 0; i < LOCK_FAILURE_MAX_RETRIES - 1; i++) {
-					try {
-						return commitTreeOnCurrentTip(
-							inserter, rw, treeId);
-					} catch (ConcurrentRefUpdateException e) {
-						prevDelay = FileUtils.delay(prevDelay,
-								LOCK_FAILURE_MIN_RETRY_DELAY_MILLIS,
-								LOCK_FAILURE_MAX_RETRY_DELAY_MILLIS);
-						Thread.sleep(prevDelay);
-						repo.getRefDatabase().refresh();
-					}
-				}
-				// In the last try, just propagate the exceptions
-				return commitTreeOnCurrentTip(inserter, rw, treeId);
-			} catch (IOException | InterruptedException e) {
-				throw new ManifestErrorException(e);
-			}
-		}
-		try (Git git = new Git(repo)) {
-			for (RepoProject proj : filteredProjects) {
-				addSubmodule(proj.getName(), proj.getUrl(), proj.getPath(),
-						proj.getRevision(), proj.getCopyFiles(),
-						proj.getLinkFiles(), git);
-			}
-			return git.commit().setMessage(RepoText.get().repoCommitMessage)
-					.call();
-		} catch (IOException e) {
-			throw new ManifestErrorException(e);
-		}
-	}
-
-	private void prepareIndex(List<RepoProject> projects, DirCache index,
-			ObjectInserter inserter) throws IOException, GitAPIException {
-		Config cfg = new Config();
-		StringBuilder attributes = new StringBuilder();
-		DirCacheBuilder builder = index.builder();
-		for (RepoProject proj : projects) {
-			String name = proj.getName();
-			String path = proj.getPath();
-			String url = proj.getUrl();
-			ObjectId objectId;
-			if (ObjectId.isId(proj.getRevision())) {
-				objectId = ObjectId.fromString(proj.getRevision());
-			} else {
-				objectId = callback.sha1(url, proj.getRevision());
-				if (objectId == null && !ignoreRemoteFailures) {
-					throw new RemoteUnavailableException(url);
-				}
-				if (recordRemoteBranch) {
-					// "branch" field is only for non-tag references.
-					// Keep tags in "ref" field as hint for other tools.
-					String field = proj.getRevision().startsWith(R_TAGS) ? "ref" //$NON-NLS-1$
-							: "branch"; //$NON-NLS-1$
-					cfg.setString("submodule", name, field, //$NON-NLS-1$
-							proj.getRevision());
-				}
-
-				if (recordShallowSubmodules
-						&& proj.getRecommendShallow() != null) {
-					// The shallow recommendation is losing information.
-					// As the repo manifests stores the recommended
-					// depth in the 'clone-depth' field, while
-					// git core only uses a binary 'shallow = true/false'
-					// hint, we'll map any depth to 'shallow = true'
-					cfg.setBoolean("submodule", name, "shallow", //$NON-NLS-1$ //$NON-NLS-2$
-							true);
-				}
-			}
-			if (recordSubmoduleLabels) {
-				StringBuilder rec = new StringBuilder();
-				rec.append("/"); //$NON-NLS-1$
-				rec.append(path);
-				for (String group : proj.getGroups()) {
-					rec.append(" "); //$NON-NLS-1$
-					rec.append(group);
-				}
-				rec.append("\n"); //$NON-NLS-1$
-				attributes.append(rec.toString());
-			}
-
-			URI submodUrl = URI.create(url);
-			if (targetUri != null) {
-				submodUrl = relativize(targetUri, submodUrl);
-			}
-			cfg.setString("submodule", name, "path", path); //$NON-NLS-1$ //$NON-NLS-2$
-			cfg.setString("submodule", name, "url", //$NON-NLS-1$ //$NON-NLS-2$
-					submodUrl.toString());
-
-			// create gitlink
-			if (objectId != null) {
-				DirCacheEntry dcEntry = new DirCacheEntry(path);
-				dcEntry.setObjectId(objectId);
-				dcEntry.setFileMode(FileMode.GITLINK);
-				builder.add(dcEntry);
-
-				for (CopyFile copyfile : proj.getCopyFiles()) {
-					RemoteFile rf = callback.readFileWithMode(url,
-							proj.getRevision(), copyfile.src);
-					objectId = inserter.insert(Constants.OBJ_BLOB,
-							rf.getContents());
-					dcEntry = new DirCacheEntry(copyfile.dest);
-					dcEntry.setObjectId(objectId);
-					dcEntry.setFileMode(rf.getFileMode());
-					builder.add(dcEntry);
-				}
-				for (LinkFile linkfile : proj.getLinkFiles()) {
-					String link;
-					if (linkfile.dest.contains("/")) { //$NON-NLS-1$
-						link = FileUtils.relativizeGitPath(
-								linkfile.dest.substring(0,
-										linkfile.dest.lastIndexOf('/')),
-								proj.getPath() + "/" + linkfile.src); //$NON-NLS-1$
-					} else {
-						link = proj.getPath() + "/" + linkfile.src; //$NON-NLS-1$
-					}
-
-					objectId = inserter.insert(Constants.OBJ_BLOB,
-							link.getBytes(UTF_8));
-					dcEntry = new DirCacheEntry(linkfile.dest);
-					dcEntry.setObjectId(objectId);
-					dcEntry.setFileMode(FileMode.SYMLINK);
-					builder.add(dcEntry);
-				}
-			}
-		}
-		String content = cfg.toText();
-
-		// create a new DirCacheEntry for .gitmodules file.
-		DirCacheEntry dcEntry = new DirCacheEntry(
-				Constants.DOT_GIT_MODULES);
-		ObjectId objectId = inserter.insert(Constants.OBJ_BLOB,
-				content.getBytes(UTF_8));
-		dcEntry.setObjectId(objectId);
-		dcEntry.setFileMode(FileMode.REGULAR_FILE);
-		builder.add(dcEntry);
-
-		if (recordSubmoduleLabels) {
-			// create a new DirCacheEntry for .gitattributes file.
-			DirCacheEntry dcEntryAttr = new DirCacheEntry(
-					Constants.DOT_GIT_ATTRIBUTES);
-			ObjectId attrId = inserter.insert(Constants.OBJ_BLOB,
-					attributes.toString().getBytes(UTF_8));
-			dcEntryAttr.setObjectId(attrId);
-			dcEntryAttr.setFileMode(FileMode.REGULAR_FILE);
-			builder.add(dcEntryAttr);
+			BareSuperprojectWriter writer = new BareSuperprojectWriter(repo, targetUri,
+					targetBranch,
+					author == null ? new PersonIdent(repo) : author,
+					callback == null ? new DefaultRemoteReader() : callback,
+					bareWriterConfig, extraContents);
+			return writer.write(renamedProjects);
 		}
 
-		builder.finish();
-	}
 
-	private RevCommit commitTreeOnCurrentTip(ObjectInserter inserter,
-			RevWalk rw, ObjectId treeId)
-			throws IOException, ConcurrentRefUpdateException {
-		ObjectId headId = repo.resolve(targetBranch + "^{commit}"); //$NON-NLS-1$
-		if (headId != null && rw.parseCommit(headId).getTree().getId().equals(treeId)) {
-			// No change. Do nothing.
-			return rw.parseCommit(headId);
-		}
-
-		CommitBuilder commit = new CommitBuilder();
-		commit.setTreeId(treeId);
-		if (headId != null)
-			commit.setParentIds(headId);
-		commit.setAuthor(author);
-		commit.setCommitter(author);
-		commit.setMessage(RepoText.get().repoCommitMessage);
-
-		ObjectId commitId = inserter.insert(commit);
-		inserter.flush();
-
-		RefUpdate ru = repo.updateRef(targetBranch);
-		ru.setNewObjectId(commitId);
-		ru.setExpectedOldObjectId(headId != null ? headId : ObjectId.zeroId());
-		Result rc = ru.update(rw);
-		switch (rc) {
-			case NEW:
-			case FORCED:
-			case FAST_FORWARD:
-				// Successful. Do nothing.
-				break;
-			case REJECTED:
-			case LOCK_FAILURE:
-				throw new ConcurrentRefUpdateException(MessageFormat
-						.format(JGitText.get().cannotLock, targetBranch),
-						ru.getRef(), rc);
-			default:
-				throw new JGitInternalException(MessageFormat.format(
-						JGitText.get().updatingRefFailed,
-						targetBranch, commitId.name(), rc));
-		}
-
-		return rw.parseCommit(commitId);
-	}
-
-	private void addSubmodule(String name, String url, String path,
-			String revision, List<CopyFile> copyfiles, List<LinkFile> linkfiles,
-			Git git) throws GitAPIException, IOException {
-		assert (!repo.isBare());
-		assert (git != null);
-		if (!linkfiles.isEmpty()) {
-			throw new UnsupportedOperationException(
-					JGitText.get().nonBareLinkFilesNotSupported);
-		}
-
-		SubmoduleAddCommand add = git.submoduleAdd().setName(name).setPath(path)
-				.setURI(url);
-		if (monitor != null)
-			add.setProgressMonitor(monitor);
-
-		Repository subRepo = add.call();
-		if (revision != null) {
-			try (Git sub = new Git(subRepo)) {
-				sub.checkout().setName(findRef(revision, subRepo)).call();
-			}
-			subRepo.close();
-			git.add().addFilepattern(path).call();
-		}
-		for (CopyFile copyfile : copyfiles) {
-			copyfile.copy();
-			git.add().addFilepattern(copyfile.dest).call();
-		}
+		RegularSuperprojectWriter writer = new RegularSuperprojectWriter(repo, monitor);
+		return writer.write(filteredProjects);
 	}
 
 	/**
@@ -910,13 +682,4 @@
 		return URI.create(j.toString());
 	}
 
-	private static String findRef(String ref, Repository repo)
-			throws IOException {
-		if (!ObjectId.isId(ref)) {
-			Ref r = repo.exactRef(R_REMOTES + DEFAULT_REMOTE_NAME + "/" + ref); //$NON-NLS-1$
-			if (r != null)
-				return r.getName();
-		}
-		return ref;
-	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
index 61a2e87..2adde0a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -387,6 +387,8 @@
 	/***/ public String inMemoryBufferLimitExceeded;
 	/***/ public String inputDidntMatchLength;
 	/***/ public String inputStreamMustSupportMark;
+	/***/ public String integerValueNotInRange;
+	/***/ public String integerValueNotInRangeSubSection;
 	/***/ public String integerValueOutOfRange;
 	/***/ public String internalRevisionError;
 	/***/ public String internalServerError;
@@ -398,6 +400,7 @@
 	/***/ public String invalidBooleanValue;
 	/***/ public String invalidChannel;
 	/***/ public String invalidCommitParentNumber;
+	/***/ public String invalidCoreAbbrev;
 	/***/ public String invalidDepth;
 	/***/ public String invalidEncoding;
 	/***/ public String invalidEncryption;
@@ -599,6 +602,11 @@
 	/***/ public String pushCertificateInvalidFieldValue;
 	/***/ public String pushCertificateInvalidHeader;
 	/***/ public String pushCertificateInvalidSignature;
+	/***/ public String pushDefaultNothing;
+	/***/ public String pushDefaultNoUpstream;
+	/***/ public String pushDefaultSimple;
+	/***/ public String pushDefaultTriangularUpstream;
+	/***/ public String pushDefaultUnknown;
 	/***/ public String pushIsNotSupportedForBundleTransport;
 	/***/ public String pushNotPermitted;
 	/***/ public String pushOptionsNotSupported;
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
new file mode 100644
index 0000000..39729a4
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java
@@ -0,0 +1,143 @@
+/*
+ * 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 java.util.TreeMap;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.internal.BooleanTriState;
+
+/**
+ * Manages diff tools.
+ */
+public class DiffTools {
+
+	private final DiffToolConfig config;
+
+	private Map<String, ExternalDiffTool> predefinedTools;
+
+	private Map<String, ExternalDiffTool> userDefinedTools;
+
+	/**
+	 * Creates the external diff-tools manager for given repository.
+	 *
+	 * @param repo
+	 *            the repository
+	 */
+	public DiffTools(Repository repo) {
+		config = repo.getConfig().get(DiffToolConfig.KEY);
+		setupPredefinedTools();
+		setupUserDefinedTools();
+	}
+
+	/**
+	 * Compare two versions of a file.
+	 *
+	 * @param newPath
+	 *            the new file path
+	 * @param oldPath
+	 *            the old file path
+	 * @param newId
+	 *            the new object ID
+	 * @param oldId
+	 *            the old object ID
+	 * @param toolName
+	 *            the selected tool name (can be null)
+	 * @param prompt
+	 *            the prompt option
+	 * @param gui
+	 *            the GUI option
+	 * @param trustExitCode
+	 *            the "trust exit code" option
+	 * @return the return code from executed tool
+	 */
+	public int compare(String newPath, String oldPath, String newId,
+			String oldId, String toolName, BooleanTriState prompt,
+			BooleanTriState gui, BooleanTriState trustExitCode) {
+		return 0;
+	}
+
+	/**
+	 * @return the tool names
+	 */
+	public Set<String> getToolNames() {
+		return config.getToolNames();
+	}
+
+	/**
+	 * @return the user defined tools
+	 */
+	public Map<String, ExternalDiffTool> getUserDefinedTools() {
+		return Collections.unmodifiableMap(userDefinedTools);
+	}
+
+	/**
+	 * @return the available predefined tools
+	 */
+	public Map<String, ExternalDiffTool> getAvailableTools() {
+		return Collections.unmodifiableMap(predefinedTools);
+	}
+
+	/**
+	 * @return the NOT available predefined tools
+	 */
+	public Map<String, ExternalDiffTool> getNotAvailableTools() {
+		return Collections.unmodifiableMap(new TreeMap<>());
+	}
+
+	/**
+	 * @param gui
+	 *            use the diff.guitool setting ?
+	 * @return the default tool name
+	 */
+	public String getDefaultToolName(BooleanTriState gui) {
+		return gui != BooleanTriState.UNSET ? "my_gui_tool" //$NON-NLS-1$
+				: "my_default_toolname"; //$NON-NLS-1$
+	}
+
+	/**
+	 * @return is interactive (config prompt enabled) ?
+	 */
+	public boolean isInteractive() {
+		return false;
+	}
+
+	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/ExternalDiffTool.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalDiffTool.java
new file mode 100644
index 0000000..f2d7e82
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalDiffTool.java
@@ -0,0 +1,33 @@
+/*
+ * 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 external tool interface.
+ */
+public interface ExternalDiffTool {
+
+	/**
+	 * @return the tool name
+	 */
+	String getName();
+
+	/**
+	 * @return the tool path
+	 */
+	String getPath();
+
+	/**
+	 * @return the tool command
+	 */
+	String getCommand();
+
+}
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/dfs/DfsBlockCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java
index 54c527c..b30d509 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java
@@ -12,6 +12,10 @@
 package org.eclipse.jgit.internal.storage.dfs;
 
 import java.io.IOException;
+import java.time.Duration;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.concurrent.atomic.AtomicReferenceArray;
@@ -166,6 +170,12 @@
 	/** Limits of cache hot count per pack file extension. */
 	private final int[] cacheHotLimits = new int[PackExt.values().length];
 
+	/** Consumer of loading and eviction events of indexes. */
+	private final DfsBlockCacheConfig.IndexEventConsumer indexEventConsumer;
+
+	/** Stores timestamps of the last eviction of indexes. */
+	private final Map<EvictKey, Long> indexEvictionMap = new ConcurrentHashMap<>();
+
 	@SuppressWarnings("unchecked")
 	private DfsBlockCache(DfsBlockCacheConfig cfg) {
 		tableSize = tableSize(cfg);
@@ -213,6 +223,7 @@
 				cacheHotLimits[i] = DfsBlockCacheConfig.DEFAULT_CACHE_HOT_MAX;
 			}
 		}
+		indexEventConsumer = cfg.getIndexEventConsumer();
 	}
 
 	boolean shouldCopyThroughCache(long length) {
@@ -461,6 +472,7 @@
 					live -= dead.size;
 					getStat(liveBytes, dead.key).addAndGet(-dead.size);
 					getStat(statEvict, dead.key).incrementAndGet();
+					reportIndexEvicted(dead);
 				} while (maxBytes < live);
 				clockHand = prev;
 			}
@@ -515,11 +527,13 @@
 	<T> Ref<T> getOrLoadRef(
 			DfsStreamKey key, long position, RefLoader<T> loader)
 			throws IOException {
+		long start = System.nanoTime();
 		int slot = slot(key, position);
 		HashEntry e1 = table.get(slot);
 		Ref<T> ref = scanRef(e1, key, position);
 		if (ref != null) {
 			getStat(statHit, key).incrementAndGet();
+			reportIndexRequested(ref, true /* cacheHit */, start);
 			return ref;
 		}
 
@@ -532,6 +546,8 @@
 				ref = scanRef(e2, key, position);
 				if (ref != null) {
 					getStat(statHit, key).incrementAndGet();
+					reportIndexRequested(ref, true /* cacheHit */,
+							start);
 					return ref;
 				}
 			}
@@ -556,6 +572,7 @@
 		} finally {
 			regionLock.unlock();
 		}
+		reportIndexRequested(ref, false /* cacheHit */, start);
 		return ref;
 	}
 
@@ -682,8 +699,9 @@
 	}
 
 	private static HashEntry clean(HashEntry top) {
-		while (top != null && top.ref.next == null)
+		while (top != null && top.ref.next == null) {
 			top = top.next;
+		}
 		if (top == null) {
 			return null;
 		}
@@ -691,6 +709,44 @@
 		return n == top.next ? top : new HashEntry(n, top.ref);
 	}
 
+	private void reportIndexRequested(Ref<?> ref, boolean cacheHit,
+			long start) {
+		if (indexEventConsumer == null
+				|| !isIndexOrBitmapExtPos(ref.key.packExtPos)) {
+			return;
+		}
+		EvictKey evictKey = new EvictKey(ref);
+		Long prevEvictedTime = indexEvictionMap.get(evictKey);
+		long now = System.nanoTime();
+		long sinceLastEvictionNanos = prevEvictedTime == null ? 0L
+				: now - prevEvictedTime.longValue();
+		indexEventConsumer.acceptRequestedEvent(ref.key.packExtPos, cacheHit,
+				(now - start) / 1000L /* micros */, ref.size,
+				Duration.ofNanos(sinceLastEvictionNanos));
+	}
+
+	private void reportIndexEvicted(Ref<?> dead) {
+		if (indexEventConsumer == null
+				|| !indexEventConsumer.shouldReportEvictedEvent()
+				|| !isIndexOrBitmapExtPos(dead.key.packExtPos)) {
+			return;
+		}
+		EvictKey evictKey = new EvictKey(dead);
+		Long prevEvictedTime = indexEvictionMap.get(evictKey);
+		long now = System.nanoTime();
+		long sinceLastEvictionNanos = prevEvictedTime == null ? 0L
+				: now - prevEvictedTime.longValue();
+		indexEvictionMap.put(evictKey, Long.valueOf(now));
+		indexEventConsumer.acceptEvictedEvent(dead.key.packExtPos, dead.size,
+				dead.totalHitCount.get(),
+				Duration.ofNanos(sinceLastEvictionNanos));
+	}
+
+	private static boolean isIndexOrBitmapExtPos(int packExtPos) {
+		return packExtPos == PackExt.INDEX.getPosition()
+				|| packExtPos == PackExt.BITMAP_INDEX.getPosition();
+	}
+
 	private static final class HashEntry {
 		/** Next entry in the hash table's chain list. */
 		final HashEntry next;
@@ -712,6 +768,7 @@
 		Ref next;
 
 		private volatile int hotCount;
+		private AtomicInteger totalHitCount = new AtomicInteger();
 
 		Ref(DfsStreamKey key, long position, long size, T v) {
 			this.key = key;
@@ -736,6 +793,7 @@
 			int cap = DfsBlockCache
 					.getInstance().cacheHotLimits[key.packExtPos];
 			hotCount = Math.min(cap, hotCount + 1);
+			totalHitCount.incrementAndGet();
 		}
 
 		void markColder() {
@@ -747,6 +805,34 @@
 		}
 	}
 
+	private static final class EvictKey {
+		private final int keyHash;
+		private final int packExtPos;
+		private final long position;
+
+		EvictKey(Ref<?> ref) {
+			keyHash = ref.key.hash;
+			packExtPos = ref.key.packExtPos;
+			position = ref.position;
+		}
+
+		@Override
+		public boolean equals(Object object) {
+			if (object instanceof EvictKey) {
+				EvictKey other = (EvictKey) object;
+				return keyHash == other.keyHash
+						&& packExtPos == other.packExtPos
+						&& position == other.position;
+			}
+			return false;
+		}
+
+		@Override
+		public int hashCode() {
+			return DfsBlockCache.getInstance().hash(keyHash, position);
+		}
+	}
+
 	@FunctionalInterface
 	interface RefLoader<T> {
 		Ref<T> load() throws IOException;
@@ -763,4 +849,4 @@
 		 */
 		ReadableChannel get() throws IOException;
 	}
-}
+}
\ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java
index 2716f79..69a3705 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java
@@ -18,6 +18,7 @@
 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_STREAM_RATIO;
 
 import java.text.MessageFormat;
+import java.time.Duration;
 import java.util.Collections;
 import java.util.Map;
 import java.util.function.Consumer;
@@ -46,9 +47,10 @@
 	private int concurrencyLevel;
 
 	private Consumer<Long> refLock;
-
 	private Map<PackExt, Integer> cacheHotMap;
 
+	private IndexEventConsumer indexEventConsumer;
+
 	/**
 	 * Create a default configuration.
 	 */
@@ -216,6 +218,28 @@
 	}
 
 	/**
+	 * Get the consumer of cache index events.
+	 *
+	 * @return consumer of cache index events.
+	 */
+	public IndexEventConsumer getIndexEventConsumer() {
+		return indexEventConsumer;
+	}
+
+	/**
+	 * Set the consumer of cache index events.
+	 *
+	 * @param indexEventConsumer
+	 *            consumer of cache index events.
+	 * @return {@code this}
+	 */
+	public DfsBlockCacheConfig setIndexEventConsumer(
+			IndexEventConsumer indexEventConsumer) {
+		this.indexEventConsumer = indexEventConsumer;
+		return this;
+	}
+
+	/**
 	 * Update properties by setting fields from the configuration.
 	 * <p>
 	 * If a property is not defined in the configuration, then it is left
@@ -272,4 +296,52 @@
 		}
 		return this;
 	}
-}
+
+	/** Consumer of DfsBlockCache loading and eviction events for indexes. */
+	public interface IndexEventConsumer {
+		/**
+		 * Accept an event of an index requested. It could be loaded from either
+		 * cache or storage.
+		 *
+		 * @param packExtPos
+		 *            position in {@code PackExt} enum
+		 * @param cacheHit
+		 *            true if an index was already in cache. Otherwise, the
+		 *            index was loaded from storage into the cache in the
+		 *            current request,
+		 * @param loadMicros
+		 *            time to load an index from cache or storage in
+		 *            microseconds
+		 * @param bytes
+		 *            number of bytes loaded
+		 * @param lastEvictionDuration
+		 *            time since last eviction, 0 if was not evicted yet
+		 */
+		void acceptRequestedEvent(int packExtPos, boolean cacheHit,
+				long loadMicros, long bytes, Duration lastEvictionDuration);
+
+		/**
+		 * Accept an event of an index evicted from cache.
+		 *
+		 * @param packExtPos
+		 *            position in {@code PackExt} enum
+		 * @param bytes
+		 *            number of bytes evicted
+		 * @param totalCacheHitCount
+		 *            number of times an index was accessed while in cache
+		 * @param lastEvictionDuration
+		 *            time since last eviction, 0 if was not evicted yet
+		 */
+		default void acceptEvictedEvent(int packExtPos, long bytes,
+				int totalCacheHitCount, Duration lastEvictionDuration) {
+			// Off by default.
+		}
+
+		/**
+		 * @return true if reporting evicted events is enabled.
+		 */
+		default boolean shouldReportEvictedEvent() {
+			return false;
+		}
+	}
+}
\ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java
index bb76df1..f7a2c94 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java
@@ -1061,7 +1061,8 @@
 				}
 				in = new BufferedInputStream(in, bs);
 				bmidx = PackBitmapIndex.read(in, () -> idx(ctx),
-						() -> getReverseIdx(ctx));
+						() -> getReverseIdx(ctx),
+						ctx.getOptions().shouldLoadRevIndexInParallel());
 			} finally {
 				size = rc.position();
 				ctx.stats.readBitmapIdxBytes += size;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java
index 89de534..146f761 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java
@@ -34,6 +34,8 @@
 
 	private int streamPackBufferSize;
 
+	private boolean loadRevIndexInParallel;
+
 	/**
 	 * Create a default reader configuration.
 	 */
@@ -113,6 +115,28 @@
 	}
 
 	/**
+	 * Check if reverse index should be loaded in parallel.
+	 *
+	 * @return true if reverse index is loaded in parallel for bitmap index.
+	 */
+	public boolean shouldLoadRevIndexInParallel() {
+		return loadRevIndexInParallel;
+	}
+
+	/**
+	 * Enable (or disable) parallel loading of reverse index.
+	 *
+	 * @param loadRevIndexInParallel
+	 *            whether to load reverse index in parallel.
+	 * @return {@code this}
+	 */
+	public DfsReaderOptions setLoadRevIndexInParallel(
+			boolean loadRevIndexInParallel) {
+		this.loadRevIndexInParallel = loadRevIndexInParallel;
+		return this;
+	}
+
+	/**
 	 * Update properties by setting fields from the configuration.
 	 * <p>
 	 * If a property is not defined in the configuration, then it is left
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
index 5b6894d..99da222 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
@@ -165,6 +165,15 @@
 				}
 			};
 		}
+
+		@Override
+		public long getApproximateObjectCount() {
+			long count = 0;
+			for (DfsPackDescription p : packs) {
+				count += p.getObjectCount();
+			}
+			return count;
+		}
 	}
 
 	private static class MemPack extends DfsPackDescription {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/LargePackedWholeObject.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/LargePackedWholeObject.java
index b0612f9..cd4f168 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/LargePackedWholeObject.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/LargePackedWholeObject.java
@@ -73,7 +73,6 @@
 	public ObjectStream openStream() throws MissingObjectException, IOException {
 		PackInputStream packIn;
 		// ctx is closed by PackInputStream, or explicitly in the finally block
-		@SuppressWarnings("resource")
 		DfsReader ctx = db.newReader();
 		try {
 			try {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java
index 7dedeb5..094fdc1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java
@@ -263,4 +263,17 @@
 	private AlternateHandle.Id getAlternateId() {
 		return wrapped.getAlternateId();
 	}
+
+	@Override
+	public long getApproximateObjectCount() {
+		long count = 0;
+		for (Pack p : getPacks()) {
+			try {
+				count += p.getObjectCount();
+			} catch (IOException e) {
+				return -1;
+			}
+		}
+		return count;
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java
index f02c861..5152367 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java
@@ -40,6 +40,7 @@
 import org.eclipse.jgit.internal.storage.reftable.ReftableWriter;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.util.FileUtils;
+import org.eclipse.jgit.util.SystemReader;
 
 /**
  * A mutable stack of reftables on local filesystem storage. Not thread-safe.
@@ -527,11 +528,19 @@
 				return false;
 			}
 
+			reload();
 			for (File f : deleteOnSuccess) {
-				Files.delete(f.toPath());
+				try {
+					Files.delete(f.toPath());
+				} catch (IOException e) {
+					// Ignore: this can happen on Windows in case of concurrent processes.
+					// leave the garbage and continue.
+					if (!SystemReader.getInstance().isWindows()) {
+						throw e;
+					}
+				}
 			}
 
-			reload();
 			return true;
 		} finally {
 			if (tmpTable != null) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
index b371557..3e92cdd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
@@ -672,18 +672,20 @@
 
 			if (writeLogs) {
 				List<ReflogEntry> logs = oldDb.getReflogReader(r.getName())
-					.getReverseEntries();
+						.getReverseEntries();
 				Collections.reverse(logs);
 				for (ReflogEntry e : logs) {
 					logWriter.log(r.getName(), e);
 				}
-		}
+			}
 		}
 
 		try (RevWalk rw = new RevWalk(this)) {
 			bru.execute(rw, NullProgressMonitor.INSTANCE);
 		}
 
+		oldDb.close();
+
 		List<String> failed = new ArrayList<>();
 		for (ReceiveCommand cmd : bru.getCommands()) {
 			if (cmd.getResult() != ReceiveCommand.Result.OK) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LooseObjects.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LooseObjects.java
index 33621a1..b9af83d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LooseObjects.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LooseObjects.java
@@ -14,6 +14,7 @@
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.InputStream;
 import java.nio.file.Files;
 import java.nio.file.NoSuchFileException;
 import java.nio.file.StandardCopyOption;
@@ -24,6 +25,8 @@
 import org.eclipse.jgit.internal.storage.file.FileObjectDatabase.InsertLooseObjectResult;
 import org.eclipse.jgit.lib.AbbreviatedObjectId;
 import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectLoader;
@@ -52,15 +55,22 @@
 
 	private final UnpackedObjectCache unpackedObjectCache;
 
+	private final boolean trustFolderStat;
+
 	/**
 	 * Initialize a reference to an on-disk object directory.
 	 *
+	 * @param config
+	 *            configuration for the loose objects handler.
 	 * @param dir
 	 *            the location of the <code>objects</code> directory.
 	 */
-	LooseObjects(File dir) {
+	LooseObjects(Config config, File dir) {
 		directory = dir;
 		unpackedObjectCache = new UnpackedObjectCache();
+		trustFolderStat = config.getBoolean(
+				ConfigConstants.CONFIG_CORE_SECTION,
+				ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);
 	}
 
 	/**
@@ -98,6 +108,19 @@
 	 * @return {@code true} if the specified object is stored as a loose object.
 	 */
 	boolean has(AnyObjectId objectId) {
+		boolean exists = hasWithoutRefresh(objectId);
+		if (trustFolderStat || exists) {
+			return exists;
+		}
+		try (InputStream stream = Files.newInputStream(directory.toPath())) {
+			// refresh directory to work around NFS caching issue
+		} catch (IOException e) {
+			return false;
+		}
+		return hasWithoutRefresh(objectId);
+	}
+
+	private boolean hasWithoutRefresh(AnyObjectId objectId) {
 		return fileFor(objectId).exists();
 	}
 
@@ -183,6 +206,22 @@
 	 */
 	ObjectLoader getObjectLoader(WindowCursor curs, File path, AnyObjectId id)
 			throws IOException {
+		try {
+			return getObjectLoaderWithoutRefresh(curs, path, id);
+		} catch (FileNotFoundException e) {
+			if (trustFolderStat) {
+				throw e;
+			}
+			try (InputStream stream = Files
+					.newInputStream(directory.toPath())) {
+				// refresh directory to work around NFS caching issues
+			}
+			return getObjectLoaderWithoutRefresh(curs, path, id);
+		}
+	}
+
+	private ObjectLoader getObjectLoaderWithoutRefresh(WindowCursor curs,
+			File path, AnyObjectId id) throws IOException {
 		try (FileInputStream in = new FileInputStream(path)) {
 			unpackedObjectCache().add(id);
 			return UnpackedObject.open(in, path, id, curs);
@@ -203,16 +242,34 @@
 	}
 
 	long getSize(WindowCursor curs, AnyObjectId id) throws IOException {
+		try {
+			return getSizeWithoutRefresh(curs, id);
+		} catch (FileNotFoundException noFile) {
+			try {
+				if (trustFolderStat) {
+					throw noFile;
+				}
+				try (InputStream stream = Files
+						.newInputStream(directory.toPath())) {
+					// refresh directory to work around NFS caching issue
+				}
+				return getSizeWithoutRefresh(curs, id);
+			} catch (FileNotFoundException e) {
+				if (fileFor(id).exists()) {
+					throw noFile;
+				}
+				unpackedObjectCache().remove(id);
+				return -1;
+			}
+		}
+	}
+
+	private long getSizeWithoutRefresh(WindowCursor curs, AnyObjectId id)
+			throws IOException {
 		File f = fileFor(id);
 		try (FileInputStream in = new FileInputStream(f)) {
 			unpackedObjectCache().add(id);
 			return UnpackedObject.getSize(in, id, curs);
-		} catch (FileNotFoundException noFile) {
-			if (f.exists()) {
-				throw noFile;
-			}
-			unpackedObjectCache().remove(id);
-			return -1;
 		}
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
index a3ce315..ac3a553 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
@@ -121,7 +121,7 @@
 		File packDirectory = new File(objects, "pack"); //$NON-NLS-1$
 		File preservedDirectory = new File(packDirectory, "preserved"); //$NON-NLS-1$
 		alternatesFile = new File(objects, Constants.INFO_ALTERNATES);
-		loose = new LooseObjects(objects);
+		loose = new LooseObjects(config, objects);
 		packed = new PackDirectory(config, packDirectory);
 		preserved = new PackDirectory(config, preservedDirectory);
 		this.fs = fs;
@@ -213,6 +213,20 @@
 		return packed.getPacks();
 	}
 
+	/** {@inheritDoc} */
+	@Override
+	public long getApproximateObjectCount() {
+		long count = 0;
+		for (Pack p : getPacks()) {
+			try {
+				count += p.getIndex().getObjectCount();
+			} catch (IOException e) {
+				return -1;
+			}
+		}
+		return count;
+	}
+
 	/**
 	 * {@inheritDoc}
 	 * <p>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndex.java
index 8401f07..8fb17fc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndex.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndex.java
@@ -95,7 +95,7 @@
 	 */
 	public static PackBitmapIndex read(InputStream fd, PackIndex packIndex,
 			PackReverseIndex reverseIndex) throws IOException {
-		return new PackBitmapIndexV1(fd, () -> packIndex, () -> reverseIndex);
+		return new PackBitmapIndexV1(fd, packIndex, reverseIndex);
 	}
 
 	/**
@@ -114,6 +114,8 @@
 	 * @param reverseIndexSupplier
 	 *            the supplier for pack reverse index for the corresponding pack
 	 *            file.
+	 * @param loadParallelRevIndex
+	 *            whether reverse index should be loaded in parallel
 	 * @return a copy of the index in-memory.
 	 * @throws java.io.IOException
 	 *             the stream cannot be read.
@@ -122,10 +124,11 @@
 	 */
 	public static PackBitmapIndex read(InputStream fd,
 			SupplierWithIOException<PackIndex> packIndexSupplier,
-			SupplierWithIOException<PackReverseIndex> reverseIndexSupplier)
+			SupplierWithIOException<PackReverseIndex> reverseIndexSupplier,
+			boolean loadParallelRevIndex)
 			throws IOException {
 		return new PackBitmapIndexV1(fd, packIndexSupplier,
-				reverseIndexSupplier);
+				reverseIndexSupplier, loadParallelRevIndex);
 	}
 
 	/** Footer checksum applied on the bottom of the pack file. */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexV1.java
index 6846e3b..21aba3e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexV1.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexV1.java
@@ -17,6 +17,12 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.internal.JGitText;
@@ -40,6 +46,23 @@
 
 	private static final int MAX_XOR_OFFSET = 126;
 
+	private static final ExecutorService executor = Executors
+			.newCachedThreadPool(new ThreadFactory() {
+				private final ThreadFactory baseFactory = Executors
+						.defaultThreadFactory();
+
+				private final AtomicInteger threadNumber = new AtomicInteger(0);
+
+				@Override
+				public Thread newThread(Runnable runnable) {
+					Thread thread = baseFactory.newThread(runnable);
+					thread.setName("JGit-PackBitmapIndexV1-" //$NON-NLS-1$
+							+ threadNumber.getAndIncrement());
+					thread.setDaemon(true);
+					return thread;
+				}
+			});
+
 	private final PackIndex packIndex;
 	private final PackReverseIndex reverseIndex;
 	private final EWAHCompressedBitmap commits;
@@ -49,15 +72,28 @@
 
 	private final ObjectIdOwnerMap<StoredBitmap> bitmaps;
 
+	PackBitmapIndexV1(final InputStream fd, PackIndex packIndex,
+			PackReverseIndex reverseIndex) throws IOException {
+		this(fd, () -> packIndex, () -> reverseIndex, false);
+	}
+
 	PackBitmapIndexV1(final InputStream fd,
 			SupplierWithIOException<PackIndex> packIndexSupplier,
-			SupplierWithIOException<PackReverseIndex> reverseIndexSupplier)
+			SupplierWithIOException<PackReverseIndex> reverseIndexSupplier,
+			boolean loadParallelRevIndex)
 			throws IOException {
 		// An entry is object id, xor offset, flag byte, and a length encoded
 		// bitmap. The object id is an int32 of the nth position sorted by name.
 		super(new ObjectIdOwnerMap<StoredBitmap>());
 		this.bitmaps = getBitmaps();
 
+		// Optionally start loading reverse index in parallel to loading bitmap
+		// from storage.
+		Future<PackReverseIndex> reverseIndexFuture = null;
+		if (loadParallelRevIndex) {
+			reverseIndexFuture = executor.submit(reverseIndexSupplier::get);
+		}
+
 		final byte[] scratch = new byte[32];
 		IO.readFully(fd, scratch, 0, scratch.length);
 
@@ -164,7 +200,18 @@
 			bitmaps.add(sb);
 		}
 
-		this.reverseIndex = reverseIndexSupplier.get();
+		PackReverseIndex computedReverseIndex;
+		if (loadParallelRevIndex && reverseIndexFuture != null) {
+			try {
+				computedReverseIndex = reverseIndexFuture.get();
+			} catch (InterruptedException | ExecutionException e) {
+				// Fallback to loading reverse index through a supplier.
+				computedReverseIndex = reverseIndexSupplier.get();
+			}
+		} else {
+			computedReverseIndex = reverseIndexSupplier.get();
+		}
+		this.reverseIndex = computedReverseIndex;
 	}
 
 	/** {@inheritDoc} */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackDirectory.java
index 22977d3..6a99cb3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackDirectory.java
@@ -65,12 +65,12 @@
 	private static final PackList NO_PACKS = new PackList(FileSnapshot.DIRTY,
 			new Pack[0]);
 
-	private final Config config;
-
 	private final File directory;
 
 	private final AtomicReference<PackList> packList;
 
+	private final boolean trustFolderStat;
+
 	/**
 	 * Initialize a reference to an on-disk 'pack' directory.
 	 *
@@ -80,9 +80,16 @@
 	 *            the location of the {@code pack} directory.
 	 */
 	PackDirectory(Config config, File directory) {
-		this.config = config;
 		this.directory = directory;
 		packList = new AtomicReference<>(NO_PACKS);
+
+		// Whether to trust the pack folder's modification time. If set to false
+		// we will always scan the .git/objects/pack folder to check for new
+		// pack files. If set to true (default) we use the folder's size,
+		// modification time, and key (inode) and assume that no new pack files
+		// can be in this folder if these attributes have not changed.
+		trustFolderStat = config.getBoolean(ConfigConstants.CONFIG_CORE_SECTION,
+				ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);
 	}
 
 	/**
@@ -350,16 +357,6 @@
 	}
 
 	boolean searchPacksAgain(PackList old) {
-		// Whether to trust the pack folder's modification time. If set
-		// to false we will always scan the .git/objects/pack folder to
-		// check for new pack files. If set to true (default) we use the
-		// lastmodified attribute of the folder and assume that no new
-		// pack files can be in this folder if his modification time has
-		// not changed.
-		boolean trustFolderStat = config.getBoolean(
-				ConfigConstants.CONFIG_CORE_SECTION,
-				ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);
-
 		return ((!trustFolderStat) || old.snapshot.isModified(directory))
 				&& old != scanPacks(old);
 	}
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 fbf0ae9..1db4f74 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
@@ -30,10 +30,12 @@
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.InterruptedIOException;
 import java.nio.file.DirectoryNotEmptyException;
 import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
 import java.security.DigestInputStream;
 import java.security.MessageDigest;
@@ -60,6 +62,7 @@
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.CoreConfig.TrustPackedRefsStat;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectIdRef;
 import org.eclipse.jgit.lib.Ref;
@@ -177,6 +180,10 @@
 
 	private List<Integer> retrySleepMs = RETRY_SLEEP_MS;
 
+	private final boolean trustFolderStat;
+
+	private final TrustPackedRefsStat trustPackedRefsStat;
+
 	RefDirectory(FileRepository db) {
 		final FS fs = db.getFS();
 		parent = db;
@@ -188,6 +195,13 @@
 
 		looseRefs.set(RefList.<LooseRef> emptyList());
 		packedRefs.set(NO_PACKED_REFS);
+		trustFolderStat = db.getConfig()
+				.getBoolean(ConfigConstants.CONFIG_CORE_SECTION,
+						ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);
+		trustPackedRefsStat = db.getConfig()
+				.getEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
+						ConfigConstants.CONFIG_KEY_TRUST_PACKED_REFS_STAT,
+						TrustPackedRefsStat.UNSET);
 	}
 
 	Repository getRepository() {
@@ -889,13 +903,30 @@
 	}
 
 	PackedRefList getPackedRefs() throws IOException {
-		boolean trustFolderStat = getRepository().getConfig().getBoolean(
-				ConfigConstants.CONFIG_CORE_SECTION,
-				ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);
-
 		final PackedRefList curList = packedRefs.get();
-		if (trustFolderStat && !curList.snapshot.isModified(packedRefsFile)) {
-			return curList;
+
+		switch (trustPackedRefsStat) {
+		case NEVER:
+			break;
+		case AFTER_OPEN:
+			try (InputStream stream = Files
+					.newInputStream(packedRefsFile.toPath())) {
+				// open the file to refresh attributes (on some NFS clients)
+			} catch (FileNotFoundException | NoSuchFileException e) {
+				// Ignore as packed-refs may not exist
+			}
+			//$FALL-THROUGH$
+		case ALWAYS:
+			if (!curList.snapshot.isModified(packedRefsFile)) {
+				return curList;
+			}
+			break;
+		case UNSET:
+			if (trustFolderStat
+					&& !curList.snapshot.isModified(packedRefsFile)) {
+				return curList;
+			}
+			break;
 		}
 
 		final PackedRefList newList = readPackedRefs();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/io/CancellableDigestOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/io/CancellableDigestOutputStream.java
new file mode 100644
index 0000000..ca2095f
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/io/CancellableDigestOutputStream.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2022, Tencent.
+ *
+ * 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.storage.io;
+
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ProgressMonitor;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.security.MessageDigest;
+
+/**
+ * An OutputStream that keeps a digest and checks every N bytes for
+ * cancellation.
+ */
+public class CancellableDigestOutputStream extends OutputStream {
+
+	/** The OutputStream checks every this value for cancellation **/
+	public static final int BYTES_TO_WRITE_BEFORE_CANCEL_CHECK = 128 * 1024;
+
+	private final ProgressMonitor writeMonitor;
+
+	private final OutputStream out;
+
+	private final MessageDigest md = Constants.newMessageDigest();
+
+	private long count;
+
+	private long checkCancelAt;
+
+	/**
+	 * Initialize a CancellableDigestOutputStream.
+	 *
+	 * @param writeMonitor
+	 *            monitor to update on output progress and check cancel.
+	 * @param out
+	 *            target stream to receive all contents.
+	 */
+	public CancellableDigestOutputStream(ProgressMonitor writeMonitor,
+			OutputStream out) {
+		this.writeMonitor = writeMonitor;
+		this.out = out;
+		this.checkCancelAt = BYTES_TO_WRITE_BEFORE_CANCEL_CHECK;
+	}
+
+	/**
+	 * Get the monitor which is used to update on output progress and check
+	 * cancel.
+	 *
+	 * @return the monitor
+	 */
+	public final ProgressMonitor getWriteMonitor() {
+		return writeMonitor;
+	}
+
+	/**
+	 * Obtain the current SHA-1 digest.
+	 *
+	 * @return SHA-1 digest
+	 */
+	public final byte[] getDigest() {
+		return md.digest();
+	}
+
+	/**
+	 * Get total number of bytes written since stream start.
+	 *
+	 * @return total number of bytes written since stream start.
+	 */
+	public final long length() {
+		return count;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public final void write(int b) throws IOException {
+		if (checkCancelAt <= count) {
+			if (writeMonitor.isCancelled()) {
+				throw new InterruptedIOException();
+			}
+			checkCancelAt = count + BYTES_TO_WRITE_BEFORE_CANCEL_CHECK;
+		}
+
+		out.write(b);
+		md.update((byte) b);
+		count++;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public final void write(byte[] b, int off, int len) throws IOException {
+		while (0 < len) {
+			if (checkCancelAt <= count) {
+				if (writeMonitor.isCancelled()) {
+					throw new InterruptedIOException();
+				}
+				checkCancelAt = count + BYTES_TO_WRITE_BEFORE_CANCEL_CHECK;
+			}
+
+			int n = Math.min(len, BYTES_TO_WRITE_BEFORE_CANCEL_CHECK);
+			out.write(b, off, n);
+			md.update(b, off, n);
+			count += n;
+
+			off += n;
+			len -= n;
+		}
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void flush() throws IOException {
+		out.flush();
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackOutputStream.java
index 7104b94..2d0fe28 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackOutputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackOutputStream.java
@@ -17,10 +17,8 @@
 
 import java.io.IOException;
 import java.io.OutputStream;
-import java.security.MessageDigest;
 
-import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.internal.storage.io.CancellableDigestOutputStream;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.util.NB;
 
@@ -28,25 +26,14 @@
  * Custom output stream to support
  * {@link org.eclipse.jgit.internal.storage.pack.PackWriter}.
  */
-public final class PackOutputStream extends OutputStream {
-	private static final int BYTES_TO_WRITE_BEFORE_CANCEL_CHECK = 128 * 1024;
-
-	private final ProgressMonitor writeMonitor;
-
-	private final OutputStream out;
+public final class PackOutputStream extends CancellableDigestOutputStream {
 
 	private final PackWriter packWriter;
 
-	private final MessageDigest md = Constants.newMessageDigest();
-
-	private long count;
-
 	private final byte[] headerBuffer = new byte[32];
 
 	private final byte[] copyBuffer = new byte[64 << 10];
 
-	private long checkCancelAt;
-
 	private boolean ofsDelta;
 
 	/**
@@ -66,48 +53,8 @@
 	 */
 	public PackOutputStream(final ProgressMonitor writeMonitor,
 			final OutputStream out, final PackWriter pw) {
-		this.writeMonitor = writeMonitor;
-		this.out = out;
+		super(writeMonitor, out);
 		this.packWriter = pw;
-		this.checkCancelAt = BYTES_TO_WRITE_BEFORE_CANCEL_CHECK;
-	}
-
-	/** {@inheritDoc} */
-	@Override
-	public final void write(int b) throws IOException {
-		count++;
-		out.write(b);
-		md.update((byte) b);
-	}
-
-	/** {@inheritDoc} */
-	@Override
-	public final void write(byte[] b, int off, int len)
-			throws IOException {
-		while (0 < len) {
-			final int n = Math.min(len, BYTES_TO_WRITE_BEFORE_CANCEL_CHECK);
-			count += n;
-
-			if (checkCancelAt <= count) {
-				if (writeMonitor.isCancelled()) {
-					throw new IOException(
-							JGitText.get().packingCancelledDuringObjectsWriting);
-				}
-				checkCancelAt = count + BYTES_TO_WRITE_BEFORE_CANCEL_CHECK;
-			}
-
-			out.write(b, off, n);
-			md.update(b, off, n);
-
-			off += n;
-			len -= n;
-		}
-	}
-
-	/** {@inheritDoc} */
-	@Override
-	public void flush() throws IOException {
-		out.flush();
 	}
 
 	final void writeFileHeader(int version, long objectCount)
@@ -160,7 +107,7 @@
 		ObjectToPack b = otp.getDeltaBase();
 		if (b != null && (b.isWritten() & ofsDelta)) { // Non-short-circuit logic is intentional
 			int n = objectHeader(rawLength, OBJ_OFS_DELTA, headerBuffer);
-			n = ofsDelta(count - b.getOffset(), headerBuffer, n);
+			n = ofsDelta(length() - b.getOffset(), headerBuffer, n);
 			write(headerBuffer, 0, n);
 		} else if (otp.isDeltaRepresentation()) {
 			int n = objectHeader(rawLength, OBJ_REF_DELTA, headerBuffer);
@@ -209,20 +156,6 @@
 	}
 
 	void endObject() {
-		writeMonitor.update(1);
-	}
-
-	/**
-	 * Get total number of bytes written since stream start.
-	 *
-	 * @return total number of bytes written since stream start.
-	 */
-	public final long length() {
-		return count;
-	}
-
-	/** @return obtain the current SHA-1 digest. */
-	final byte[] getDigest() {
-		return md.digest();
+		getWriteMonitor().update(1);
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java
index 648d4a1..659ccb8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java
@@ -502,6 +502,98 @@
 	}
 
 	/**
+	 * Converts an OpenSSH time value into a number of seconds. The format is
+	 * defined by OpenSSH as a sequence of (positive) integers with suffixes for
+	 * seconds, minutes, hours, days, and weeks.
+	 *
+	 * @param value
+	 *            to convert
+	 * @return the parsed value as a number of seconds, or -1 if the value is
+	 *         not a valid OpenSSH time value
+	 * @see <a href="https://man.openbsd.org/sshd_config.5#TIME_FORMATS">OpenBSD
+	 *      man 5 sshd_config, section TIME FORMATS</a>
+	 */
+	public static int timeSpec(String value) {
+		if (value == null) {
+			return -1;
+		}
+		try {
+			int length = value.length();
+			int i = 0;
+			int seconds = 0;
+			boolean valueSeen = false;
+			while (i < length) {
+				// Skip whitespace
+				char ch = value.charAt(i);
+				if (Character.isWhitespace(ch)) {
+					i++;
+					continue;
+				}
+				if (ch == '+') {
+					// OpenSSH uses strtol with base 10: a leading plus sign is
+					// allowed.
+					i++;
+				}
+				int val = 0;
+				int j = i;
+				while (j < length) {
+					ch = value.charAt(j++);
+					if (ch >= '0' && ch <= '9') {
+						val = Math.addExact(Math.multiplyExact(val, 10),
+								ch - '0');
+					} else {
+						j--;
+						break;
+					}
+				}
+				if (i == j) {
+					// No digits seen
+					return -1;
+				}
+				i = j;
+				int multiplier = 1;
+				if (i < length) {
+					ch = value.charAt(i++);
+					switch (ch) {
+					case 's':
+					case 'S':
+						break;
+					case 'm':
+					case 'M':
+						multiplier = 60;
+						break;
+					case 'h':
+					case 'H':
+						multiplier = 3600;
+						break;
+					case 'd':
+					case 'D':
+						multiplier = 24 * 3600;
+						break;
+					case 'w':
+					case 'W':
+						multiplier = 7 * 24 * 3600;
+						break;
+					default:
+						if (Character.isWhitespace(ch)) {
+							break;
+						}
+						// Invalid time spec
+						return -1;
+					}
+				}
+				seconds = Math.addExact(seconds,
+						Math.multiplyExact(val, multiplier));
+				valueSeen = true;
+			}
+			return valueSeen ? seconds : -1;
+		} catch (ArithmeticException e) {
+			// Overflow
+			return -1;
+		}
+	}
+
+	/**
 	 * Retrieves the local user name as given in the constructor.
 	 *
 	 * @return the user name
@@ -549,6 +641,7 @@
 			LIST_KEYS.add(SshConstants.GLOBAL_KNOWN_HOSTS_FILE);
 			LIST_KEYS.add(SshConstants.SEND_ENV);
 			LIST_KEYS.add(SshConstants.USER_KNOWN_HOSTS_FILE);
+			LIST_KEYS.add(SshConstants.ADD_KEYS_TO_AGENT); // confirm timeSpec
 		}
 
 		/**
@@ -871,7 +964,8 @@
 			if (options != null) {
 				// HOSTNAME already done above
 				String value = options.get(SshConstants.IDENTITY_AGENT);
-				if (value != null) {
+				if (value != null && !SshConstants.NONE.equals(value)
+						&& !SshConstants.ENV_SSH_AUTH_SOCKET.equals(value)) {
 					value = r.substitute(value, Replacer.DEFAULT_TOKENS, true);
 					value = toFile(value, home).getPath();
 					options.put(SshConstants.IDENTITY_AGENT, value);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbrevConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbrevConfig.java
new file mode 100644
index 0000000..9109cfd
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbrevConfig.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2022,  Matthias Sohn <matthias.sohn@sap.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
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.lib;
+
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
+import static org.eclipse.jgit.lib.TypedConfigGetter.UNSET_INT;
+
+import java.text.MessageFormat;
+
+import org.eclipse.jgit.api.errors.InvalidConfigurationException;
+import org.eclipse.jgit.internal.JGitText;
+
+/**
+ * Git configuration option <a
+ * href=https://git-scm.com/docs/git-config#Documentation/git-config.txt-coreabbrev">
+ * core.abbrev</a>
+ *
+ * @since 6.1
+ */
+public final class AbbrevConfig {
+	private static final String VALUE_NO = "no"; //$NON-NLS-1$
+
+	private static final String VALUE_AUTO = "auto"; //$NON-NLS-1$
+
+	/**
+	 * The minimum value of abbrev
+	 */
+	public static final int MIN_ABBREV = 4;
+
+	/**
+	 * Cap configured core.abbrev to range between minimum of 4 and number of
+	 * hex-digits of a full object id.
+	 *
+	 * @param len
+	 *            configured number of hex-digits to abbreviate object ids to
+	 * @return core.abbrev capped to range between minimum of 4 and number of
+	 *         hex-digits of a full object id
+	 */
+	public static int capAbbrev(int len) {
+		return Math.min(Math.max(MIN_ABBREV, len),
+				Constants.OBJECT_ID_STRING_LENGTH);
+	}
+
+	/**
+	 * No abbreviation
+	 */
+	public final static AbbrevConfig NO = new AbbrevConfig(
+			Constants.OBJECT_ID_STRING_LENGTH);
+
+	/**
+	 * Parse string value of core.abbrev git option for a given repository
+	 *
+	 * @param repo
+	 *            repository
+	 * @return the parsed AbbrevConfig
+	 * @throws InvalidConfigurationException
+	 *             if value of core.abbrev is invalid
+	 */
+	public static AbbrevConfig parseFromConfig(Repository repo)
+			throws InvalidConfigurationException {
+		Config config = repo.getConfig();
+		String value = config.getString(ConfigConstants.CONFIG_CORE_SECTION,
+				null, ConfigConstants.CONFIG_KEY_ABBREV);
+		if (value == null || value.equalsIgnoreCase(VALUE_AUTO)) {
+			return auto(repo);
+		}
+		if (value.equalsIgnoreCase(VALUE_NO)) {
+			return NO;
+		}
+		try {
+			int len = config.getIntInRange(ConfigConstants.CONFIG_CORE_SECTION,
+					ConfigConstants.CONFIG_KEY_ABBREV, MIN_ABBREV,
+					Constants.OBJECT_ID_STRING_LENGTH, UNSET_INT);
+			if (len == UNSET_INT) {
+				// Unset was checked above. If we get UNSET_INT here, then
+				// either the value was UNSET_INT, or it was an invalid value
+				// (not an integer, or out of range), and EGit's
+				// ReportingTypedGetter caught the exception and has logged a
+				// warning. In either case we should fall back to some sane
+				// default.
+				len = OBJECT_ID_ABBREV_STRING_LENGTH;
+			}
+			return new AbbrevConfig(len);
+		} catch (IllegalArgumentException e) {
+			throw new InvalidConfigurationException(MessageFormat
+					.format(JGitText.get().invalidCoreAbbrev, value), e);
+		}
+	}
+
+	/**
+	 * An appropriate value is computed based on the approximate number of
+	 * packed objects in a repository, which hopefully is enough for abbreviated
+	 * object names to stay unique for some time.
+	 *
+	 * @param repo
+	 * @return appropriate value computed based on the approximate number of
+	 *         packed objects in a repository
+	 */
+	private static AbbrevConfig auto(Repository repo) {
+		long count = repo.getObjectDatabase().getApproximateObjectCount();
+		if (count == -1) {
+			return new AbbrevConfig(OBJECT_ID_ABBREV_STRING_LENGTH);
+		}
+		// find msb, round to next power of 2
+		int len = 63 - Long.numberOfLeadingZeros(count) + 1;
+		// With the order of 2^len objects, we expect a collision at
+		// 2^(len/2). But we also care about hex chars, not bits, and
+		// there are 4 bits per hex. So all together we need to divide
+		// by 2; but we also want to round odd numbers up, hence adding
+		// one before dividing.
+		len = (len + 1) / 2;
+		// for small repos use at least fallback length
+		return new AbbrevConfig(Math.max(len, OBJECT_ID_ABBREV_STRING_LENGTH));
+	}
+
+	/**
+	 * All other possible abbreviation lengths. Valid range 4 to number of
+	 * hex-digits of an unabbreviated object id (40 for SHA1 object ids, jgit
+	 * doesn't support SHA256 yet).
+	 */
+	private int abbrev;
+
+	/**
+	 * @param abbrev
+	 */
+	private AbbrevConfig(int abbrev) {
+		this.abbrev = capAbbrev(abbrev);
+	}
+
+	/**
+	 * Get the configured abbreviation length for object ids.
+	 *
+	 * @return the configured abbreviation length for object ids
+	 */
+	public int get() {
+		return abbrev;
+	}
+
+	@Override
+	public String toString() {
+		return Integer.toString(abbrev);
+	}
+}
\ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java
index 6da6f12..aa613d0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java
@@ -138,6 +138,18 @@
 	}
 
 	/**
+	 * Get the remote this branch is configured to push to.
+	 *
+	 * @return the remote this branch is configured to push to, or {@code null}
+	 *         if not defined
+	 * @since 6.1
+	 */
+	public String getPushRemote() {
+		return config.getString(ConfigConstants.CONFIG_BRANCH_SECTION,
+				branchName, ConfigConstants.CONFIG_KEY_PUSH_REMOTE);
+	}
+
+	/**
 	 * Get the name of the upstream branch as it is called on the remote
 	 *
 	 * @return the name of the upstream branch as it is called on the remote, or
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitConfig.java
index 22e1f98..55cc026 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitConfig.java
@@ -18,11 +18,13 @@
 import java.nio.charset.StandardCharsets;
 import java.nio.charset.UnsupportedCharsetException;
 import java.text.MessageFormat;
+import java.util.Locale;
 
 import org.eclipse.jgit.annotations.NonNull;
 import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.Config.ConfigEnum;
 import org.eclipse.jgit.lib.Config.SectionParser;
 import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.IO;
@@ -34,22 +36,76 @@
  * @since 5.13
  */
 public class CommitConfig {
+
 	/**
 	 * Key for {@link Config#get(SectionParser)}.
 	 */
 	public static final Config.SectionParser<CommitConfig> KEY = CommitConfig::new;
 
+	private static final String CUT = " ------------------------ >8 ------------------------\n"; //$NON-NLS-1$
+
+	/**
+	 * How to clean up commit messages when committing.
+	 *
+	 * @since 6.1
+	 */
+	public enum CleanupMode implements ConfigEnum {
+
+		/**
+		 * {@link #WHITESPACE}, additionally remove comment lines.
+		 */
+		STRIP,
+
+		/**
+		 * Remove trailing whitespace and leading and trailing empty lines;
+		 * collapse multiple empty lines to a single one.
+		 */
+		WHITESPACE,
+
+		/**
+		 * Make no changes.
+		 */
+		VERBATIM,
+
+		/**
+		 * Omit everything from the first "scissor" line on, then apply
+		 * {@link #WHITESPACE}.
+		 */
+		SCISSORS,
+
+		/**
+		 * Use {@link #STRIP} for user-edited messages, otherwise
+		 * {@link #WHITESPACE}, unless overridden by a git config setting other
+		 * than DEFAULT.
+		 */
+		DEFAULT;
+
+		@Override
+		public String toConfigValue() {
+			return name().toLowerCase(Locale.ROOT);
+		}
+
+		@Override
+		public boolean matchConfigValue(String in) {
+			return toConfigValue().equals(in);
+		}
+	}
+
 	private final static Charset DEFAULT_COMMIT_MESSAGE_ENCODING = StandardCharsets.UTF_8;
 
 	private String i18nCommitEncoding;
 
 	private String commitTemplatePath;
 
+	private CleanupMode cleanupMode;
+
 	private CommitConfig(Config rc) {
 		commitTemplatePath = rc.getString(ConfigConstants.CONFIG_COMMIT_SECTION,
 				null, ConfigConstants.CONFIG_KEY_COMMIT_TEMPLATE);
 		i18nCommitEncoding = rc.getString(ConfigConstants.CONFIG_SECTION_I18N,
 				null, ConfigConstants.CONFIG_KEY_COMMIT_ENCODING);
+		cleanupMode = rc.getEnum(ConfigConstants.CONFIG_COMMIT_SECTION, null,
+				ConfigConstants.CONFIG_KEY_CLEANUP, CleanupMode.DEFAULT);
 	}
 
 	/**
@@ -75,6 +131,48 @@
 	}
 
 	/**
+	 * Retrieves the {@link CleanupMode} as given by git config
+	 * {@code commit.cleanup}.
+	 *
+	 * @return the {@link CleanupMode}; {@link CleanupMode#DEFAULT} if the git
+	 *         config is not set
+	 * @since 6.1
+	 */
+	@NonNull
+	public CleanupMode getCleanupMode() {
+		return cleanupMode;
+	}
+
+	/**
+	 * Computes a non-default {@link CleanupMode} from the given mode and the
+	 * git config.
+	 *
+	 * @param mode
+	 *            {@link CleanupMode} to resolve
+	 * @param defaultStrip
+	 *            if {@code true} return {@link CleanupMode#STRIP} if the git
+	 *            config is also "default", otherwise return
+	 *            {@link CleanupMode#WHITESPACE}
+	 * @return the {@code mode}, if it is not {@link CleanupMode#DEFAULT},
+	 *         otherwise the resolved mode, which is never
+	 *         {@link CleanupMode#DEFAULT}
+	 * @since 6.1
+	 */
+	@NonNull
+	public CleanupMode resolve(@NonNull CleanupMode mode,
+			boolean defaultStrip) {
+		if (CleanupMode.DEFAULT == mode) {
+			CleanupMode defaultMode = getCleanupMode();
+			if (CleanupMode.DEFAULT == defaultMode) {
+				return defaultStrip ? CleanupMode.STRIP
+						: CleanupMode.WHITESPACE;
+			}
+			return defaultMode;
+		}
+		return mode;
+	}
+
+	/**
 	 * Get the content to the commit template as defined in
 	 * {@code commit.template}. If no {@code i18n.commitEncoding} is specified,
 	 * UTF-8 fallback is used.
@@ -135,4 +233,86 @@
 
 		return commitMessageEncoding;
 	}
+
+	/**
+	 * Processes a text according to the given {@link CleanupMode}.
+	 *
+	 * @param text
+	 *            text to process
+	 * @param mode
+	 *            {@link CleanupMode} to use
+	 * @param commentChar
+	 *            comment character (normally {@code #}) to use if {@code mode}
+	 *            is {@link CleanupMode#STRIP} or {@link CleanupMode#SCISSORS}
+	 * @return the processed text
+	 * @throws IllegalArgumentException
+	 *             if {@code mode} is {@link CleanupMode#DEFAULT} (use
+	 *             {@link #resolve(CleanupMode, boolean)} first)
+	 * @since 6.1
+	 */
+	public static String cleanText(@NonNull String text,
+			@NonNull CleanupMode mode, char commentChar) {
+		String toProcess = text;
+		boolean strip = false;
+		switch (mode) {
+		case VERBATIM:
+			return text;
+		case SCISSORS:
+			String cut = commentChar + CUT;
+			if (text.startsWith(cut)) {
+				return ""; //$NON-NLS-1$
+			}
+			int cutPos = text.indexOf('\n' + cut);
+			if (cutPos >= 0) {
+				toProcess = text.substring(0, cutPos + 1);
+			}
+			break;
+		case STRIP:
+			strip = true;
+			break;
+		case WHITESPACE:
+			break;
+		case DEFAULT:
+		default:
+			// Internal error; no translation
+			throw new IllegalArgumentException("Invalid clean-up mode " + mode); //$NON-NLS-1$
+		}
+		// WHITESPACE
+		StringBuilder result = new StringBuilder();
+		boolean lastWasEmpty = true;
+		for (String line : toProcess.split("\n")) { //$NON-NLS-1$
+			line = line.stripTrailing();
+			if (line.isEmpty()) {
+				if (!lastWasEmpty) {
+					result.append('\n');
+					lastWasEmpty = true;
+				}
+			} else if (!strip || !isComment(line, commentChar)) {
+				lastWasEmpty = false;
+				result.append(line).append('\n');
+			}
+		}
+		int bufferSize = result.length();
+		if (lastWasEmpty && bufferSize > 0) {
+			bufferSize--;
+			result.setLength(bufferSize);
+		}
+		if (bufferSize > 0 && !toProcess.endsWith("\n")) { //$NON-NLS-1$
+			if (result.charAt(bufferSize - 1) == '\n') {
+				result.setLength(bufferSize - 1);
+			}
+		}
+		return result.toString();
+	}
+
+	private static boolean isComment(String text, char commentChar) {
+		int len = text.length();
+		for (int i = 0; i < len; i++) {
+			char ch = text.charAt(i);
+			if (!Character.isWhitespace(ch)) {
+				return ch == commentChar;
+			}
+		}
+		return false;
+	}
 }
\ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
index 1ce3e31..d1d66d2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
@@ -278,6 +278,54 @@
 	}
 
 	/**
+	 * Obtain an integer value from the configuration which must be inside given
+	 * range.
+	 *
+	 * @param section
+	 *            section the key is grouped within.
+	 * @param name
+	 *            name of the key to get.
+	 * @param minValue
+	 *            minimum value
+	 * @param maxValue
+	 *            maximum value
+	 * @param defaultValue
+	 *            default value to return if no value was present.
+	 * @return an integer value from the configuration, or defaultValue.
+	 * @since 6.1
+	 */
+	public int getIntInRange(String section, String name, int minValue,
+			int maxValue, int defaultValue) {
+		return typedGetter.getIntInRange(this, section, null, name, minValue,
+				maxValue, defaultValue);
+	}
+
+	/**
+	 * Obtain an integer value from the configuration which must be inside given
+	 * range.
+	 *
+	 * @param section
+	 *            section the key is grouped within.
+	 * @param subsection
+	 *            subsection name, such a remote or branch name.
+	 * @param name
+	 *            name of the key to get.
+	 * @param minValue
+	 *            minimum value
+	 * @param maxValue
+	 *            maximum value
+	 * @param defaultValue
+	 *            default value to return if no value was present.
+	 * @return an integer value from the configuration, or defaultValue.
+	 * @since 6.1
+	 */
+	public int getIntInRange(String section, String subsection, String name,
+			int minValue, int maxValue, int defaultValue) {
+		return typedGetter.getIntInRange(this, section, subsection, name,
+				minValue, maxValue, defaultValue);
+	}
+
+	/**
 	 * Obtain an integer value from the configuration.
 	 *
 	 * @param section
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 605b44b..2d83549 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 @@
 	/** 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";
 
@@ -145,6 +188,13 @@
 	public static final String CONFIG_TAG_SECTION = "tag";
 
 	/**
+	 * The "cleanup" key
+	 *
+	 * @since 6.1
+	 */
+	public static final String CONFIG_KEY_CLEANUP = "cleanup";
+
+	/**
 	 * The "gpgSign" key
 	 *
 	 * @since 5.2
@@ -285,6 +335,20 @@
 	/** The "remote" key */
 	public static final String CONFIG_KEY_REMOTE = "remote";
 
+	/**
+	 * The "pushRemote" key.
+	 *
+	 * @since 6.1
+	 */
+	public static final String CONFIG_KEY_PUSH_REMOTE = "pushRemote";
+
+	/**
+	 * The "pushDefault" key.
+	 *
+	 * @since 6.1
+	 */
+	public static final String CONFIG_KEY_PUSH_DEFAULT = "pushDefault";
+
 	/** The "merge" key */
 	public static final String CONFIG_KEY_MERGE = "merge";
 
@@ -791,6 +855,34 @@
 	public static final String CONFIG_KEY_SEARCH_FOR_REUSE_TIMEOUT = "searchforreusetimeout";
 
 	/**
+	 * The "push" section.
+	 *
+	 * @since 6.1
+	 */
+	public static final String CONFIG_PUSH_SECTION = "push";
+
+	/**
+	 * The "default" key.
+	 *
+	 * @since 6.1
+	 */
+	public static final String CONFIG_KEY_DEFAULT = "default";
+
+	/**
+	 * The "abbrev" key
+	 *
+	 * @since 6.1
+	 */
+	public static final String CONFIG_KEY_ABBREV = "abbrev";
+
+	/**
+	 * The "trustPackedRefsStat" key
+	 *
+	 * @since 6.1.1
+	 */
+	public static final String CONFIG_KEY_TRUST_PACKED_REFS_STAT = "trustPackedRefsStat";
+
+	/**
 	 * The "pack.preserveOldPacks" key
 	 *
 	 * @since 5.13.2
@@ -803,5 +895,4 @@
 	 * @since 5.13.2
 	 */
 	public static final String CONFIG_KEY_PRUNE_PRESERVED = "prunepreserved";
-
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
index 92367eb..cf2e69d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
@@ -48,6 +48,15 @@
 	 */
 	public static final int OBJECT_ID_STRING_LENGTH = OBJECT_ID_LENGTH * 2;
 
+	/**
+	 * The historic length of an abbreviated Git object hash string. Git 2.11
+	 * changed this static number to a dynamically calculated one that scales
+	 * as the repository grows.
+	 *
+	 * @since 6.1
+	 */
+	public static final int OBJECT_ID_ABBREV_STRING_LENGTH = 7;
+
 	/** Special name for the "HEAD" symbolic-ref. */
 	public static final String HEAD = "HEAD";
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
index f23c6e0..fc82a5f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
@@ -116,6 +116,27 @@
 		ALWAYS
 	}
 
+	/**
+	 * Permissible values for {@code core.trustPackedRefsStat}.
+	 *
+	 * @since 6.1.1
+	 */
+	public enum TrustPackedRefsStat {
+		/** Do not trust file attributes of the packed-refs file. */
+		NEVER,
+
+		/** Trust file attributes of the packed-refs file. */
+		ALWAYS,
+
+		/** Open and close the packed-refs file to refresh its file attributes
+		 * and then trust it. */
+		AFTER_OPEN,
+
+		/** {@code core.trustPackedRefsStat} defaults to this when it is
+		 * not set */
+		UNSET
+	}
+
 	private final int compression;
 
 	private final int packIndexVersion;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
index 9f96bce..8640940 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
@@ -120,6 +120,26 @@
 
 	/** {@inheritDoc} */
 	@Override
+	public int getIntInRange(Config config, String section, String subsection,
+			String name, int minValue, int maxValue, int defaultValue) {
+		int val = getInt(config, section, subsection, name, defaultValue);
+		if ((val >= minValue && val <= maxValue) || val == UNSET_INT) {
+			return val;
+		}
+		if (subsection == null) {
+			throw new IllegalArgumentException(MessageFormat.format(
+					JGitText.get().integerValueNotInRange, section, name,
+					Integer.valueOf(val), Integer.valueOf(minValue),
+					Integer.valueOf(maxValue)));
+		}
+		throw new IllegalArgumentException(MessageFormat.format(
+				JGitText.get().integerValueNotInRangeSubSection, section,
+				subsection, name, Integer.valueOf(val),
+				Integer.valueOf(minValue), Integer.valueOf(maxValue)));
+	}
+
+	/** {@inheritDoc} */
+	@Override
 	public long getLong(Config config, String section, String subsection,
 			String name, long defaultValue) {
 		final String str = config.getString(section, subsection, name);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
index 28ea927..df9fd47 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
@@ -568,6 +568,9 @@
 		if (ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL) {
 			try (SubmoduleWalk smw = new SubmoduleWalk(repository)) {
 				smw.setTree(new DirCacheIterator(dirCache));
+				if (filter != null) {
+					smw.setFilter(filter);
+				}
 				smw.setBuilderFactory(factory);
 				while (smw.next()) {
 					IgnoreSubmoduleMode localIgnoreSubmoduleMode = ignoreSubmoduleMode;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java
index 04262c0..70009cb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java
@@ -155,4 +155,14 @@
 	public ObjectDatabase newCachedDatabase() {
 		return this;
 	}
+
+	/**
+	 * Get a quick, rough count of objects in this repository. Ignores loose
+	 * objects. Returns {@code -1} if an exception occurs.
+	 *
+	 * @return quick, rough count of objects in this repository, {@code -1} if
+	 *         an exception occurs
+	 * @since 6.1
+	 */
+	public abstract long getApproximateObjectCount();
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java
index a2c7381..26c3ff6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java
@@ -10,6 +10,8 @@
 
 package org.eclipse.jgit.lib;
 
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
+
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -76,7 +78,7 @@
 	 */
 	public AbbreviatedObjectId abbreviate(AnyObjectId objectId)
 			throws IOException {
-		return abbreviate(objectId, 7);
+		return abbreviate(objectId, OBJECT_ID_ABBREV_STRING_LENGTH);
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java
index 428a6b9..9371029 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java
@@ -14,6 +14,8 @@
 
 import java.io.Serializable;
 import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.ZoneId;
 import java.util.Date;
 import java.util.Locale;
 import java.util.TimeZone;
@@ -206,6 +208,20 @@
 	}
 
 	/**
+	 * Copy a {@link org.eclipse.jgit.lib.PersonIdent}, but alter the clone's
+	 * time stamp
+	 *
+	 * @param pi
+	 *            original {@link org.eclipse.jgit.lib.PersonIdent}
+	 * @param aWhen
+	 *            local time as Instant
+	 * @since 6.1
+	 */
+	public PersonIdent(PersonIdent pi, Instant aWhen) {
+		this(pi.getName(), pi.getEmailAddress(), aWhen.toEpochMilli(), pi.tzOffset);
+	}
+
+	/**
 	 * Construct a PersonIdent from simple data
 	 *
 	 * @param aName a {@link java.lang.String} object.
@@ -222,6 +238,27 @@
 	}
 
 	/**
+	 * Construct a PersonIdent from simple data
+	 *
+	 * @param aName
+	 *            a {@link java.lang.String} object.
+	 * @param aEmailAddress
+	 *            a {@link java.lang.String} object.
+	 * @param aWhen
+	 *            local time stamp
+	 * @param zoneId
+	 *            time zone id
+	 * @since 6.1
+	 */
+	public PersonIdent(final String aName, String aEmailAddress, Instant aWhen,
+			ZoneId zoneId) {
+		this(aName, aEmailAddress, aWhen.toEpochMilli(),
+				TimeZone.getTimeZone(zoneId)
+						.getOffset(aWhen
+				.toEpochMilli()) / (60 * 1000));
+	}
+
+	/**
 	 * Copy a PersonIdent, but alter the clone's time stamp
 	 *
 	 * @param pi
@@ -304,6 +341,16 @@
 	}
 
 	/**
+	 * Get when attribute as instant
+	 *
+	 * @return timestamp
+	 * @since 6.1
+	 */
+	public Instant getWhenAsInstant() {
+		return Instant.ofEpochMilli(when);
+	}
+
+	/**
 	 * Get this person's declared time zone
 	 *
 	 * @return this person's declared time zone; null if time zone is unknown.
@@ -313,6 +360,16 @@
 	}
 
 	/**
+	 * Get the time zone id
+	 *
+	 * @return the time zone id
+	 * @since 6.1
+	 */
+	public ZoneId getZoneId() {
+		return getTimeZone().toZoneId();
+	}
+
+	/**
 	 * Get this person's declared time zone as minutes east of UTC.
 	 *
 	 * @return this person's declared time zone as minutes east of UTC. If the
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TypedConfigGetter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TypedConfigGetter.java
index 0f2f6cf..c4eb8f1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TypedConfigGetter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TypedConfigGetter.java
@@ -29,6 +29,13 @@
 public interface TypedConfigGetter {
 
 	/**
+	 * Use {@code Integer#MIN_VALUE} as unset int value
+	 *
+	 * @since 6.1
+	 */
+	public static final int UNSET_INT = Integer.MIN_VALUE;
+
+	/**
 	 * Get a boolean value from a git {@link Config}.
 	 *
 	 * @param config
@@ -87,6 +94,32 @@
 			int defaultValue);
 
 	/**
+	 * Obtain an integer value from a git {@link Config} which must be in given
+	 * range.
+	 *
+	 * @param config
+	 *            to get the value from
+	 * @param section
+	 *            section the key is grouped within.
+	 * @param subsection
+	 *            subsection name, such a remote or branch name.
+	 * @param name
+	 *            name of the key to get.
+	 * @param minValue
+	 *            minimal value
+	 * @param maxValue
+	 *            maximum value
+	 * @param defaultValue
+	 *            default value to return if no value was present. Use
+	 *            {@code #UNSET_INT} to set the default to unset.
+	 * @return an integer value from the configuration, or defaultValue.
+	 *         {@code #UNSET_INT} if unset.
+	 * @since 6.1
+	 */
+	int getIntInRange(Config config, String section, String subsection,
+			String name, int minValue, int maxValue, int defaultValue);
+
+	/**
 	 * Obtain a long value from a git {@link Config}.
 	 *
 	 * @param config
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BooleanTriState.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BooleanTriState.java
new file mode 100644
index 0000000..44d3bb3
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BooleanTriState.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2021 Simeon Andreev <simeon.danailov.andreev@gmail.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
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.lib.internal;
+
+/**
+ * A boolean value that can also have an unset state.
+ */
+public enum BooleanTriState {
+	/**
+	 * Value equivalent to {@code true}.
+	 */
+	TRUE,
+	/**
+	 * Value equivalent to {@code false}.
+	 */
+	FALSE,
+	/**
+	 * Value is not set.
+	 */
+	UNSET;
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java
index f7966a2..e0c083f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java
@@ -92,36 +92,62 @@
 	}
 
 	/**
-	 * Add section with conflicting paths to merge message.
+	 * Add section with conflicting paths to merge message. Lines are prefixed
+	 * with a hash.
 	 *
 	 * @param message
 	 *            the original merge message
 	 * @param conflictingPaths
 	 *            the paths with conflicts
 	 * @return merge message with conflicting paths added
+	 * @deprecated since 6.1; use
+	 *             {@link #formatWithConflicts(String, Iterable, char)} instead
 	 */
+	@Deprecated
 	public String formatWithConflicts(String message,
 			List<String> conflictingPaths) {
+		return formatWithConflicts(message, conflictingPaths, '#');
+	}
+
+	/**
+	 * Add section with conflicting paths to merge message.
+	 *
+	 * @param message
+	 *            the original merge message
+	 * @param conflictingPaths
+	 *            the paths with conflicts
+	 * @param commentChar
+	 *            comment character to use for prefixing the conflict lines
+	 * @return merge message with conflicting paths added
+	 * @since 6.1
+	 */
+	public String formatWithConflicts(String message,
+			Iterable<String> conflictingPaths, char commentChar) {
 		StringBuilder sb = new StringBuilder();
 		String[] lines = message.split("\n"); //$NON-NLS-1$
 		int firstFooterLine = ChangeIdUtil.indexOfFirstFooterLine(lines);
-		for (int i = 0; i < firstFooterLine; i++)
+		for (int i = 0; i < firstFooterLine; i++) {
 			sb.append(lines[i]).append('\n');
-		if (firstFooterLine == lines.length && message.length() != 0)
+		}
+		if (firstFooterLine == lines.length && message.length() != 0) {
 			sb.append('\n');
-		addConflictsMessage(conflictingPaths, sb);
-		if (firstFooterLine < lines.length)
+		}
+		addConflictsMessage(conflictingPaths, sb, commentChar);
+		if (firstFooterLine < lines.length) {
 			sb.append('\n');
-		for (int i = firstFooterLine; i < lines.length; i++)
+		}
+		for (int i = firstFooterLine; i < lines.length; i++) {
 			sb.append(lines[i]).append('\n');
+		}
 		return sb.toString();
 	}
 
-	private static void addConflictsMessage(List<String> conflictingPaths,
-			StringBuilder sb) {
-		sb.append("Conflicts:\n"); //$NON-NLS-1$
+	private static void addConflictsMessage(Iterable<String> conflictingPaths,
+			StringBuilder sb, char commentChar) {
+		sb.append(commentChar).append(" Conflicts:\n"); //$NON-NLS-1$
 		for (String conflictingPath : conflictingPaths) {
-			sb.append('\t').append(conflictingPath).append('\n');
+			sb.append(commentChar).append('\t').append(conflictingPath)
+					.append('\n');
 		}
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
index 7767662..b9ab1d1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
@@ -3,7 +3,7 @@
  * Copyright (C) 2010-2012, Matthias Sohn <matthias.sohn@sap.com>
  * Copyright (C) 2012, Research In Motion Limited
  * Copyright (C) 2017, Obeo (mathieu.cartaud@obeo.fr)
- * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2018, 2022 Thomas Wolf <thomas.wolf@paranor.ch> 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
@@ -276,11 +276,15 @@
 	private ContentMergeStrategy contentStrategy = ContentMergeStrategy.CONFLICT;
 
 	/**
-	 * Keeps {@link CheckoutMetadata} for {@link #checkout()} and
-	 * {@link #cleanUp()}.
+	 * Keeps {@link CheckoutMetadata} for {@link #checkout()}.
 	 */
 	private Map<String, CheckoutMetadata> checkoutMetadata;
 
+	/**
+	 * Keeps {@link CheckoutMetadata} for {@link #cleanUp()}.
+	 */
+	private Map<String, CheckoutMetadata> cleanupMetadata;
+
 	private static MergeAlgorithm getMergeAlgorithm(Config config) {
 		SupportedAlgorithm diffAlg = config.getEnum(
 				CONFIG_DIFF_SECTION, null, CONFIG_KEY_ALGORITHM,
@@ -383,12 +387,14 @@
 		}
 		if (!inCore) {
 			checkoutMetadata = new HashMap<>();
+			cleanupMetadata = new HashMap<>();
 		}
 		try {
 			return mergeTrees(mergeBase(), sourceTrees[0], sourceTrees[1],
 					false);
 		} finally {
 			checkoutMetadata = null;
+			cleanupMetadata = null;
 			if (implicitDirCache) {
 				dircache.unlock();
 			}
@@ -447,7 +453,7 @@
 			DirCacheEntry entry = dc.getEntry(mpath);
 			if (entry != null) {
 				DirCacheCheckout.checkoutEntry(db, entry, reader, false,
-						checkoutMetadata.get(mpath));
+						cleanupMetadata.get(mpath));
 			}
 			mpathsIt.remove();
 		}
@@ -501,22 +507,26 @@
 	 * Remembers the {@link CheckoutMetadata} for the given path; it may be
 	 * needed in {@link #checkout()} or in {@link #cleanUp()}.
 	 *
+	 * @param map
+	 *            to add the metadata to
 	 * @param path
 	 *            of the current node
 	 * @param attributes
-	 *            for the current node
+	 *            to use for determining the metadata
 	 * @throws IOException
 	 *             if the smudge filter cannot be determined
-	 * @since 5.1
+	 * @since 6.1
 	 */
-	protected void addCheckoutMetadata(String path, Attributes attributes)
+	protected void addCheckoutMetadata(Map<String, CheckoutMetadata> map,
+			String path, Attributes attributes)
 			throws IOException {
-		if (checkoutMetadata != null) {
+		if (map != null) {
 			EolStreamType eol = EolStreamTypeUtil.detectStreamType(
-					OperationType.CHECKOUT_OP, workingTreeOptions, attributes);
+					OperationType.CHECKOUT_OP, workingTreeOptions,
+					attributes);
 			CheckoutMetadata data = new CheckoutMetadata(eol,
-					tw.getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE));
-			checkoutMetadata.put(path, data);
+					tw.getSmudgeCommand(attributes));
+			map.put(path, data);
 		}
 	}
 
@@ -529,15 +539,17 @@
 	 * @param entry
 	 *            to add
 	 * @param attributes
-	 *            for the current entry
+	 *            the {@link Attributes} of the trees
 	 * @throws IOException
 	 *             if the {@link CheckoutMetadata} cannot be determined
-	 * @since 5.1
+	 * @since 6.1
 	 */
 	protected void addToCheckout(String path, DirCacheEntry entry,
-			Attributes attributes) throws IOException {
+			Attributes[] attributes)
+			throws IOException {
 		toBeCheckedOut.put(path, entry);
-		addCheckoutMetadata(path, attributes);
+		addCheckoutMetadata(cleanupMetadata, path, attributes[T_OURS]);
+		addCheckoutMetadata(checkoutMetadata, path, attributes[T_THEIRS]);
 	}
 
 	/**
@@ -549,7 +561,7 @@
 	 * @param isFile
 	 *            whether it is a file
 	 * @param attributes
-	 *            for the entry
+	 *            to use for determining the {@link CheckoutMetadata}
 	 * @throws IOException
 	 *             if the {@link CheckoutMetadata} cannot be determined
 	 * @since 5.1
@@ -558,7 +570,7 @@
 			Attributes attributes) throws IOException {
 		toBeDeleted.add(path);
 		if (isFile) {
-			addCheckoutMetadata(path, attributes);
+			addCheckoutMetadata(cleanupMetadata, path, attributes);
 		}
 	}
 
@@ -599,7 +611,7 @@
 	 *            see
 	 *            {@link org.eclipse.jgit.merge.ResolveMerger#mergeTrees(AbstractTreeIterator, RevTree, RevTree, boolean)}
 	 * @param attributes
-	 *            the attributes defined for this entry
+	 *            the {@link Attributes} for the three trees
 	 * @return <code>false</code> if the merge will fail because the index entry
 	 *         didn't match ours or the working-dir file was dirty and a
 	 *         conflict occurred
@@ -607,12 +619,12 @@
 	 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
 	 * @throws org.eclipse.jgit.errors.CorruptObjectException
 	 * @throws java.io.IOException
-	 * @since 4.9
+	 * @since 6.1
 	 */
 	protected boolean processEntry(CanonicalTreeParser base,
 			CanonicalTreeParser ours, CanonicalTreeParser theirs,
 			DirCacheBuildIterator index, WorkingTreeIterator work,
-			boolean ignoreConflicts, Attributes attributes)
+			boolean ignoreConflicts, Attributes[] attributes)
 			throws MissingObjectException, IncorrectObjectTypeException,
 			CorruptObjectException, IOException {
 		enterSubtree = true;
@@ -729,7 +741,7 @@
 				// Base, ours, and theirs all contain a folder: don't delete
 				return true;
 			}
-			addDeletion(tw.getPathString(), nonTree(modeO), attributes);
+			addDeletion(tw.getPathString(), nonTree(modeO), attributes[T_OURS]);
 			return true;
 		}
 
@@ -772,7 +784,7 @@
 		if (nonTree(modeO) && nonTree(modeT)) {
 			// Check worktree before modifying files
 			boolean worktreeDirty = isWorktreeDirty(work, ourDce);
-			if (!attributes.canBeContentMerged() && worktreeDirty) {
+			if (!attributes[T_OURS].canBeContentMerged() && worktreeDirty) {
 				return false;
 			}
 
@@ -791,7 +803,7 @@
 				mergeResults.put(tw.getPathString(), result);
 				unmergedPaths.add(tw.getPathString());
 				return true;
-			} else if (!attributes.canBeContentMerged()) {
+			} else if (!attributes[T_OURS].canBeContentMerged()) {
 				// File marked as binary
 				switch (getContentMergeStrategy()) {
 				case OURS:
@@ -842,13 +854,16 @@
 			if (ignoreConflicts) {
 				result.setContainsConflicts(false);
 			}
-			updateIndex(base, ours, theirs, result, attributes);
+			updateIndex(base, ours, theirs, result, attributes[T_OURS]);
 			String currentPath = tw.getPathString();
 			if (result.containsConflicts() && !ignoreConflicts) {
 				unmergedPaths.add(currentPath);
 			}
 			modifiedFiles.add(currentPath);
-			addCheckoutMetadata(currentPath, attributes);
+			addCheckoutMetadata(cleanupMetadata, currentPath,
+					attributes[T_OURS]);
+			addCheckoutMetadata(checkoutMetadata, currentPath,
+					attributes[T_THEIRS]);
 		} else if (modeO != modeT) {
 			// OURS or THEIRS has been deleted
 			if (((modeO != 0 && !tw.idEqual(T_BASE, T_OURS)) || (modeT != 0 && !tw
@@ -881,7 +896,8 @@
 						// markers). But also stage 0 of the index is filled
 						// with that content.
 						result.setContainsConflicts(false);
-						updateIndex(base, ours, theirs, result, attributes);
+						updateIndex(base, ours, theirs, result,
+								attributes[T_OURS]);
 					} else {
 						add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH,
 								0);
@@ -896,11 +912,9 @@
 							if (isWorktreeDirty(work, ourDce)) {
 								return false;
 							}
-							if (nonTree(modeT)) {
-								if (e != null) {
-									addToCheckout(tw.getPathString(), e,
-											attributes);
-								}
+							if (nonTree(modeT) && e != null) {
+								addToCheckout(tw.getPathString(), e,
+										attributes);
 							}
 						}
 
@@ -945,14 +959,16 @@
 	 */
 	private MergeResult<RawText> contentMerge(CanonicalTreeParser base,
 			CanonicalTreeParser ours, CanonicalTreeParser theirs,
-			Attributes attributes, ContentMergeStrategy strategy)
+			Attributes[] attributes, ContentMergeStrategy strategy)
 			throws BinaryBlobException, IOException {
+		// TW: The attributes here are used to determine the LFS smudge filter.
+		// Is doing a content merge on LFS items really a good idea??
 		RawText baseText = base == null ? RawText.EMPTY_TEXT
-				: getRawText(base.getEntryObjectId(), attributes);
+				: getRawText(base.getEntryObjectId(), attributes[T_BASE]);
 		RawText ourText = ours == null ? RawText.EMPTY_TEXT
-				: getRawText(ours.getEntryObjectId(), attributes);
+				: getRawText(ours.getEntryObjectId(), attributes[T_OURS]);
 		RawText theirsText = theirs == null ? RawText.EMPTY_TEXT
-				: getRawText(theirs.getEntryObjectId(), attributes);
+				: getRawText(theirs.getEntryObjectId(), attributes[T_THEIRS]);
 		mergeAlgorithm.setContentMergeStrategy(strategy);
 		return mergeAlgorithm.merge(RawTextComparator.DEFAULT, baseText,
 				ourText, theirsText);
@@ -1342,7 +1358,7 @@
 
 		tw = new NameConflictTreeWalk(db, reader);
 		tw.addTree(baseTree);
-		tw.addTree(headTree);
+		tw.setHead(tw.addTree(headTree));
 		tw.addTree(mergeTree);
 		int dciPos = tw.addTree(buildIt);
 		if (workingTreeIterator != null) {
@@ -1403,6 +1419,13 @@
 		boolean hasAttributeNodeProvider = treeWalk
 				.getAttributesNodeProvider() != null;
 		while (treeWalk.next()) {
+			Attributes[] attributes = { NO_ATTRIBUTES, NO_ATTRIBUTES,
+					NO_ATTRIBUTES };
+			if (hasAttributeNodeProvider) {
+				attributes[T_BASE] = treeWalk.getAttributes(T_BASE);
+				attributes[T_OURS] = treeWalk.getAttributes(T_OURS);
+				attributes[T_THEIRS] = treeWalk.getAttributes(T_THEIRS);
+			}
 			if (!processEntry(
 					treeWalk.getTree(T_BASE, CanonicalTreeParser.class),
 					treeWalk.getTree(T_OURS, CanonicalTreeParser.class),
@@ -1410,9 +1433,7 @@
 					treeWalk.getTree(T_INDEX, DirCacheBuildIterator.class),
 					hasWorkingTreeIterator ? treeWalk.getTree(T_FILE,
 							WorkingTreeIterator.class) : null,
-					ignoreConflicts, hasAttributeNodeProvider
-							? treeWalk.getAttributes()
-							: NO_ATTRIBUTES)) {
+					ignoreConflicts, attributes)) {
 				cleanUp();
 				return false;
 			}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java
index e6f9580..4e48a5c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
+ * Copyright (C) 2008, 2022 Shawn O. Pearce <spearce@spearce.org> 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
@@ -139,7 +139,7 @@
 	 *            the repository the walker will obtain data from.
 	 */
 	public ObjectWalk(Repository repo) {
-		this(repo.newObjectReader());
+		this(repo.newObjectReader(), true);
 	}
 
 	/**
@@ -151,7 +151,11 @@
 	 *            required.
 	 */
 	public ObjectWalk(ObjectReader or) {
-		super(or);
+		this(or, false);
+	}
+
+	private ObjectWalk(ObjectReader or, boolean closeReader) {
+		super(or, closeReader);
 		setRetainBody(false);
 		rootObjects = new ArrayList<>();
 		pendingObjects = new BlockObjQueue();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
index 8d571f5..a50eaf1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
@@ -215,7 +215,7 @@
 		this(or, false);
 	}
 
-	private RevWalk(ObjectReader or, boolean closeReader) {
+	RevWalk(ObjectReader or, boolean closeReader) {
 		reader = or;
 		idBuffer = new MutableObjectId();
 		objects = new ObjectIdOwnerMap<>();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConfig.java
index fda7a81..c8774d5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConfig.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017, David Pursehouse <david.pursehouse@gmail.com> and others
+ * Copyright (C) 2017, 2022 David Pursehouse <david.pursehouse@gmail.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
@@ -10,7 +10,10 @@
 
 package org.eclipse.jgit.transport;
 
+import java.util.Locale;
+
 import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.util.StringUtils;
 
 /**
@@ -19,8 +22,9 @@
  * @since 4.9
  */
 public class PushConfig {
+
 	/**
-	 * Config values for push.recurseSubmodules.
+	 * Git config values for {@code push.recurseSubmodules}.
 	 */
 	public enum PushRecurseSubmodulesMode implements Config.ConfigEnum {
 		/**
@@ -59,4 +63,100 @@
 					|| configValue.equalsIgnoreCase(s);
 		}
 	}
+
+	/**
+	 * Git config values for {@code push.default}.
+	 *
+	 * @since 6.1
+	 */
+	public enum PushDefault implements Config.ConfigEnum {
+
+		/**
+		 * Do not push if there are no explicit refspecs.
+		 */
+		NOTHING,
+
+		/**
+		 * Push the current branch to an upstream branch of the same name.
+		 */
+		CURRENT,
+
+		/**
+		 * Push the current branch to an upstream branch determined by git
+		 * config {@code branch.<currentBranch>.merge}.
+		 */
+		UPSTREAM("tracking"), //$NON-NLS-1$
+
+		/**
+		 * Like {@link #UPSTREAM}, but only if the upstream name is the same as
+		 * the name of the current local branch.
+		 */
+		SIMPLE,
+
+		/**
+		 * Push all current local branches that match a configured push refspec
+		 * of the remote configuration.
+		 */
+		MATCHING;
+
+		private final String alias;
+
+		private PushDefault() {
+			alias = null;
+		}
+
+		private PushDefault(String alias) {
+			this.alias = alias;
+		}
+
+		@Override
+		public String toConfigValue() {
+			return name().toLowerCase(Locale.ROOT);
+		}
+
+		@Override
+		public boolean matchConfigValue(String in) {
+			return toConfigValue().equalsIgnoreCase(in)
+					|| (alias != null && alias.equalsIgnoreCase(in));
+		}
+	}
+
+	private final PushRecurseSubmodulesMode recurseSubmodules;
+
+	private final PushDefault pushDefault;
+
+	/**
+	 * Creates a new instance.
+	 *
+	 * @param config
+	 *            {@link Config} to fill the {@link PushConfig} from
+	 * @since 6.1
+	 */
+	public PushConfig(Config config) {
+		recurseSubmodules = config.getEnum(ConfigConstants.CONFIG_PUSH_SECTION,
+				null, ConfigConstants.CONFIG_KEY_RECURSE_SUBMODULES,
+				PushRecurseSubmodulesMode.NO);
+		pushDefault = config.getEnum(ConfigConstants.CONFIG_PUSH_SECTION, null,
+				ConfigConstants.CONFIG_KEY_DEFAULT, PushDefault.SIMPLE);
+	}
+
+	/**
+	 * Retrieves the value of git config {@code push.recurseSubmodules}.
+	 *
+	 * @return the value
+	 * @since 6.1
+	 */
+	public PushRecurseSubmodulesMode getRecurseSubmodules() {
+		return recurseSubmodules;
+	}
+
+	/**
+	 * Retrieves the value of git config {@code push.default}.
+	 *
+	 * @return the value
+	 * @since 6.1
+	 */
+	public PushDefault getPushDefault() {
+		return pushDefault;
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
index a244c55..942dad4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
@@ -18,11 +18,15 @@
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
+import org.eclipse.jgit.api.errors.AbortedByHookException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.errors.NotSupportedException;
 import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.hooks.PrePushHook;
 import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
@@ -58,6 +62,8 @@
 	/** A list of option strings associated with this push */
 	private List<String> pushOptions;
 
+	private final PrePushHook prePush;
+
 	/**
 	 * Create process for specified transport and refs updates specification.
 	 *
@@ -66,12 +72,14 @@
 	 *            connection.
 	 * @param toPush
 	 *            specification of refs updates (and local tracking branches).
-	 *
+	 * @param prePush
+	 *            {@link PrePushHook} to run after the remote advertisement has
+	 *            been gotten
 	 * @throws TransportException
 	 */
-	PushProcess(final Transport transport,
-			final Collection<RemoteRefUpdate> toPush) throws TransportException {
-		this(transport, toPush, null);
+	PushProcess(Transport transport, Collection<RemoteRefUpdate> toPush,
+			PrePushHook prePush) throws TransportException {
+		this(transport, toPush, prePush, null);
 	}
 
 	/**
@@ -82,16 +90,19 @@
 	 *            connection.
 	 * @param toPush
 	 *            specification of refs updates (and local tracking branches).
+	 * @param prePush
+	 *            {@link PrePushHook} to run after the remote advertisement has
+	 *            been gotten
 	 * @param out
 	 *            OutputStream to write messages to
 	 * @throws TransportException
 	 */
-	PushProcess(final Transport transport,
-			final Collection<RemoteRefUpdate> toPush, OutputStream out)
-			throws TransportException {
+	PushProcess(Transport transport, Collection<RemoteRefUpdate> toPush,
+			PrePushHook prePush, OutputStream out) throws TransportException {
 		this.walker = new RevWalk(transport.local);
 		this.transport = transport;
 		this.toPush = new LinkedHashMap<>();
+		this.prePush = prePush;
 		this.out = out;
 		this.pushOptions = transport.getPushOptions();
 		for (RemoteRefUpdate rru : toPush) {
@@ -129,10 +140,38 @@
 				res.setAdvertisedRefs(transport.getURI(), connection
 						.getRefsMap());
 				res.peerUserAgent = connection.getPeerUserAgent();
-				res.setRemoteUpdates(toPush);
 				monitor.endTask();
 
+				Map<String, RemoteRefUpdate> expanded = expandMatching();
+				toPush.clear();
+				toPush.putAll(expanded);
+
+				res.setRemoteUpdates(toPush);
 				final Map<String, RemoteRefUpdate> preprocessed = prepareRemoteUpdates();
+				List<RemoteRefUpdate> willBeAttempted = preprocessed.values()
+						.stream().filter(u -> {
+							switch (u.getStatus()) {
+							case NON_EXISTING:
+							case REJECTED_NODELETE:
+							case REJECTED_NONFASTFORWARD:
+							case REJECTED_OTHER_REASON:
+							case REJECTED_REMOTE_CHANGED:
+							case UP_TO_DATE:
+								return false;
+							default:
+								return true;
+							}
+						}).collect(Collectors.toList());
+				if (!willBeAttempted.isEmpty()) {
+					if (prePush != null) {
+						try {
+							prePush.setRefs(willBeAttempted);
+							prePush.call();
+						} catch (AbortedByHookException | IOException e) {
+							throw new TransportException(e.getMessage(), e);
+						}
+					}
+				}
 				if (transport.isDryRun())
 					modifyUpdatesForDryRun();
 				else if (!preprocessed.isEmpty())
@@ -201,25 +240,8 @@
 				continue;
 			}
 
-			// check for fast-forward:
-			// - both old and new ref must point to commits, AND
-			// - both of them must be known for us, exist in repository, AND
-			// - old commit must be ancestor of new commit
-			boolean fastForward = true;
-			try {
-				RevObject oldRev = walker.parseAny(advertisedOld);
-				final RevObject newRev = walker.parseAny(rru.getNewObjectId());
-				if (!(oldRev instanceof RevCommit)
-						|| !(newRev instanceof RevCommit)
-						|| !walker.isMergedInto((RevCommit) oldRev,
-								(RevCommit) newRev))
-					fastForward = false;
-			} catch (MissingObjectException x) {
-				fastForward = false;
-			} catch (Exception x) {
-				throw new TransportException(transport.getURI(), MessageFormat.format(
-						JGitText.get().readingObjectsFromLocalRepositoryFailed, x.getMessage()), x);
-			}
+			boolean fastForward = isFastForward(advertisedOld,
+					rru.getNewObjectId());
 			rru.setFastForward(fastForward);
 			if (!fastForward && !rru.isForceUpdate()) {
 				rru.setStatus(Status.REJECTED_NONFASTFORWARD);
@@ -233,6 +255,134 @@
 		return result;
 	}
 
+	/**
+	 * Determines whether an update from {@code oldOid} to {@code newOid} is a
+	 * fast-forward update:
+	 * <ul>
+	 * <li>both old and new must be commits, AND</li>
+	 * <li>both of them must be known to us and exist in the repository,
+	 * AND</li>
+	 * <li>the old commit must be an ancestor of the new commit.</li>
+	 * </ul>
+	 *
+	 * @param oldOid
+	 *            {@link ObjectId} of the old commit
+	 * @param newOid
+	 *            {@link ObjectId} of the new commit
+	 * @return {@code true} if the update fast-forwards, {@code false} otherwise
+	 * @throws TransportException
+	 */
+	private boolean isFastForward(ObjectId oldOid, ObjectId newOid)
+			throws TransportException {
+		try {
+			RevObject oldRev = walker.parseAny(oldOid);
+			RevObject newRev = walker.parseAny(newOid);
+			if (!(oldRev instanceof RevCommit) || !(newRev instanceof RevCommit)
+					|| !walker.isMergedInto((RevCommit) oldRev,
+							(RevCommit) newRev)) {
+				return false;
+			}
+		} catch (MissingObjectException x) {
+			return false;
+		} catch (Exception x) {
+			throw new TransportException(transport.getURI(),
+					MessageFormat.format(JGitText
+							.get().readingObjectsFromLocalRepositoryFailed,
+							x.getMessage()),
+					x);
+		}
+		return true;
+	}
+
+	/**
+	 * Expands all placeholder {@link RemoteRefUpdate}s for "matching"
+	 * {@link RefSpec}s ":" in {@link #toPush} and returns the resulting map in
+	 * which the placeholders have been replaced by their expansion.
+	 *
+	 * @return a new map of {@link RemoteRefUpdate}s keyed by remote name
+	 * @throws TransportException
+	 *             if the expansion results in duplicate updates
+	 */
+	private Map<String, RemoteRefUpdate> expandMatching()
+			throws TransportException {
+		Map<String, RemoteRefUpdate> result = new LinkedHashMap<>();
+		boolean hadMatch = false;
+		for (RemoteRefUpdate update : toPush.values()) {
+			if (update.isMatching()) {
+				if (hadMatch) {
+					throw new TransportException(MessageFormat.format(
+							JGitText.get().duplicateRemoteRefUpdateIsIllegal,
+							":")); //$NON-NLS-1$
+				}
+				expandMatching(result, update);
+				hadMatch = true;
+			} else if (result.put(update.getRemoteName(), update) != null) {
+				throw new TransportException(MessageFormat.format(
+						JGitText.get().duplicateRemoteRefUpdateIsIllegal,
+						update.getRemoteName()));
+			}
+		}
+		return result;
+	}
+
+	/**
+	 * Expands the placeholder {@link RemoteRefUpdate} {@code match} for a
+	 * "matching" {@link RefSpec} ":" or "+:" and puts the expansion into the
+	 * given map {@code updates}.
+	 *
+	 * @param updates
+	 *            map to put the expansion in
+	 * @param match
+	 *            the placeholder {@link RemoteRefUpdate} to expand
+	 *
+	 * @throws TransportException
+	 *             if the expansion results in duplicate updates, or the local
+	 *             branches cannot be determined
+	 */
+	private void expandMatching(Map<String, RemoteRefUpdate> updates,
+			RemoteRefUpdate match) throws TransportException {
+		try {
+			Map<String, Ref> advertisement = connection.getRefsMap();
+			Collection<RefSpec> fetchSpecs = match.getFetchSpecs();
+			boolean forceUpdate = match.isForceUpdate();
+			for (Ref local : transport.local.getRefDatabase()
+					.getRefsByPrefix(Constants.R_HEADS)) {
+				if (local.isSymbolic()) {
+					continue;
+				}
+				String name = local.getName();
+				Ref advertised = advertisement.get(name);
+				if (advertised == null || advertised.isSymbolic()) {
+					continue;
+				}
+				ObjectId oldOid = advertised.getObjectId();
+				if (oldOid == null || ObjectId.zeroId().equals(oldOid)) {
+					continue;
+				}
+				ObjectId newOid = local.getObjectId();
+				if (newOid == null || ObjectId.zeroId().equals(newOid)) {
+					continue;
+				}
+
+				RemoteRefUpdate rru = new RemoteRefUpdate(transport.local, name,
+						newOid, name, forceUpdate,
+						Transport.findTrackingRefName(name, fetchSpecs),
+						oldOid);
+				if (updates.put(rru.getRemoteName(), rru) != null) {
+					throw new TransportException(MessageFormat.format(
+							JGitText.get().duplicateRemoteRefUpdateIsIllegal,
+							rru.getRemoteName()));
+				}
+			}
+		} catch (IOException x) {
+			throw new TransportException(transport.getURI(),
+					MessageFormat.format(JGitText
+							.get().readingObjectsFromLocalRepositoryFailed,
+							x.getMessage()),
+					x);
+		}
+	}
+
 	private Map<String, RemoteRefUpdate> rejectAll() {
 		for (RemoteRefUpdate rru : toPush.values()) {
 			if (rru.getStatus() == Status.NOT_ATTEMPTED) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java
index ac357af..56d0036 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java
@@ -12,11 +12,11 @@
 
 import java.io.Serializable;
 import java.text.MessageFormat;
+import java.util.Objects;
 
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.util.References;
 
 /**
  * Describes how refs in one repository copy into another repository.
@@ -50,6 +50,9 @@
 	/** Is this specification actually a wildcard match? */
 	private boolean wildcard;
 
+	/** Is this the special ":" RefSpec? */
+	private boolean matching;
+
 	/**
 	 * How strict to be about wildcards.
 	 *
@@ -71,6 +74,7 @@
 		 */
 		ALLOW_MISMATCH
 	}
+
 	/** Whether a wildcard is allowed on one side but not the other. */
 	private WildcardMode allowMismatchedWildcards;
 
@@ -87,6 +91,7 @@
 	 * applications, as at least one field must be set to match a source name.
 	 */
 	public RefSpec() {
+		matching = false;
 		force = false;
 		wildcard = false;
 		srcName = Constants.HEAD;
@@ -133,17 +138,25 @@
 			s = s.substring(1);
 		}
 
+		boolean matchPushSpec = false;
 		final int c = s.lastIndexOf(':');
 		if (c == 0) {
 			s = s.substring(1);
-			if (isWildcard(s)) {
+			if (s.isEmpty()) {
+				matchPushSpec = true;
 				wildcard = true;
-				if (mode == WildcardMode.REQUIRE_MATCH) {
-					throw new IllegalArgumentException(MessageFormat
-							.format(JGitText.get().invalidWildcards, spec));
+				srcName = Constants.R_HEADS + '*';
+				dstName = srcName;
+			} else {
+				if (isWildcard(s)) {
+					wildcard = true;
+					if (mode == WildcardMode.REQUIRE_MATCH) {
+						throw new IllegalArgumentException(MessageFormat
+								.format(JGitText.get().invalidWildcards, spec));
+					}
 				}
+				dstName = checkValid(s);
 			}
-			dstName = checkValid(s);
 		} else if (c > 0) {
 			String src = s.substring(0, c);
 			String dst = s.substring(c + 1);
@@ -168,6 +181,7 @@
 			}
 			srcName = checkValid(s);
 		}
+		matching = matchPushSpec;
 	}
 
 	/**
@@ -195,6 +209,7 @@
 	}
 
 	private RefSpec(RefSpec p) {
+		matching = false;
 		force = p.isForceUpdate();
 		wildcard = p.isWildcard();
 		srcName = p.getSource();
@@ -203,6 +218,17 @@
 	}
 
 	/**
+	 * Tells whether this {@link RefSpec} is the special "matching" RefSpec ":"
+	 * for pushing.
+	 *
+	 * @return whether this is a "matching" RefSpec
+	 * @since 6.1
+	 */
+	public boolean isMatching() {
+		return matching;
+	}
+
+	/**
 	 * Check if this specification wants to forcefully update the destination.
 	 *
 	 * @return true if this specification asks for updates without merge tests.
@@ -220,6 +246,7 @@
 	 */
 	public RefSpec setForceUpdate(boolean forceUpdate) {
 		final RefSpec r = new RefSpec(this);
+		r.matching = matching;
 		r.force = forceUpdate;
 		return r;
 	}
@@ -322,8 +349,7 @@
 	 *             The wildcard status of the new source disagrees with the
 	 *             wildcard status of the new destination.
 	 */
-	public RefSpec setSourceDestination(final String source,
-			final String destination) {
+	public RefSpec setSourceDestination(String source, String destination) {
 		if (isWildcard(source) != isWildcard(destination))
 			throw new IllegalStateException(JGitText.get().sourceDestinationMustMatch);
 		final RefSpec r = new RefSpec(this);
@@ -541,37 +567,36 @@
 		if (!(obj instanceof RefSpec))
 			return false;
 		final RefSpec b = (RefSpec) obj;
-		if (isForceUpdate() != b.isForceUpdate())
+		if (isForceUpdate() != b.isForceUpdate()) {
 			return false;
-		if (isWildcard() != b.isWildcard())
-			return false;
-		if (!eq(getSource(), b.getSource()))
-			return false;
-		if (!eq(getDestination(), b.getDestination()))
-			return false;
-		return true;
-	}
-
-	private static boolean eq(String a, String b) {
-		if (References.isSameObject(a, b)) {
-			return true;
 		}
-		if (a == null || b == null)
+		if (isMatching()) {
+			return b.isMatching();
+		} else if (b.isMatching()) {
 			return false;
-		return a.equals(b);
+		}
+		return isWildcard() == b.isWildcard()
+				&& Objects.equals(getSource(), b.getSource())
+				&& Objects.equals(getDestination(), b.getDestination());
 	}
 
 	/** {@inheritDoc} */
 	@Override
 	public String toString() {
 		final StringBuilder r = new StringBuilder();
-		if (isForceUpdate())
+		if (isForceUpdate()) {
 			r.append('+');
-		if (getSource() != null)
-			r.append(getSource());
-		if (getDestination() != null) {
+		}
+		if (isMatching()) {
 			r.append(':');
-			r.append(getDestination());
+		} else {
+			if (getSource() != null) {
+				r.append(getSource());
+			}
+			if (getDestination() != null) {
+				r.append(':');
+				r.append(getDestination());
+			}
 		}
 		return r.toString();
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java
index 43eaac7..218e62c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java
@@ -12,7 +12,9 @@
 
 import java.io.IOException;
 import java.text.MessageFormat;
+import java.util.Collection;
 
+import org.eclipse.jgit.annotations.NonNull;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
@@ -116,6 +118,12 @@
 	private RefUpdate localUpdate;
 
 	/**
+	 * If set, the RemoteRefUpdate is a placeholder for the "matching" RefSpec
+	 * to be expanded after the advertisements have been received in a push.
+	 */
+	private Collection<RefSpec> fetchSpecs;
+
+	/**
 	 * Construct remote ref update request by providing an update specification.
 	 * Object is created with default
 	 * {@link org.eclipse.jgit.transport.RemoteRefUpdate.Status#NOT_ATTEMPTED}
@@ -157,9 +165,8 @@
 	 * @throws java.lang.IllegalArgumentException
 	 *             if some required parameter was null
 	 */
-	public RemoteRefUpdate(final Repository localDb, final String srcRef,
-			final String remoteName, final boolean forceUpdate,
-			final String localName, final ObjectId expectedOldObjectId)
+	public RemoteRefUpdate(Repository localDb, String srcRef, String remoteName,
+			boolean forceUpdate, String localName, ObjectId expectedOldObjectId)
 			throws IOException {
 		this(localDb, srcRef, srcRef != null ? localDb.resolve(srcRef)
 				: ObjectId.zeroId(), remoteName, forceUpdate, localName,
@@ -203,9 +210,8 @@
 	 * @throws java.lang.IllegalArgumentException
 	 *             if some required parameter was null
 	 */
-	public RemoteRefUpdate(final Repository localDb, final Ref srcRef,
-			final String remoteName, final boolean forceUpdate,
-			final String localName, final ObjectId expectedOldObjectId)
+	public RemoteRefUpdate(Repository localDb, Ref srcRef, String remoteName,
+			boolean forceUpdate, String localName, ObjectId expectedOldObjectId)
 			throws IOException {
 		this(localDb, srcRef != null ? srcRef.getName() : null,
 				srcRef != null ? srcRef.getObjectId() : null, remoteName,
@@ -255,28 +261,41 @@
 	 * @throws java.lang.IllegalArgumentException
 	 *             if some required parameter was null
 	 */
-	public RemoteRefUpdate(final Repository localDb, final String srcRef,
-			final ObjectId srcId, final String remoteName,
-			final boolean forceUpdate, final String localName,
-			final ObjectId expectedOldObjectId) throws IOException {
-		if (remoteName == null)
-			throw new IllegalArgumentException(JGitText.get().remoteNameCannotBeNull);
-		if (srcId == null && srcRef != null)
-			throw new IOException(MessageFormat.format(
-					JGitText.get().sourceRefDoesntResolveToAnyObject, srcRef));
+	public RemoteRefUpdate(Repository localDb, String srcRef, ObjectId srcId,
+			String remoteName, boolean forceUpdate, String localName,
+			ObjectId expectedOldObjectId) throws IOException {
+		this(localDb, srcRef, srcId, remoteName, forceUpdate, localName, null,
+				expectedOldObjectId);
+	}
 
-		if (srcRef != null)
+	private RemoteRefUpdate(Repository localDb, String srcRef, ObjectId srcId,
+			String remoteName, boolean forceUpdate, String localName,
+			Collection<RefSpec> fetchSpecs, ObjectId expectedOldObjectId)
+			throws IOException {
+		if (fetchSpecs == null) {
+			if (remoteName == null) {
+				throw new IllegalArgumentException(
+						JGitText.get().remoteNameCannotBeNull);
+			}
+			if (srcId == null && srcRef != null) {
+				throw new IOException(MessageFormat.format(
+						JGitText.get().sourceRefDoesntResolveToAnyObject,
+						srcRef));
+			}
+		}
+		if (srcRef != null) {
 			this.srcRef = srcRef;
-		else if (srcId != null && !srcId.equals(ObjectId.zeroId()))
+		} else if (srcId != null && !srcId.equals(ObjectId.zeroId())) {
 			this.srcRef = srcId.name();
-		else
+		} else {
 			this.srcRef = null;
-
-		if (srcId != null)
+		}
+		if (srcId != null) {
 			this.newObjectId = srcId;
-		else
+		} else {
 			this.newObjectId = ObjectId.zeroId();
-
+		}
+		this.fetchSpecs = fetchSpecs;
 		this.remoteName = remoteName;
 		this.forceUpdate = forceUpdate;
 		if (localName != null && localDb != null) {
@@ -292,8 +311,9 @@
 						? localUpdate.getOldObjectId()
 						: ObjectId.zeroId(),
 					newObjectId);
-		} else
+		} else {
 			trackingRefUpdate = null;
+		}
 		this.localDb = localDb;
 		this.expectedOldObjectId = expectedOldObjectId;
 		this.status = Status.NOT_ATTEMPTED;
@@ -316,11 +336,57 @@
 	 *             local tracking branch or srcRef of base object no longer can
 	 *             be resolved to any object.
 	 */
-	public RemoteRefUpdate(final RemoteRefUpdate base,
-			final ObjectId newExpectedOldObjectId) throws IOException {
-		this(base.localDb, base.srcRef, base.remoteName, base.forceUpdate,
+	public RemoteRefUpdate(RemoteRefUpdate base,
+			ObjectId newExpectedOldObjectId) throws IOException {
+		this(base.localDb, base.srcRef, base.newObjectId, base.remoteName,
+				base.forceUpdate,
 				(base.trackingRefUpdate == null ? null : base.trackingRefUpdate
-						.getLocalName()), newExpectedOldObjectId);
+						.getLocalName()),
+				base.fetchSpecs, newExpectedOldObjectId);
+	}
+
+	/**
+	 * Creates a "placeholder" update for the "matching" RefSpec ":".
+	 *
+	 * @param localDb
+	 *            local repository to push from
+	 * @param forceUpdate
+	 *            whether non-fast-forward updates shall be allowed
+	 * @param fetchSpecs
+	 *            The fetch {@link RefSpec}s to use when this placeholder is
+	 *            expanded to determine remote tracking branch updates
+	 */
+	RemoteRefUpdate(Repository localDb, boolean forceUpdate,
+			@NonNull Collection<RefSpec> fetchSpecs) {
+		this.localDb = localDb;
+		this.forceUpdate = forceUpdate;
+		this.fetchSpecs = fetchSpecs;
+		this.trackingRefUpdate = null;
+		this.srcRef = null;
+		this.remoteName = null;
+		this.newObjectId = null;
+		this.status = Status.NOT_ATTEMPTED;
+	}
+
+	/**
+	 * Tells whether this {@link RemoteRefUpdate} is a placeholder for a
+	 * "matching" {@link RefSpec}.
+	 *
+	 * @return {@code true} if this is a placeholder, {@code false} otherwise
+	 * @since 6.1
+	 */
+	public boolean isMatching() {
+		return fetchSpecs != null;
+	}
+
+	/**
+	 * Retrieves the fetch {@link RefSpec}s of this {@link RemoteRefUpdate}.
+	 *
+	 * @return the fetch {@link RefSpec}s, or {@code null} if
+	 *         {@code this.}{@link #isMatching()} {@code == false}
+	 */
+	Collection<RefSpec> getFetchSpecs() {
+		return fetchSpecs;
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java
index 212a4e4..48cacf0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018, 2020 Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2018, 2021 Thomas Wolf <thomas.wolf@paranor.ch> 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
@@ -44,6 +44,14 @@
 
 	// Config file keys
 
+	/**
+	 * Property to control whether private keys are added to an SSH agent, if
+	 * one is running, after having been loaded.
+	 *
+	 * @since 6.1
+	 */
+	public static final String ADD_KEYS_TO_AGENT = "AddKeysToAgent";
+
 	/** Key in an ssh config file. */
 	public static final String BATCH_MODE = "BatchMode";
 
@@ -62,6 +70,15 @@
 	/** Key in an ssh config file. */
 	public static final String CONNECTION_ATTEMPTS = "ConnectionAttempts";
 
+	/**
+	 * An OpenSSH time value for the connection timeout. In OpenSSH, this
+	 * includes everything until the end of the initial key exchange; in JGit it
+	 * covers only the underlying TCP connect.
+	 *
+	 * @since 6.1
+	 */
+	public static final String CONNECT_TIMEOUT = "ConnectTimeout";
+
 	/** Key in an ssh config file. */
 	public static final String CONTROL_PATH = "ControlPath";
 
@@ -159,6 +176,14 @@
 	/** Key in an ssh config file. */
 	public static final String REMOTE_FORWARD = "RemoteForward";
 
+	/**
+	 * (Absolute) path to a middleware library the SSH agent shall use to load
+	 * SK (U2F) keys.
+	 *
+	 * @since 6.1
+	 */
+	public static final String SECURITY_KEY_PROVIDER = "SecurityKeyProvider";
+
 	/** Key in an ssh config file. */
 	public static final String SEND_ENV = "SendEnv";
 
@@ -229,4 +254,12 @@
 	public static final String[] DEFAULT_IDENTITIES = { //
 			ID_RSA, ID_DSA, ID_ECDSA, ID_ED25519
 	};
+
+	/**
+	 * Name of the environment variable holding the Unix domain socket for
+	 * communication with an SSH agent.
+	 *
+	 * @since 6.1
+	 */
+	public static final String ENV_SSH_AUTH_SOCKET = "SSH_AUTH_SOCK";
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java
index 696ca7c..51bc07c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java
@@ -12,6 +12,8 @@
 
 package org.eclipse.jgit.transport;
 
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
+
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.RefUpdate;
@@ -184,9 +186,13 @@
 		if (forceUpdate)
 			sb.append(" (forced)");
 		sb.append(" ");
-		sb.append(oldObjectId == null ? "" : oldObjectId.abbreviate(7).name());
+		sb.append(oldObjectId == null ? ""
+				: oldObjectId.abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH)
+						.name());
 		sb.append("..");
-		sb.append(newObjectId == null ? "" : newObjectId.abbreviate(7).name());
+		sb.append(newObjectId == null ? ""
+				: newObjectId.abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH)
+						.name());
 		sb.append("]");
 		return sb.toString();
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
index 5b781ac..0eab443 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
@@ -2,7 +2,7 @@
  * Copyright (C) 2008, 2009 Google Inc.
  * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
  * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2008, 2020 Shawn O. Pearce <spearce@spearce.org> and others
+ * Copyright (C) 2008, 2022 Shawn O. Pearce <spearce@spearce.org> 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
@@ -40,7 +40,6 @@
 
 import org.eclipse.jgit.annotations.NonNull;
 import org.eclipse.jgit.annotations.Nullable;
-import org.eclipse.jgit.api.errors.AbortedByHookException;
 import org.eclipse.jgit.errors.NotSupportedException;
 import org.eclipse.jgit.errors.TransportException;
 import org.eclipse.jgit.hooks.Hooks;
@@ -590,6 +589,11 @@
 		final Collection<RefSpec> procRefs = expandPushWildcardsFor(db, specs);
 
 		for (RefSpec spec : procRefs) {
+			if (spec.isMatching()) {
+				result.add(new RemoteRefUpdate(db, spec.isForceUpdate(),
+						fetchSpecs));
+				continue;
+			}
 			String srcSpec = spec.getSource();
 			final Ref srcRef = db.findRef(srcSpec);
 			if (srcRef != null)
@@ -656,14 +660,18 @@
 	private static Collection<RefSpec> expandPushWildcardsFor(
 			final Repository db, final Collection<RefSpec> specs)
 			throws IOException {
-		final List<Ref> localRefs = db.getRefDatabase().getRefs();
 		final Collection<RefSpec> procRefs = new LinkedHashSet<>();
 
+		List<Ref> localRefs = null;
 		for (RefSpec spec : specs) {
-			if (spec.isWildcard()) {
+			if (!spec.isMatching() && spec.isWildcard()) {
+				if (localRefs == null) {
+					localRefs = db.getRefDatabase().getRefs();
+				}
 				for (Ref localRef : localRefs) {
-					if (spec.matchSource(localRef))
+					if (spec.matchSource(localRef)) {
 						procRefs.add(spec.expandFromSource(localRef));
+					}
 				}
 			} else {
 				procRefs.add(spec);
@@ -672,7 +680,7 @@
 		return procRefs;
 	}
 
-	private static String findTrackingRefName(final String remoteName,
+	static String findTrackingRefName(final String remoteName,
 			final Collection<RefSpec> fetchSpecs) {
 		// try to find matching tracking refs
 		for (RefSpec fetchSpec : fetchSpecs) {
@@ -1371,16 +1379,9 @@
 			if (toPush.isEmpty())
 				throw new TransportException(JGitText.get().nothingToPush);
 		}
-		if (prePush != null) {
-			try {
-				prePush.setRefs(toPush);
-				prePush.call();
-			} catch (AbortedByHookException | IOException e) {
-				throw new TransportException(e.getMessage(), e);
-			}
-		}
 
-		final PushProcess pushProcess = new PushProcess(this, toPush, out);
+		final PushProcess pushProcess = new PushProcess(this, toPush, prePush,
+				out);
 		return pushProcess.execute(monitor);
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
index 0c23c8c..46abe34 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
@@ -2358,7 +2358,7 @@
 						: req.getDepth() - 1;
 				pw.setShallowPack(req.getDepth(), unshallowCommits);
 
-				@SuppressWarnings("resource") // Ownership is transferred below
+				// Ownership is transferred below
 				DepthWalk.RevWalk dw = new DepthWalk.RevWalk(
 						walk.getObjectReader(), walkDepth);
 				dw.setDeepenSince(req.getDeepenSince());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java
index 3d15ef5..046f395 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009-2010, Google Inc. and others
+ * Copyright (C) 2009-2022, Google Inc. 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
@@ -18,11 +18,11 @@
 import java.util.concurrent.CopyOnWriteArrayList;
 
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.RepositoryCache;
 import org.eclipse.jgit.lib.RepositoryCache.FileKey;
 import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.StringUtils;
 
 /**
  * Default resolver serving from the local filesystem.
@@ -67,7 +67,7 @@
 		if (isUnreasonableName(name))
 			throw new RepositoryNotFoundException(name);
 
-		Repository db = exports.get(nameWithDotGit(name));
+		Repository db = exports.get(StringUtils.nameWithDotGit(name));
 		if (db != null) {
 			db.incrementOpen();
 			return db;
@@ -154,7 +154,7 @@
 	 *            the repository instance.
 	 */
 	public void exportRepository(String name, Repository db) {
-		exports.put(nameWithDotGit(name), db);
+		exports.put(StringUtils.nameWithDotGit(name), db);
 	}
 
 	/**
@@ -197,12 +197,6 @@
 			return false;
 	}
 
-	private static String nameWithDotGit(String name) {
-		if (name.endsWith(Constants.DOT_GIT_EXT))
-			return name;
-		return name + Constants.DOT_GIT_EXT;
-	}
-
 	private static boolean isUnreasonableName(String name) {
 		if (name.length() == 0)
 			return true; // no empty paths
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
index 1f614e3..8269666 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
@@ -1,6 +1,6 @@
 /*
- * Copyright (C) 2008-2009, Google Inc.
- * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
+ * Copyright (C) 2008, 2009 Google Inc.
+ * Copyright (C) 2008, 2022 Shawn O. Pearce <spearce@spearce.org> 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
@@ -14,6 +14,7 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
@@ -73,6 +74,7 @@
  * threads.
  */
 public class TreeWalk implements AutoCloseable, AttributesProvider {
+
 	private static final AbstractTreeIterator[] NO_TREES = {};
 
 	/**
@@ -92,7 +94,7 @@
 	}
 
 	/**
-	 *            Type of operation you want to retrieve the git attributes for.
+	 * Type of operation you want to retrieve the git attributes for.
 	 */
 	private OperationType operationType = OperationType.CHECKOUT_OP;
 
@@ -284,11 +286,20 @@
 
 	AbstractTreeIterator currentHead;
 
-	/** Cached attribute for the current entry */
-	private Attributes attrs = null;
+	/**
+	 * Cached attributes for the current entry; per tree. Index i+1 is for tree
+	 * i; index 0 is for the deprecated legacy behavior.
+	 */
+	private Attributes[] attrs;
 
-	/** Cached attributes handler */
-	private AttributesHandler attributesHandler;
+	/**
+	 * Cached attributes handler; per tree. Index i+1 is for tree i; index 0 is
+	 * for the deprecated legacy behavior.
+	 */
+	private AttributesHandler[] attributesHandlers;
+
+	/** Can be set to identify the tree to use for {@link #getAttributes()}. */
+	private int headIndex = -1;
 
 	private Config config;
 
@@ -515,6 +526,24 @@
 	}
 
 	/**
+	 * Identifies the tree at the given index as the head tree. This is the tree
+	 * use by default to determine attributes and EOL modes.
+	 *
+	 * @param index
+	 *            of the tree to use as head
+	 * @throws IllegalArgumentException
+	 *             if the index is out of range
+	 * @since 6.1
+	 */
+	public void setHead(int index) {
+		if (index < 0 || index >= trees.length) {
+			throw new IllegalArgumentException("Head index " + index //$NON-NLS-1$
+					+ " out of range [0," + trees.length + ')'); //$NON-NLS-1$
+		}
+		headIndex = index;
+	}
+
+	/**
 	 * {@inheritDoc}
 	 * <p>
 	 * Retrieve the git attributes for the current entry.
@@ -556,25 +585,51 @@
 	 */
 	@Override
 	public Attributes getAttributes() {
-		if (attrs != null)
-			return attrs;
+		return getAttributes(headIndex);
+	}
 
+	/**
+	 * Retrieves the git attributes based on the given tree.
+	 *
+	 * @param index
+	 *            of the tree to use as base for the attributes
+	 * @return the attributes
+	 * @since 6.1
+	 */
+	public Attributes getAttributes(int index) {
+		int attrIndex = index + 1;
+		Attributes result = attrs[attrIndex];
+		if (result != null) {
+			return result;
+		}
 		if (attributesNodeProvider == null) {
-			// The work tree should have a AttributesNodeProvider to be able to
-			// retrieve the info and global attributes node
 			throw new IllegalStateException(
 					"The tree walk should have one AttributesNodeProvider set in order to compute the git attributes."); //$NON-NLS-1$
 		}
 
 		try {
-			// Lazy create the attributesHandler on the first access of
-			// attributes. This requires the info, global and root
-			// attributes nodes
-			if (attributesHandler == null) {
-				attributesHandler = new AttributesHandler(this);
+			AttributesHandler handler = attributesHandlers[attrIndex];
+			if (handler == null) {
+				if (index < 0) {
+					// Legacy behavior (headIndex not set, getAttributes() above
+					// called)
+					handler = new AttributesHandler(this, () -> {
+						return getTree(CanonicalTreeParser.class);
+					});
+				} else {
+					handler = new AttributesHandler(this, () -> {
+						AbstractTreeIterator tree = trees[index];
+						if (tree instanceof CanonicalTreeParser) {
+							return (CanonicalTreeParser) tree;
+						}
+						return null;
+					});
+				}
+				attributesHandlers[attrIndex] = handler;
 			}
-			attrs = attributesHandler.getAttributes();
-			return attrs;
+			result = handler.getAttributes();
+			attrs[attrIndex] = result;
+			return result;
 		} catch (IOException e) {
 			throw new JGitInternalException("Error while parsing attributes", //$NON-NLS-1$
 					e);
@@ -595,11 +650,34 @@
 	 */
 	@Nullable
 	public EolStreamType getEolStreamType(OperationType opType) {
-		if (attributesNodeProvider == null || config == null)
+		if (attributesNodeProvider == null || config == null) {
 			return null;
-		return EolStreamTypeUtil.detectStreamType(
-				opType != null ? opType : operationType,
-					config.get(WorkingTreeOptions.KEY), getAttributes());
+		}
+		OperationType op = opType != null ? opType : operationType;
+		return EolStreamTypeUtil.detectStreamType(op,
+				config.get(WorkingTreeOptions.KEY), getAttributes());
+	}
+
+	/**
+	 * Get the EOL stream type of the current entry for checking out using the
+	 * config and {@link #getAttributes()}.
+	 *
+	 * @param tree
+	 *            index of the tree the check-out is to be from
+	 * @return the EOL stream type of the current entry using the config and
+	 *         {@link #getAttributes()}. Note that this method may return null
+	 *         if the {@link org.eclipse.jgit.treewalk.TreeWalk} is not based on
+	 *         a working tree
+	 * @since 6.1
+	 */
+	@Nullable
+	public EolStreamType getCheckoutEolStreamType(int tree) {
+		if (attributesNodeProvider == null || config == null) {
+			return null;
+		}
+		Attributes attr = getAttributes(tree);
+		return EolStreamTypeUtil.detectStreamType(OperationType.CHECKOUT_OP,
+				config.get(WorkingTreeOptions.KEY), attr);
 	}
 
 	/**
@@ -607,7 +685,8 @@
 	 */
 	public void reset() {
 		attrs = null;
-		attributesHandler = null;
+		attributesHandlers = null;
+		headIndex = -1;
 		trees = NO_TREES;
 		advance = false;
 		depth = 0;
@@ -651,7 +730,9 @@
 
 		advance = false;
 		depth = 0;
-		attrs = null;
+		attrs = new Attributes[2];
+		attributesHandlers = new AttributesHandler[2];
+		headIndex = -1;
 	}
 
 	/**
@@ -701,7 +782,14 @@
 		trees = r;
 		advance = false;
 		depth = 0;
-		attrs = null;
+		if (oldLen == newLen) {
+			Arrays.fill(attrs, null);
+			Arrays.fill(attributesHandlers, null);
+		} else {
+			attrs = new Attributes[newLen + 1];
+			attributesHandlers = new AttributesHandler[newLen + 1];
+		}
+		headIndex = -1;
 	}
 
 	/**
@@ -758,6 +846,16 @@
 		p.matchShift = 0;
 
 		trees = newTrees;
+		if (attrs == null) {
+			attrs = new Attributes[n + 2];
+		} else {
+			attrs = Arrays.copyOf(attrs, n + 2);
+		}
+		if (attributesHandlers == null) {
+			attributesHandlers = new AttributesHandler[n + 2];
+		} else {
+			attributesHandlers = Arrays.copyOf(attributesHandlers, n + 2);
+		}
 		return n;
 	}
 
@@ -800,7 +898,7 @@
 			}
 
 			for (;;) {
-				attrs = null;
+				Arrays.fill(attrs, null);
 				final AbstractTreeIterator t = min();
 				if (t.eof()) {
 					if (depth > 0) {
@@ -1255,7 +1353,7 @@
 	 */
 	public void enterSubtree() throws MissingObjectException,
 			IncorrectObjectTypeException, CorruptObjectException, IOException {
-		attrs = null;
+		Arrays.fill(attrs, null);
 		final AbstractTreeIterator ch = currentHead;
 		final AbstractTreeIterator[] tmp = new AbstractTreeIterator[trees.length];
 		for (int i = 0; i < trees.length; i++) {
@@ -1374,11 +1472,12 @@
 
 	/**
 	 * Inspect config and attributes to return a filtercommand applicable for
-	 * the current path, but without expanding %f occurences
+	 * the current path.
 	 *
 	 * @param filterCommandType
 	 *            which type of filterCommand should be executed. E.g. "clean",
-	 *            "smudge"
+	 *            "smudge". For "smudge" consider using
+	 *            {{@link #getSmudgeCommand(int)} instead.
 	 * @return a filter command
 	 * @throws java.io.IOException
 	 * @since 4.2
@@ -1407,6 +1506,54 @@
 	}
 
 	/**
+	 * Inspect config and attributes to return a filtercommand applicable for
+	 * the current path.
+	 *
+	 * @param index
+	 *            of the tree the item to be smudged is in
+	 * @return a filter command
+	 * @throws java.io.IOException
+	 * @since 6.1
+	 */
+	public String getSmudgeCommand(int index)
+			throws IOException {
+		return getSmudgeCommand(getAttributes(index));
+	}
+
+	/**
+	 * Inspect config and attributes to return a filtercommand applicable for
+	 * the current path.
+	 *
+	 * @param attributes
+	 *            to use
+	 * @return a filter command
+	 * @throws java.io.IOException
+	 * @since 6.1
+	 */
+	public String getSmudgeCommand(Attributes attributes) throws IOException {
+		if (attributes == null) {
+			return null;
+		}
+		Attribute f = attributes.get(Constants.ATTR_FILTER);
+		if (f == null) {
+			return null;
+		}
+		String filterValue = f.getValue();
+		if (filterValue == null) {
+			return null;
+		}
+
+		String filterCommand = getFilterCommandDefinition(filterValue,
+				Constants.ATTR_FILTER_TYPE_SMUDGE);
+		if (filterCommand == null) {
+			return null;
+		}
+		return filterCommand.replaceAll("%f", //$NON-NLS-1$
+				Matcher.quoteReplacement(
+						QuotedString.BOURNE.quote((getPathString()))));
+	}
+
+	/**
 	 * Get the filter command how it is defined in gitconfig. The returned
 	 * string may contain "%f" which needs to be replaced by the current path
 	 * before executing the filter command. These filter definitions are cached
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 50ce15e..427eac5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
@@ -2,7 +2,7 @@
  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
  * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>
  * Copyright (C) 2010, Matthias Sohn <matthias.sohn@sap.com>
- * Copyright (C) 2012-2021, Robin Rosenberg and others
+ * Copyright (C) 2012, 2022, Robin Rosenberg 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
@@ -387,8 +387,8 @@
 				state.initializeReadBuffer();
 
 				final long len = e.getLength();
-				InputStream filteredIs = possiblyFilteredInputStream(e, is, len,
-						OperationType.CHECKIN_OP);
+				InputStream filteredIs = possiblyFilteredInputStream(e, is,
+						len);
 				return computeHash(filteredIs, canonLen);
 			} finally {
 				safeClose(is);
@@ -400,23 +400,18 @@
 	}
 
 	private InputStream possiblyFilteredInputStream(final Entry e,
-			final InputStream is, final long len) throws IOException {
-		return possiblyFilteredInputStream(e, is, len, null);
-
-	}
-
-	private InputStream possiblyFilteredInputStream(final Entry e,
-			final InputStream is, final long len, OperationType opType)
+			final InputStream is, final long len)
 			throws IOException {
 		if (getCleanFilterCommand() == null
-				&& getEolStreamType(opType) == EolStreamType.DIRECT) {
+				&& getEolStreamType(
+						OperationType.CHECKIN_OP) == EolStreamType.DIRECT) {
 			canonLen = len;
 			return is;
 		}
 
 		if (len <= MAXIMUM_FILE_SIZE_TO_READ_FULLY) {
 			ByteBuffer rawbuf = IO.readWholeStream(is, (int) len);
-			rawbuf = filterClean(rawbuf.array(), rawbuf.limit(), opType);
+			rawbuf = filterClean(rawbuf.array(), rawbuf.limit());
 			canonLen = rawbuf.limit();
 			return new ByteArrayInputStream(rawbuf.array(), 0, (int) canonLen);
 		}
@@ -426,14 +421,13 @@
 				return is;
 			}
 
-		final InputStream lenIs = filterClean(e.openInputStream(),
-				opType);
+			final InputStream lenIs = filterClean(e.openInputStream());
 		try {
 			canonLen = computeLength(lenIs);
 		} finally {
 			safeClose(lenIs);
 		}
-		return filterClean(is, opType);
+		return filterClean(is);
 	}
 
 	private static void safeClose(InputStream in) {
@@ -455,23 +449,20 @@
 		}
 	}
 
-	private ByteBuffer filterClean(byte[] src, int n, OperationType opType)
+	private ByteBuffer filterClean(byte[] src, int n)
 			throws IOException {
 		InputStream in = new ByteArrayInputStream(src);
 		try {
-			return IO.readWholeStream(filterClean(in, opType), n);
+			return IO.readWholeStream(filterClean(in), n);
 		} finally {
 			safeClose(in);
 		}
 	}
 
-	private InputStream filterClean(InputStream in) throws IOException {
-		return filterClean(in, null);
-	}
-
-	private InputStream filterClean(InputStream in, OperationType opType)
+	private InputStream filterClean(InputStream in)
 			throws IOException {
-		in = handleAutoCRLF(in, opType);
+		in = EolStreamTypeUtil.wrapInputStream(in,
+				getEolStreamType(OperationType.CHECKIN_OP));
 		String filterCommand = getCleanFilterCommand();
 		if (filterCommand != null) {
 			if (FilterCommandRegistry.isRegistered(filterCommand)) {
@@ -509,11 +500,6 @@
 		return in;
 	}
 
-	private InputStream handleAutoCRLF(InputStream in, OperationType opType)
-			throws IOException {
-		return EolStreamTypeUtil.wrapInputStream(in, getEolStreamType(opType));
-	}
-
 	/**
 	 * Returns the working tree options used by this iterator.
 	 *
@@ -664,7 +650,8 @@
 	public InputStream openEntryStream() throws IOException {
 		InputStream rawis = current().openInputStream();
 		if (getCleanFilterCommand() == null
-				&& getEolStreamType() == EolStreamType.DIRECT) {
+				&& getEolStreamType(
+						OperationType.CHECKIN_OP) == EolStreamType.DIRECT) {
 			return rawis;
 		}
 		return filterClean(rawis);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java
index ff094f6..ae73d3f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java
@@ -16,6 +16,7 @@
 import java.nio.file.FileVisitOption;
 import java.nio.file.FileVisitResult;
 import java.nio.file.Files;
+import java.nio.file.LinkOption;
 import java.nio.file.Path;
 import java.nio.file.SimpleFileVisitor;
 import java.nio.file.attribute.BasicFileAttributes;
@@ -96,6 +97,9 @@
 	/** {@inheritDoc} */
 	@Override
 	public Entry[] list(File directory, FileModeStrategy fileModeStrategy) {
+		if (!Files.isDirectory(directory.toPath(), LinkOption.NOFOLLOW_LINKS)) {
+			return NO_ENTRIES;
+		}
 		List<Entry> result = new ArrayList<>();
 		FS fs = this;
 		boolean checkExecutable = fs.supportsExecute();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java
index 8ab1338..917add3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009-2010, Google Inc. and others
+ * Copyright (C) 2009-2022, Google Inc. 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
@@ -15,6 +15,7 @@
 
 import org.eclipse.jgit.annotations.NonNull;
 import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.Constants;
 
 /**
  * Miscellaneous string comparison utility methods.
@@ -37,6 +38,10 @@
 			LC[c] = (char) ('a' + (c - 'A'));
 	}
 
+	private StringUtils() {
+		// Do not create instances
+	}
+
 	/**
 	 * Convert the input to lowercase.
 	 * <p>
@@ -269,8 +274,20 @@
 		return sb.toString();
 	}
 
-	private StringUtils() {
-		// Do not create instances
+	/**
+	 * Appends {@link Constants#DOT_GIT_EXT} unless the given name already ends
+	 * with that suffix.
+	 *
+	 * @param name
+	 *            to complete
+	 * @return the name ending with {@link Constants#DOT_GIT_EXT}
+	 * @since 6.1
+	 */
+	public static String nameWithDotGit(String name) {
+		if (name.endsWith(Constants.DOT_GIT_EXT)) {
+			return name;
+		}
+		return name + Constants.DOT_GIT_EXT;
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/BinaryHunkInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/BinaryHunkInputStream.java
index 4f940d7..2c972b5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/BinaryHunkInputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/BinaryHunkInputStream.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2021, 2022 Thomas Wolf <thomas.wolf@paranor.ch> 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
@@ -90,7 +90,7 @@
 		byte[] encoded = new byte[Base85.encodedLength(length)];
 		for (int i = 0; i < encoded.length; i++) {
 			int b = in.read();
-			if (b < 0 || b == '\n') {
+			if (b < 0 || b == '\r' || b == '\n') {
 				throw new EOFException(MessageFormat.format(
 						JGitText.get().binaryHunkInvalidLength,
 						Integer.valueOf(lineNumber)));
@@ -99,6 +99,10 @@
 		}
 		// Must be followed by a newline; tolerate EOF.
 		int b = in.read();
+		if (b == '\r') {
+			// Be lenient and accept CR-LF, too.
+			b = in.read();
+		}
 		if (b >= 0 && b != '\n') {
 			throw new StreamCorruptedException(MessageFormat.format(
 					JGitText.get().binaryHunkMissingNewline,
diff --git a/pom.xml b/pom.xml
index c486569..81093c0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,7 +18,7 @@
   <groupId>org.eclipse.jgit</groupId>
   <artifactId>org.eclipse.jgit-parent</artifactId>
   <packaging>pom</packaging>
-  <version>6.0.1-SNAPSHOT</version>
+  <version>6.1.1-SNAPSHOT</version>
 
   <name>JGit - Parent</name>
   <url>${jgit-url}</url>
@@ -150,9 +150,9 @@
     <java.version>11</java.version>
     <bundle-manifest>${project.build.directory}/META-INF/MANIFEST.MF</bundle-manifest>
 
-    <jgit-last-release-version>5.13.0.202109080827-r</jgit-last-release-version>
+    <jgit-last-release-version>6.0.0.202111291000-r</jgit-last-release-version>
     <ant-version>1.10.12</ant-version>
-    <apache-sshd-version>2.7.0</apache-sshd-version>
+    <apache-sshd-version>2.8.0</apache-sshd-version>
     <jsch-version>0.1.55</jsch-version>
     <jzlib-version>1.1.1</jzlib-version>
     <javaewah-version>1.1.13</javaewah-version>
@@ -160,7 +160,7 @@
     <test-fork-count>1C</test-fork-count>
     <args4j-version>2.33</args4j-version>
     <commons-compress-version>1.21</commons-compress-version>
-    <osgi-core-version>4.3.1</osgi-core-version>
+    <osgi-core-version>6.0.0</osgi-core-version>
     <servlet-api-version>4.0.0</servlet-api-version>
     <jetty-version>10.0.6</jetty-version>
     <japicmp-version>0.15.3</japicmp-version>
@@ -169,8 +169,8 @@
     <slf4j-version>1.7.30</slf4j-version>
     <maven-javadoc-plugin-version>3.3.1</maven-javadoc-plugin-version>
     <tycho-extras-version>2.5.0</tycho-extras-version>
-    <gson-version>2.8.8</gson-version>
-    <bouncycastle-version>1.69</bouncycastle-version>
+    <gson-version>2.8.9</gson-version>
+    <bouncycastle-version>1.70</bouncycastle-version>
     <spotbugs-maven-plugin-version>4.3.0</spotbugs-maven-plugin-version>
     <maven-project-info-reports-plugin-version>3.1.2</maven-project-info-reports-plugin-version>
     <maven-jxr-plugin-version>3.1.1</maven-jxr-plugin-version>
@@ -203,6 +203,14 @@
       <id>repo.eclipse.org.cbi-snapshots</id>
       <url>https://repo.eclipse.org/content/repositories/cbi-snapshots/</url>
     </pluginRepository>
+    <pluginRepository>
+      <id>repo.eclipse.org.dash-releases</id>
+      <url>https://repo.eclipse.org/content/repositories/dash-licenses-releases/</url>
+    </pluginRepository>
+    <pluginRepository>
+      <id>repo.eclipse.org.dash-snapshots</id>
+      <url>https://repo.eclipse.org/content/repositories/dash-licenses-snapshots/</url>
+    </pluginRepository>
   </pluginRepositories>
 
   <build>
@@ -338,7 +346,7 @@
             <dependency><!-- add support for ssh/scp -->
               <groupId>org.apache.maven.wagon</groupId>
               <artifactId>wagon-ssh</artifactId>
-              <version>3.4.3</version>
+              <version>3.5.1</version>
             </dependency>
           </dependencies>
         </plugin>
@@ -382,6 +390,11 @@
           <artifactId>spring-boot-maven-plugin</artifactId>
           <version>2.5.4</version>
         </plugin>
+        <plugin>
+          <groupId>org.eclipse.dash</groupId>
+          <artifactId>license-tool-plugin</artifactId>
+          <version>0.0.1-SNAPSHOT</version>
+        </plugin>
       </plugins>
     </pluginManagement>
 
@@ -540,6 +553,10 @@
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-surefire-report-plugin</artifactId>
       </plugin>
+      <plugin>
+        <groupId>org.eclipse.dash</groupId>
+        <artifactId>license-tool-plugin</artifactId>
+      </plugin>
     </plugins>
   </build>
 
@@ -853,7 +870,7 @@
               <dependency>
                 <groupId>org.eclipse.jdt</groupId>
                 <artifactId>ecj</artifactId>
-                <version>3.27.0</version>
+                <version>3.28.0</version>
               </dependency>
             </dependencies>
           </plugin>
diff --git a/tools/BUILD b/tools/BUILD
index 1826249..1e67108 100644
--- a/tools/BUILD
+++ b/tools/BUILD
@@ -1,34 +1,28 @@
 load(
     "@bazel_tools//tools/jdk:default_java_toolchain.bzl",
-    "JDK9_JVM_OPTS",
     "default_java_toolchain",
 )
 load("@rules_java//java:defs.bzl", "java_package_configuration")
 
-JDK11_JVM_OPTS = [
-    "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED",
-    "--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
-    "--add-exports=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED",
-    "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED",
-    "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED",
-    "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
-    "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
-    "--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED",
-    "--patch-module=java.compiler=$(location @bazel_tools//tools/jdk:java_compiler_jar)",
-    "--patch-module=jdk.compiler=$(location @bazel_tools//tools/jdk:jdk_compiler_jar)",
-    "--add-opens=java.base/java.nio=ALL-UNNAMED",
-    "--add-opens=java.base/java.lang=ALL-UNNAMED",
-]
-
 default_java_toolchain(
-    name = "error_prone_warnings_toolchain",
-    bootclasspath = ["@bazel_tools//tools/jdk:platformclasspath.jar"],
-    jvm_opts = JDK11_JVM_OPTS,
-    source_version = "11",
-    target_version = "11",
+    name = "error_prone_warnings_toolchain_java11",
     package_configuration = [
         ":error_prone",
     ],
+    source_version = "11",
+    target_version = "11",
+    visibility = ["//visibility:public"],
+)
+
+default_java_toolchain(
+    name = "error_prone_warnings_toolchain_java17",
+    configuration = dict(),
+    java_runtime = "@bazel_tools//tools/jdk:remotejdk_17",
+    package_configuration = [
+        ":error_prone",
+    ],
+    source_version = "17",
+    target_version = "17",
     visibility = ["//visibility:public"],
 )
 
@@ -108,20 +102,25 @@
         "//org.eclipse.jgit.ant.test/...",
         "//org.eclipse.jgit.ant/...",
         "//org.eclipse.jgit.archive/...",
+        "//org.eclipse.jgit.gpg.bc.test/...",
+        "//org.eclipse.jgit.gpg.bc/...",
         "//org.eclipse.jgit.http.apache/...",
         "//org.eclipse.jgit.http.server/...",
         "//org.eclipse.jgit.http.test/...",
-        "//org.eclipse.jgit.junit.http/...",
+        "//org.eclipse.jgit.junit.ssh/...",
         "//org.eclipse.jgit.junit/...",
+        "//org.eclipse.jgit.junit/http/...",
         "//org.eclipse.jgit.lfs.server.test/...",
         "//org.eclipse.jgit.lfs.server/...",
         "//org.eclipse.jgit.lfs.test/...",
         "//org.eclipse.jgit.lfs/...",
-        "//org.eclipse.jgit.packaging/...",
         "//org.eclipse.jgit.pgm.test/...",
         "//org.eclipse.jgit.pgm/...",
-        "//org.eclipse.jgit.ssh.apache/...",
         "//org.eclipse.jgit.ssh.apache.agent/...",
+        "//org.eclipse.jgit.ssh.apache.test/...",
+        "//org.eclipse.jgit.ssh.apache/...",
+        "//org.eclipse.jgit.ssh.jsch.test/...",
+        "//org.eclipse.jgit.ssh.jsch/...",
         "//org.eclipse.jgit.test/...",
         "//org.eclipse.jgit.ui/...",
         "//org.eclipse.jgit/...",
diff --git a/tools/bazelisk_version.bzl b/tools/bazelisk_version.bzl
deleted file mode 100644
index d8b3d10..0000000
--- a/tools/bazelisk_version.bzl
+++ /dev/null
@@ -1,16 +0,0 @@
-_template = """
-load("@bazel_skylib//lib:versions.bzl", "versions")
-
-def check_bazel_version():
-  versions.check(minimum_bazel_version = "{version}")
-""".strip()
-
-def _impl(repository_ctx):
-    repository_ctx.symlink(Label("@//:.bazelversion"), ".bazelversion")
-    bazelversion = repository_ctx.read(".bazelversion").strip()
-
-    repository_ctx.file("BUILD", executable = False)
-
-    repository_ctx.file("check.bzl", executable = False, content = _template.format(version = bazelversion))
-
-bazelisk_version = repository_rule(implementation = _impl)
diff --git a/tools/remote-bazelrc b/tools/remote-bazelrc
new file mode 100644
index 0000000..58f794e
--- /dev/null
+++ b/tools/remote-bazelrc
@@ -0,0 +1,67 @@
+# Copyright 2022 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This file is auto-generated from release/bazelrc.tpl and should not be
+# modified directly.
+
+# This .bazelrc file contains all of the flags required for the provided
+# toolchain with Remote Build Execution.
+#
+# This .bazelrc file also contains all of the flags required for the local
+# docker sandboxing.
+
+# Depending on how many machines are in the remote execution instance, setting
+# this higher can make builds faster by allowing more jobs to run in parallel.
+# Setting it too high can result in jobs that timeout, however, while waiting
+# for a remote machine to execute them.
+build:remote --jobs=200
+build:remote --disk_cache=
+
+# Set several flags related to specifying the platform, toolchain and java
+# properties.
+build:remote --host_javabase=@rbe_jdk11//java:jdk
+build:remote --javabase=@rbe_jdk11//java:jdk
+build:remote --crosstool_top=@rbe_jdk11//cc:toolchain
+build:remote --extra_toolchains=@rbe_jdk11//config:cc-toolchain
+build:remote --extra_execution_platforms=@rbe_jdk11//config:platform
+build:remote --host_platform=@rbe_jdk11//config:platform
+build:remote --platforms=@rbe_jdk11//config:platform
+build:remote --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1
+
+# Set various strategies so that all actions execute remotely. Mixing remote
+# and local execution will lead to errors unless the toolchain and remote
+# machine exactly match the host machine.
+build:remote --define=EXECUTOR=remote
+
+# Enable the remote cache so action results can be shared across machines,
+# developers, and workspaces.
+build:remote --remote_cache=remotebuildexecution.googleapis.com
+
+# Enable remote execution so actions are performed on the remote systems.
+build:remote --remote_executor=remotebuildexecution.googleapis.com
+
+# Set a higher timeout value, just in case.
+build:remote --remote_timeout=3600
+
+# Enable authentication. This will pick up application default credentials by
+# default. You can use --auth_credentials=some_file.json to use a service
+# account credential instead.
+build:remote --google_default_credentials
+
+# The following flags enable the remote cache so action results can be shared
+# across machines, developers, and workspaces.
+build:remote-cache --remote_cache=remotebuildexecution.googleapis.com
+build:remote-cache --tls_enabled=true
+build:remote-cache --remote_timeout=3600
+build:remote-cache --auth_enabled=true