Merge branch 'master' into next

* master:
  Remove unused imports
  Silence API warnings
  Remove erraneously merged source features
  Add support for reading symrefs from pack capabilities
  Prepare 5.3.9-SNAPSHOT builds
  JGit v5.3.8.202011260953-r
  Prepare 5.1.15-SNAPSHOT builds
  JGit v5.1.14.202011251942-r
  GC#deleteOrphans: log warning for deleted orphaned files
  GC#deleteOrphans: handle failure to list files in pack directory
  Ensure that GC#deleteOrphans respects pack lock
  Prepare 5.10.0-SNAPSHOT builds
  JGit v5.10.0.202011251205-m3
  PacketLineIn: ensure that END != DELIM
  Update Orbit to S20201118210000 and add target for 4.18
  PacketLineIn: ensure that END != DELIM
  PacketLineIn: ensure that END != DELIM
  Allow to resolve a conflict by checking out a file
  Update Orbit to I20201111205634
  Document that setLastModified sets time of symlink target
  Fix bug in PerformanceLogContext
  Fix IOException occurring during gc
  Prepare 5.10.0-SNAPSHOT builds
  JGit v5.10.0.202011041322-m2
  Revert "Client-side protocol V2 support for fetching"
  Close Repository to fix tests failing on Windows
  Client-side protocol V2 support for fetching
  Update slf4j to 1.7.30
  Update Orbit to S20201027182932 (2020-12 M2)
  Fix formatting of config option values
  Document options in core section supported by JGit
  Ensure .gitmodules is loaded when accessing submodule name
  Export new package org.eclipse.jgit.logging and import it where used
  Ensure GC.deleteOrphans() can delete read-only orphaned files on Windows
  Add new performance logging
  Implement git describe --all
  Compute time differences with Duration
  Override config http.userAgent from environment GIT_HTTP_USER_AGENT
  Upgrade spotbugs-maven-plugin to 4.1.3
  Fix OperatorPrecedence warning flagged by error prone
  UploadPackTest#testUploadRedundantBytes: ensure test repo is closed
  ObjectDirectory#selectObjectRepresentation: fix formatting
  Upgrade ecj to 3.23.0
  Support "http.userAgent" and "http.extraHeader" from the git config
  sshd: better error report when user cancels authentication
  API filters for PackStatistics.Accumulator
  Add TypedConfigGetter.getPath()
  Make Javadoc consistent for PackStatistics fields
  Measure time taken for reachability checks
  Measure time taken for negotiation in protocol V2
  IndexDiffFilter: handle path prefixes correctly
  sshd: support the ProxyJump ssh config
  Upgrade jacoco-maven-plugin to 0.8.6
  ReceivePackStats: Add size and count of unnecessary pushed objects
  Upgrade maven-project-info-reports-plugin to 3.1.1
  Prepare 5.9.1-SNAPSHOT builds
  JGit v5.9.0.202009080501-r
  [releng] Enable japicmp for the fragments added in 5.8.0
  GitlinkMergeTest: fix boxing warnings
  Remove unused API problem filters
  Add missing since tag on BundleWriter#addObjectsAsIs
  SshdSession: close channel gracefully
  GPG: include signer's user ID in the signature
  jgit: Add DfsBundleWriter
  Bump Bazel version to 3.5.0
  Upgrade maven-resources-plugin to 3.2.0
  Upgrade plexus-compiler version to 2.8.8
  [bazel] Add missing dependency to slf4j-api
  [errorprone] DirCacheEntry: make clear operator precedence
  [errorprone] PackWriter#parallelDeltaSearch: avoid suppressed exception
  [errorprone] Declare DirCache#version final
  Add jgit-4.17-staging target platform for 2020-09
  Update target platform to R20200831200620
  Prepare 5.10.0-SNAPSHOT builds
  Prepare 5.9.0-SNAPSHOT builds
  ResolveMerger: do not content-merge gitlinks on del/mod conflicts
  ResolveMerger: Adding test cases for GITLINK deletion
  ResolveMerger: choose OURS on gitlink when ignoreConflicts
  ResolveMerger: improving content merge readability
  ResolveMerger: extracting createGitLinksMergeResult method
  ResolveMerger: Adding test cases for GITLINK merge
  JGit v5.9.0.202008260805-m3
  Fix possible NegativeArraySizeException in PackIndexV1
  FS: use binary search to determine filesystem timestamp resolution
  Do not prematurely create directory of jgit's XDG config file
  FS: write to JGit config in a background thread
  FS: don't cache fallback if running in background
  Keep line endings for text files committed with CR/LF on text=auto
  Delay WindowCache statistics JMX MBean registration
  [releng] Update plexus-compiler to 2.8.7
  DirCache: support index V4
  Update javadoc for RemoteSession and SshSessionFactory
  Fix JSchProcess.waitFor() with time-out
  sshd: work around a race condition in Apache MINA sshd 2.4.0/2.5.x
  sshd: store per-session data on the sshd session object
  FilterSpec: Use BigInteger.ZERO instead of valueOf(0)
  Do not send empty blob in response to blob:none filter
  Add support for tree filters when fetching
  sshd: use PropertyResolver in test
  FS_POSIX: avoid prompt to install the XCode tools on OS X
  Remove dependency on JSch from SSH test framework
  Use LinkedBlockingQueue for executor determining filesystem attributes
  Update API warning filters
  Remove unused imports
  Bazel: Add workspace status command to stamp final artifact
  DiffFormatter: correctly deal with tracked files in ignored folders
  Prepare 5.8.2-SNAPSHOT builds
  JGit v5.8.1.202007141445-r
  Update Jetty to 9.4.30.v20200611
  Fix writing GPG signatures with trailing newline
  Rename a test method
  Add a test for upstream bug SSHD-1028
  Improve error message when receive.maxCommandBytes is exceeded
  LfsConnectionFactory#getLfsUrl: Fix unconditional break in for-loop
  DiffFormatterTest: Add a test to confirm the default rename detection settings
  Upgrade maven-site-plugin to 3.9.1
  Upgrade build-helper-maven-plugin to 3.2.0
  Upgrade spotbugs to 4.0.4
  MergedReftable: Include the last reftable in determining minUpdateIndex
  Add new osgi fragments to maven-central deploy scripts
  PackBitmapIndex: Not buffer inflated bitmap during bitmap creation.
  Do not require org.assertj.core.annotations
  Upgrade ecj to 3.22.0
  Remove workaround for signing jars using Tycho plugins
  Use https for URL of jgit website
  Fix CI information in pom.xml
  Use gitiles as scm url in pom.xml for browsing source code
  Update API baseline to 5.8.0.202006091008-r
  Remove trailing whitespace

Change-Id: Ie6bc6954741a47cfbd32c0886bdbd7b594f08b31
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
diff --git a/.bazelrc b/.bazelrc
index 3a61f8c..e24be88 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -1,3 +1,4 @@
+build --workspace_status_command="python ./tools/workspace_status.py"
 build --repository_cache=~/.gerritcodereview/bazel-cache/repository
 build --experimental_strict_action_env
 build --action_env=PATH
diff --git a/.bazelversion b/.bazelversion
index fd2a018..1545d96 100644
--- a/.bazelversion
+++ b/.bazelversion
@@ -1 +1 @@
-3.1.0
+3.5.0
diff --git a/Documentation/config-options.md b/Documentation/config-options.md
index 94ef5b9..d463551 100644
--- a/Documentation/config-options.md
+++ b/Documentation/config-options.md
@@ -11,15 +11,47 @@
 
 |  option | default | git option | description |
 |---------|---------|------------|-------------|
-| `core.bigFileThreshold` | `52428800` (50 MiB) | &#x2705; | Maximum file size that will be delta compressed. Files larger than this size are stored deflated, without attempting delta compression. |
-| `core.compression` | `-1` (default compression) | &#x2705; | An integer -1..9, indicating a default compression level. -1 is the zlib default. 0 means no compression, and 1..9 are various speed/size tradeoffs, 9 being slowest.|
+| `core.attributesFile` | | &#x2705; | In addition to `.gitattributes` (per-directory) and `.git/info/attributes`, Git looks into this file for attributes . Path expansions are made the same way as for `core.excludesFile`. |
+| `core.autocrlf` | `false` | &#x2705; | Setting this variable to `true` is the same as setting the text attribute to `auto` on all files and `core.eol` to `crlf`. Set to `true` if you want to have CRLF line endings in your working directory and the repository has LF line endings. This variable can be set to `input`, in which case no output conversion is performed. |
+| `core.bare` | set automatically on init or clone | &#x2705; | If true this repository is assumed to be bare and has no working directory associated with it. If this is the case a number of commands that require a working directory will be disabled |
+| `core.bigFileThreshold` | `50 MiB` | &#x2705; | Files larger than this size are stored deflated, without attempting delta compression. Storing large files without delta compression avoids excessive memory usage, at the slight expense of increased disk usage. Additionally files larger than this size are always treated as binary. |
+| `core.checkstat` |  | &#x2705; | When missing or is set to `default`, many fields in the stat structure are checked to detect if a file has been modified since Git looked at it. Checks as much of the dircache stat info as possible (in JGit limited by Java filesystem API). When set to `minimum` only checks the size and whole second part of time stamp when comparing the stat info in the dircache with actual file stat info. |
+| `core.compression` | `-1` (zlib default) | &#x2705; | An integer `-1..9`, indicating a default compression level. `-1` is the zlib default. `0` means no compression, and `1..9` are various speed/size tradeoffs, `9` being slowest.|
+| `core.deltaBaseCacheLimit` | `10 MiB` | &#x2705; | Maximum number of bytes to reserve for caching base objects that multiple deltafied objects reference. By storing the entire decompressed base object in a cache Git is able to avoid unpacking and decompressing frequently used base objects multiple times. |
+| `core.dfs.blockLimit` | `30 MiB` | &#x20DE; | Maximum number bytes of heap memory to dedicate to caching pack file data in DFS block cache. |
+| `core.dfs.blockSize` | `64 kiB` | &#x20DE; | Size in bytes of a single window read in from the pack file into the DFS block cache. |
+| `core.dfs.concurrencyLevel` | `32` | &#x20DE; | The estimated number of threads concurrently accessing the DFS block cache. |
+| `core.dfs.deltaBaseCacheLimit` | `10 MiB` | &#x20DE; | Maximum number of bytes to hold in per-reader DFS delta base cache. |
+| `core.dfs.streamFileThreshold` | `50 MiB` | &#x20DE; | The size threshold beyond which objects must be streamed. |
+| `core.dfs.streamBuffer` | Block size of the pack | &#x20DE; | Number of bytes to use for buffering when streaming a pack file during copying. If 0 the block size of the pack is used|
+| `core.dfs.streamRatio` | `0.30` | &#x20DE; | Ratio of DFS block cache to occupy with a copied pack. Values between `0` and `1.0`. |
+| `core.dirNoGitLinks` | `false` | &#x20DE; | If set to `true` avoid checking for submodules. See [bug 436200](https://bugs.eclipse.org/bugs/show_bug.cgi?id=436200). |
+| `core.eol` | `native` | &#x2705; | Sets the line ending type to use in the working directory for files that are marked as text (either by having the text attribute set, or by having `text=auto` and Git auto-detecting the contents as text). Alternatives are `lf`, `crlf` and `native`, which uses the platform’s native line ending. |
+| `core.excludesFile` | | &#x2705; | Specifies the pathname to the file that contains patterns to describe paths that are not meant to be tracked, in addition to `.gitignore` (per-directory) and `.git/info/exclude`. |
+| `core.fileMode` | Auto detects if file modes are supported | &#x2705; | Tells Git if the executable bit of files in the working tree is to be honored. |
+| `core.hideDotFiles` | `dotGitOnly` | &#x2705; | Windows only. If `true`, mark newly-created directories and files whose name starts with a dot as hidden. If `dotGitOnly`, only the `.git/` directory is hidden, but no other files starting with a dot. |
+| `core.hooksPath` | `$GIT_DIR/hooks` | &#x2705; | Path to look for hooks. |
+| `core.logAllRefUpdates` | `true` in a repository with working tree, `false` in bare repository | &#x2705; | Enable the reflog. |
+| `core.packedGitLimit` | `10 MiB` | &#x2705; | Maximum number of bytes to cache in memory from pack files. |
+| `core.packedGitMmap` | `false` | &#x2705; | Whether to use Java NIO virtual memory mapping for JGit buffer cache. When set to `true` enables use of Java NIO virtual memory mapping for cache windows, `false` reads entire window into a `byte[]` with standard read calls. `true` is experimental and may cause instabilities and crashes since Java doesn't support explicit unmapping of file regions mapped to virtual memory. |
+| `core.packedGitOpenFiles` | `128` | &#x20DE; | Maximum number of streams to open at a time. Open packs count against the process limits. |
+| `core.packedGitUseStrongRefs` | `false` | &#x20DE; | Whether the window cache should use strong references (`true`) or SoftReferences (`false`). When `false` the JVM will drop data cached in the JGit block cache when heap usage comes close to the maximum heap size. |
+| `core.packedGitWindowSize` | `8 kiB` | &#x2705; | Number of bytes of a pack file to load into memory in a single read operation. This is the "page size" of the JGit buffer cache, used for all pack access operations. All disk IO occurs as single window reads. Setting this too large may cause the process to load more data than is required; setting this too small may increase the frequency of read() system calls. |
+| `core.precomposeUnicode` | `true` on Mac OS | &#x2705; | MacOS only. When `true`, JGit reverts the unicode decomposition of filenames done by Mac OS. |
+| `core.quotePath` | `true` | &#x2705; | Commands that output paths (e.g. ls-files, diff), will quote "unusual" characters in the pathname by enclosing the pathname in double-quotes and escaping those characters with backslashes in the same way C escapes control characters (e.g. `\t` for TAB, `\n` for LF, `\\` for backslash) or bytes with values larger than `0x80` (e.g. octal `\302\265` for "micro" in UTF-8). |
+| `core.repositoryFormatVersion` | `1` | &#x20DE; | Internal version identifying the repository format and layout version. Don't set manually. |
+| `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.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
 
 |  option | default | git option | description |
 |---------|---------|------------|-------------|
-| `gc.aggressiveDepth` | 50 | &#x2705; | The depth parameter used in the delta compression algorithm used by aggressive garbage collection. |
-| `gc.aggressiveWindow` | 250 | &#x2705; | The window size parameter used in the delta compression algorithm used by aggressive garbage collection. |
+| `gc.aggressiveDepth` | `50` | &#x2705; | The depth parameter used in the delta compression algorithm used by aggressive garbage collection. |
+| `gc.aggressiveWindow` | `250` | &#x2705; | The window size parameter used in the delta compression algorithm used by aggressive garbage collection. |
 | `gc.auto` | `6700` | &#x2705; | Number of loose objects until auto gc combines all loose objects into a pack and consolidates all existing packs into one. Setting to 0 disables automatic packing of loose objects. |
 | `gc.autoDetach` | `true` |  &#x2705; | Make auto gc return immediately and run in background. |
 | `gc.autoPackLimit` | `50` |  &#x2705; | Number of packs until auto gc consolidates existing packs (except those marked with a .keep file) into a single pack. Setting `gc.autoPackLimit` to 0 disables automatic consolidation of packs. |
@@ -41,11 +73,11 @@
 | `pack.compression` | `core.compression` | &#x2705; | Compression level applied to objects in the pack. |
 | `pack.cutDeltaChains` | `false` | &#x20DE; | Whether existing delta chains should be cut at {@link #getMaxDeltaDepth() |
 | `pack.deltaCacheLimit` | `100` | &#x2705; | Maximum size in bytes of a delta to cache. |
-| `pack.deltaCacheSize` | `52428800` (50 MiB) | &#x2705; | Size of the in-memory delta cache. |
+| `pack.deltaCacheSize` | `50 MiB` | &#x2705; | Size of the in-memory delta cache. |
 | `pack.deltaCompression` | `true` | &#x20DE; | Whether the writer will create new deltas on the fly. `true` if the pack writer will create a new delta when either `pack.reuseDeltas` is false, or no suitable delta is available for reuse. |
 | `pack.depth` | `50` | &#x2705; | Maximum depth of delta chain set up for the pack writer. |
 | `pack.indexVersion` | `2` | &#x2705; | Pack index file format version. |
-| `pack.minSizePreventRacyPack` | `104857600` (100 MiB) | &#x20DE; | Minimum packfile size for which we wait before opening a newly written pack to prevent its lastModified timestamp could be racy if `pack.waitPreventRacyPack` is `true`. |
+| `pack.minSizePreventRacyPack` | `100 MiB` | &#x20DE; | Minimum packfile size for which we wait before opening a newly written pack to prevent its lastModified timestamp could be racy if `pack.waitPreventRacyPack` is `true`. |
 | `pack.preserveOldPacks` | `false` | &#x20DE; | Whether to preserve old packs in a preserved directory. |
 | `prunePreserved`, only via API of PackConfig | `false` | &#x20DE; | Whether to remove preserved pack files in a preserved directory. |
 | `pack.reuseDeltas` | `true` |&#x20DE; | Whether to reuse deltas existing in repository. |
diff --git a/WORKSPACE b/WORKSPACE
index 509cf6d..7651cba 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -107,8 +107,8 @@
 
 maven_jar(
     name = "commons-codec",
-    artifact = "commons-codec:commons-codec:1.13",
-    sha1 = "3f18e1aa31031d89db6f01ba05d501258ce69d2c",
+    artifact = "commons-codec:commons-codec:1.14",
+    sha1 = "3cb1181b2141a7e752f5bdc998b7ef1849f726cf",
 )
 
 maven_jar(
@@ -119,14 +119,14 @@
 
 maven_jar(
     name = "log-api",
-    artifact = "org.slf4j:slf4j-api:1.7.2",
-    sha1 = "0081d61b7f33ebeab314e07de0cc596f8e858d97",
+    artifact = "org.slf4j:slf4j-api:1.7.30",
+    sha1 = "b5a4b6d16ab13e34a88fae84c35cd5d68cac922c",
 )
 
 maven_jar(
     name = "slf4j-simple",
-    artifact = "org.slf4j:slf4j-simple:1.7.2",
-    sha1 = "760055906d7353ba4f7ce1b8908bc6b2e91f39fa",
+    artifact = "org.slf4j:slf4j-simple:1.7.30",
+    sha1 = "e606eac955f55ecf1d8edcccba04eb8ac98088dd",
 )
 
 maven_jar(
@@ -209,48 +209,48 @@
     sha1 = "3edcfe49d2c6053a70a2a47e4e1c2f94998a49cf",
 )
 
-JETTY_VER = "9.4.28.v20200408"
+JETTY_VER = "9.4.30.v20200611"
 
 maven_jar(
     name = "jetty-servlet",
     artifact = "org.eclipse.jetty:jetty-servlet:" + JETTY_VER,
-    sha1 = "7df27a6d73e3094ad94ea4f32e3e6597cecbdf38",
-    src_sha1 = "49da8455dd5760b7c5961df3b1e7d1490ff9723e",
+    sha1 = "ca3dea2cd34ee88cec017001603af0c9e74781d6",
+    src_sha1 = "6908f24428060bd542bddfa3e89e03d0dbbc2a6d",
 )
 
 maven_jar(
     name = "jetty-security",
     artifact = "org.eclipse.jetty:jetty-security:" + JETTY_VER,
-    sha1 = "d5fe6851f14d1375e4b4ab1818475bfd929cf517",
-    src_sha1 = "204f19ac7e4df9f6f68df1910154d7667ecd78e8",
+    sha1 = "1a5261f6ad4081ad9e9bb01416d639931d391273",
+    src_sha1 = "6ca41b34aa4f84c267603edd4b069122bd5f17d3",
 )
 
 maven_jar(
     name = "jetty-server",
     artifact = "org.eclipse.jetty:jetty-server:" + JETTY_VER,
-    sha1 = "9c2cbd96426be38b1273ec87ae21e2696688a737",
-    src_sha1 = "83454098deb880ecc7168252578f712c06a5504b",
+    sha1 = "e5ede3724d062717d0c04e4c77f74fe8115c2a6f",
+    src_sha1 = "c8b02a47a35c1f083b310cbd202738cf08bc1d55",
 )
 
 maven_jar(
     name = "jetty-http",
     artifact = "org.eclipse.jetty:jetty-http:" + JETTY_VER,
-    sha1 = "dd56750ea7410c925f1fbae973c0a19cce5a0a68",
-    src_sha1 = "1ef8d10cb5ce5694f12650cbb49b31008c673182",
+    sha1 = "cd6223382e4f82b9ea807d8cdb04a23e5d629f1c",
+    src_sha1 = "00520c04b10609b981159b5ca284b5a158c077a9",
 )
 
 maven_jar(
     name = "jetty-io",
     artifact = "org.eclipse.jetty:jetty-io:" + JETTY_VER,
-    sha1 = "adda6786588a922f834e9c33c7db5f1484310f44",
-    src_sha1 = "4e7756e00b97b439d404e6a682bb1cdeb36fc887",
+    sha1 = "9c360d08e903b2dbd5d1f8e889a32046948628ce",
+    src_sha1 = "dac8f8a3f84afdd3686d36f58b5ccb276961b8ce",
 )
 
 maven_jar(
     name = "jetty-util",
     artifact = "org.eclipse.jetty:jetty-util:" + JETTY_VER,
-    sha1 = "118d2a44721885a04238aee21a5055dc1ab3818a",
-    src_sha1 = "e2e6d7c90e4126645d2667014d02f0732c08c948",
+    sha1 = "39ec6aa4745952077f5407cb1394d8ba2db88b13",
+    src_sha1 = "f41f9391f91884a79350f3ad9b09b8e46c9be0ec",
 )
 
 BOUNCYCASTLE_VER = "1.65"
diff --git a/org.eclipse.jgit.benchmarks/pom.xml b/org.eclipse.jgit.benchmarks/pom.xml
index 1d735bd..5941f13 100644
--- a/org.eclipse.jgit.benchmarks/pom.xml
+++ b/org.eclipse.jgit.benchmarks/pom.xml
@@ -179,7 +179,7 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-project-info-reports-plugin</artifactId>
-          <version>3.0.0</version>
+          <version>3.1.1</version>
         </plugin>
       </plugins>
     </pluginManagement>
diff --git a/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF b/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF
index aa851a9..b59a850 100644
--- a/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF
@@ -8,8 +8,7 @@
 Bundle-Localization: plugin
 Bundle-Version: 6.0.0.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Import-Package: org.assertj.core.annotations;version="3.14.0",
- org.bouncycastle.bcpg;version="[1.65.0,2.0.0)",
+Import-Package: org.bouncycastle.bcpg;version="[1.65.0,2.0.0)",
  org.bouncycastle.gpg;version="[1.65.0,2.0.0)",
  org.bouncycastle.gpg.keybox;version="[1.65.0,2.0.0)",
  org.bouncycastle.gpg.keybox.jcajce;version="[1.65.0,2.0.0)",
diff --git a/org.eclipse.jgit.gpg.bc/pom.xml b/org.eclipse.jgit.gpg.bc/pom.xml
index 64f784d..833d23c 100644
--- a/org.eclipse.jgit.gpg.bc/pom.xml
+++ b/org.eclipse.jgit.gpg.bc/pom.xml
@@ -128,7 +128,6 @@
         </configuration>
       </plugin>
 
-      <!-- No previous version to compare to
       <plugin>
           <groupId>com.github.siom79.japicmp</groupId>
           <artifactId>japicmp-maven-plugin</artifactId>
@@ -170,13 +169,11 @@
           </execution>
         </executions>
       </plugin>
-      -->
     </plugins>
   </build>
 
   <reporting>
     <plugins>
-      <!-- No previous version to compare to
       <plugin>
           <groupId>com.github.siom79.japicmp</groupId>
           <artifactId>japicmp-maven-plugin</artifactId>
@@ -217,7 +214,6 @@
               <skip>false</skip>
           </configuration>
       </plugin>
-      -->
     </plugins>
   </reporting>
 </project>
diff --git a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSigner.java b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSigner.java
index c6ecdbe..ea159c5 100644
--- a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSigner.java
+++ b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSigner.java
@@ -15,6 +15,7 @@
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
 import java.security.Security;
+import java.util.Iterator;
 
 import org.bouncycastle.bcpg.ArmoredOutputStream;
 import org.bouncycastle.bcpg.BCPGOutputStream;
@@ -22,6 +23,7 @@
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.openpgp.PGPException;
 import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPPublicKey;
 import org.bouncycastle.openpgp.PGPSecretKey;
 import org.bouncycastle.openpgp.PGPSignature;
 import org.bouncycastle.openpgp.PGPSignatureGenerator;
@@ -38,6 +40,7 @@
 import org.eclipse.jgit.lib.GpgSigner;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.util.StringUtils;
 
 /**
  * GPG Signer using BouncyCastle library
@@ -126,17 +129,32 @@
 				privateKey = secretKey
 						.extractPrivateKey(decryptorBuilder.build(passphrase));
 			}
+			PGPPublicKey publicKey = secretKey.getPublicKey();
 			PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(
 					new JcaPGPContentSignerBuilder(
-							secretKey.getPublicKey().getAlgorithm(),
+							publicKey.getAlgorithm(),
 							HashAlgorithmTags.SHA256).setProvider(
 									BouncyCastleProvider.PROVIDER_NAME));
 			signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, privateKey);
-			PGPSignatureSubpacketGenerator subpacketGenerator = new PGPSignatureSubpacketGenerator();
-			subpacketGenerator.setIssuerFingerprint(false,
-					secretKey.getPublicKey());
+			PGPSignatureSubpacketGenerator subpackets = new PGPSignatureSubpacketGenerator();
+			subpackets.setIssuerFingerprint(false, publicKey);
+			// Also add the signer's user ID. Note that GPG uses only the e-mail
+			// address part.
+			String userId = committer.getEmailAddress();
+			Iterator<String> userIds = publicKey.getUserIDs();
+			if (userIds.hasNext()) {
+				String keyUserId = userIds.next();
+				if (!StringUtils.isEmptyOrNull(keyUserId)
+						&& (userId == null || !keyUserId.contains(userId))) {
+					// Not the committer's key?
+					userId = extractSignerId(keyUserId);
+				}
+			}
+			if (userId != null) {
+				subpackets.setSignerUserID(false, userId);
+			}
 			signatureGenerator
-					.setHashedSubpackets(subpacketGenerator.generate());
+					.setHashedSubpackets(subpackets.generate());
 			ByteArrayOutputStream buffer = new ByteArrayOutputStream();
 			try (BCPGOutputStream out = new BCPGOutputStream(
 					new ArmoredOutputStream(buffer))) {
@@ -149,4 +167,15 @@
 			throw new JGitInternalException(e.getMessage(), e);
 		}
 	}
+
+	private String extractSignerId(String pgpUserId) {
+		int from = pgpUserId.indexOf('<');
+		if (from >= 0) {
+			int to = pgpUserId.indexOf('>', from + 1);
+			if (to > from + 1) {
+				return pgpUserId.substring(from + 1, to);
+			}
+		}
+		return pgpUserId;
+	}
 }
diff --git a/org.eclipse.jgit.junit.ssh/BUILD b/org.eclipse.jgit.junit.ssh/BUILD
index 61b5ce7..f7856b5 100644
--- a/org.eclipse.jgit.junit.ssh/BUILD
+++ b/org.eclipse.jgit.junit.ssh/BUILD
@@ -13,8 +13,8 @@
         "//org.eclipse.jgit.ssh.jsch.test:__pkg__",
     ],
     deps = [
-        "//lib:jsch",
         "//lib:junit",
+        "//lib:slf4j-api",
         "//lib:sshd-osgi",
         "//lib:sshd-sftp",
         # We want these deps to be provided_deps
diff --git a/org.eclipse.jgit.junit.ssh/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit.ssh/META-INF/MANIFEST.MF
index 2b06d48..8858c09 100644
--- a/org.eclipse.jgit.junit.ssh/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.junit.ssh/META-INF/MANIFEST.MF
@@ -8,8 +8,7 @@
 Bundle-Vendor: %Bundle-Vendor
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Import-Package: com.jcraft.jsch;version="0.1.55",
- org.apache.sshd.common;version="[2.4.0,2.5.0)",
+Import-Package: org.apache.sshd.common;version="[2.4.0,2.5.0)",
  org.apache.sshd.common.config.keys;version="[2.4.0,2.5.0)",
  org.apache.sshd.common.file.virtualfs;version="[2.4.0,2.5.0)",
  org.apache.sshd.common.helpers;version="[2.4.0,2.5.0)",
diff --git a/org.eclipse.jgit.junit.ssh/pom.xml b/org.eclipse.jgit.junit.ssh/pom.xml
index cd46260..a6f4660 100644
--- a/org.eclipse.jgit.junit.ssh/pom.xml
+++ b/org.eclipse.jgit.junit.ssh/pom.xml
@@ -58,16 +58,6 @@
     </dependency>
 
     <dependency>
-      <groupId>com.jcraft</groupId>
-      <artifactId>jsch</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>com.jcraft</groupId>
-      <artifactId>jzlib</artifactId>
-    </dependency>
-
-    <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
       <scope>provided</scope>
diff --git a/org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestBase.java b/org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestBase.java
index 2d284cf..3784741 100644
--- a/org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestBase.java
+++ b/org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestBase.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2018, 2020 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
@@ -14,6 +14,7 @@
 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.Assume.assumeTrue;
 
@@ -22,9 +23,14 @@
 import java.nio.file.Files;
 import java.util.List;
 import java.util.Locale;
+import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jgit.api.errors.TransportException;
+import org.eclipse.jgit.errors.CommandFailedException;
 import org.eclipse.jgit.transport.CredentialItem;
+import org.eclipse.jgit.transport.URIish;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.SshSupport;
 import org.junit.Test;
 import org.junit.experimental.theories.DataPoints;
 import org.junit.experimental.theories.Theory;
@@ -67,10 +73,45 @@
 		defaultCloneDir = new File(getTemporaryDirectory(), "cloned");
 	}
 
-	@Test(expected = TransportException.class)
+	@Test
 	public void testSshWithoutConfig() throws Exception {
-		cloneWith("ssh://" + TEST_USER + "@localhost:" + testPort
-				+ "/doesntmatter", defaultCloneDir, null);
+		assertThrows(TransportException.class,
+				() -> cloneWith("ssh://" + TEST_USER + "@localhost:" + testPort
+						+ "/doesntmatter", defaultCloneDir, null));
+	}
+
+	@Test
+	public void testSingleCommand() throws Exception {
+		installConfig("IdentityFile " + privateKey1.getAbsolutePath());
+		String command = SshTestGitServer.ECHO_COMMAND + " 1 without timeout";
+		long start = System.nanoTime();
+		String reply = SshSupport.runSshCommand(
+				new URIish("ssh://" + TEST_USER + "@localhost:" + testPort),
+				null, FS.DETECTED, command, 0); // 0 == no timeout
+		long elapsed = System.nanoTime() - start;
+		assertEquals(command, reply);
+		// Now that we have an idea how long this takes on the test
+		// infrastructure, try again with a timeout.
+		command = SshTestGitServer.ECHO_COMMAND + " 1 expecting no timeout";
+		// Still use a generous timeout.
+		int timeout = 10 * ((int) TimeUnit.NANOSECONDS.toSeconds(elapsed) + 1);
+		reply = SshSupport.runSshCommand(
+				new URIish("ssh://" + TEST_USER + "@localhost:" + testPort),
+				null, FS.DETECTED, command, timeout);
+		assertEquals(command, reply);
+	}
+
+	@Test
+	public void testSingleCommandWithTimeoutExpired() throws Exception {
+		installConfig("IdentityFile " + privateKey1.getAbsolutePath());
+		String command = SshTestGitServer.ECHO_COMMAND + " 2 EXPECTING TIMEOUT";
+
+		CommandFailedException e = assertThrows(CommandFailedException.class,
+				() -> SshSupport.runSshCommand(new URIish(
+						"ssh://" + TEST_USER + "@localhost:" + testPort), null,
+						FS.DETECTED, command, 1));
+		assertTrue(e.getMessage().contains(command));
+		assertTrue(e.getMessage().contains("time"));
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestGitServer.java b/org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestGitServer.java
index 03e2855..ab8e0c1 100644
--- a/org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestGitServer.java
+++ b/org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestGitServer.java
@@ -12,6 +12,8 @@
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.security.GeneralSecurityException;
@@ -22,8 +24,10 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
+import java.util.concurrent.TimeUnit;
 
 import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.PropertyResolver;
 import org.apache.sshd.common.PropertyResolverUtils;
 import org.apache.sshd.common.SshConstants;
 import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
@@ -66,6 +70,16 @@
  */
 public class SshTestGitServer {
 
+	/**
+	 * Simple echo test command. Replies with the command string as passed. If
+	 * of the form "echo [int] anything", takes the integer value as a delay in
+	 * seconds before replying, which may be useful to test various
+	 * timeout-related things.
+	 *
+	 * @since 5.9
+	 */
+	public static final String ECHO_COMMAND = "echo";
+
 	@NonNull
 	protected final String testUser;
 
@@ -90,8 +104,7 @@
 	 * @param testUser
 	 *            user name of the test user
 	 * @param testKey
-	 *            <em>private</em> key file of the test user; the server will
-	 *            only user the public key from it
+	 *            public key file of the test user
 	 * @param repository
 	 *            to serve
 	 * @param hostKey
@@ -102,17 +115,54 @@
 	public SshTestGitServer(@NonNull String testUser, @NonNull Path testKey,
 			@NonNull Repository repository, @NonNull byte[] hostKey)
 			throws IOException, GeneralSecurityException {
+		this(testUser, readPublicKey(testKey), repository,
+				readKeyPair(hostKey));
+	}
+
+	/**
+	 * Creates a ssh git <em>test</em> server. It serves one single repository,
+	 * and accepts public-key authentication for exactly one test user.
+	 *
+	 * @param testUser
+	 *            user name of the test user
+	 * @param testKey
+	 *            public key file of the test user
+	 * @param repository
+	 *            to serve
+	 * @param hostKey
+	 *            the unencrypted private key to use as host key
+	 * @throws IOException
+	 * @throws GeneralSecurityException
+	 * @since 5.9
+	 */
+	public SshTestGitServer(@NonNull String testUser, @NonNull Path testKey,
+			@NonNull Repository repository, @NonNull KeyPair hostKey)
+			throws IOException, GeneralSecurityException {
+		this(testUser, readPublicKey(testKey), repository, hostKey);
+	}
+
+	/**
+	 * Creates a ssh git <em>test</em> server. It serves one single repository,
+	 * and accepts public-key authentication for exactly one test user.
+	 *
+	 * @param testUser
+	 *            user name of the test user
+	 * @param testKey
+	 *            the {@link PublicKey} of the test user
+	 * @param repository
+	 *            to serve
+	 * @param hostKey
+	 *            the {@link KeyPair} to use as host key
+	 * @since 5.9
+	 */
+	public SshTestGitServer(@NonNull String testUser,
+			@NonNull PublicKey testKey, @NonNull Repository repository,
+			@NonNull KeyPair hostKey) {
 		this.testUser = testUser;
 		setTestUserPublicKey(testKey);
 		this.repository = repository;
 		server = SshServer.setUpDefaultServer();
-		// Set host key
-		try (ByteArrayInputStream in = new ByteArrayInputStream(hostKey)) {
-			SecurityUtils.loadKeyPairIdentities(null, null, in, null)
-					.forEach((k) -> hostKeys.add(k));
-		} catch (IOException | GeneralSecurityException e) {
-			// Ignore.
-		}
+		hostKeys.add(hostKey);
 		server.setKeyPairProvider((session) -> hostKeys);
 
 		configureAuthentication();
@@ -129,11 +179,27 @@
 				return new GitUploadPackCommand(command, executorService);
 			} else if (command.startsWith(RemoteConfig.DEFAULT_RECEIVE_PACK)) {
 				return new GitReceivePackCommand(command, executorService);
+			} else if (command.startsWith(ECHO_COMMAND)) {
+				return new EchoCommand(command, executorService);
 			}
 			return new UnknownCommand(command);
 		});
 	}
 
+	private static PublicKey readPublicKey(Path key)
+			throws IOException, GeneralSecurityException {
+		return AuthorizedKeyEntry.readAuthorizedKeys(key).get(0)
+				.resolvePublicKey(null, PublicKeyEntryResolver.IGNORING);
+	}
+
+	private static KeyPair readKeyPair(byte[] keyMaterial)
+			throws IOException, GeneralSecurityException {
+		try (ByteArrayInputStream in = new ByteArrayInputStream(keyMaterial)) {
+			return SecurityUtils.loadKeyPairIdentities(null, null, in, null)
+					.iterator().next();
+		}
+	}
+
 	private static class FakeUserAuthGSS extends UserAuthGSS {
 		@Override
 		protected Boolean doAuth(Buffer buffer, boolean initial)
@@ -298,6 +364,17 @@
 	}
 
 	/**
+	 * Retrieves the server's {@link PropertyResolver}, giving access to server
+	 * properties.
+	 *
+	 * @return the {@link PropertyResolver}
+	 * @since 5.9
+	 */
+	public PropertyResolver getPropertyResolver() {
+		return server;
+	}
+
+	/**
 	 * Starts the test server, listening on a random port.
 	 *
 	 * @return the port the server listens on; test clients should connect to
@@ -331,8 +408,7 @@
 	 */
 	public void setTestUserPublicKey(Path key)
 			throws IOException, GeneralSecurityException {
-		this.testKey = AuthorizedKeyEntry.readAuthorizedKeys(key).get(0)
-				.resolvePublicKey(null, PublicKeyEntryResolver.IGNORING);
+		this.testKey = readPublicKey(key);
 	}
 
 	/**
@@ -414,4 +490,52 @@
 		}
 
 	}
+
+	/**
+	 * Simple echo command that echoes back the command string. If the first
+	 * argument is a positive integer, it's taken as a delay (in seconds) before
+	 * replying. Assumes UTF-8 character encoding.
+	 */
+	private static class EchoCommand extends AbstractCommandSupport {
+
+		protected EchoCommand(String command,
+				CloseableExecutorService executorService) {
+			super(command, ThreadUtils.noClose(executorService));
+		}
+
+		@Override
+		public void run() {
+			String[] parts = getCommand().split(" ");
+			int timeout = 0;
+			if (parts.length >= 2) {
+				try {
+					timeout = Integer.parseInt(parts[1]);
+				} catch (NumberFormatException e) {
+					// No timeout.
+				}
+				if (timeout > 0) {
+					try {
+						Thread.sleep(TimeUnit.SECONDS.toMillis(timeout));
+					} catch (InterruptedException e) {
+						// Ignore.
+					}
+				}
+			}
+			try {
+				doEcho(getCommand(), getOutputStream());
+				onExit(0);
+			} catch (IOException e) {
+				log.warn(
+						MessageFormat.format("Could not run {0}", getCommand()),
+						e);
+				onExit(-1, e.toString());
+			}
+		}
+
+		private void doEcho(String text, OutputStream stream)
+				throws IOException {
+			stream.write(text.getBytes(StandardCharsets.UTF_8));
+			stream.flush();
+		}
+	}
 }
diff --git a/org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestHarness.java b/org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestHarness.java
index 43f9dc4..90d981b 100644
--- a/org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestHarness.java
+++ b/org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestHarness.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2018, 2020 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
@@ -9,27 +9,31 @@
  */
 package org.eclipse.jgit.junit.ssh;
 
-import static java.nio.charset.StandardCharsets.US_ASCII;
-import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
-import java.io.ByteArrayOutputStream;
+import java.io.BufferedWriter;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Base64;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
 import org.eclipse.jgit.api.CloneCommand;
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.PushCommand;
@@ -48,9 +52,6 @@
 import org.eclipse.jgit.util.FS;
 import org.junit.After;
 
-import com.jcraft.jsch.JSch;
-import com.jcraft.jsch.KeyPair;
-
 /**
  * Root class for ssh tests. Sets up the ssh test server. A set of pre-computed
  * keys for testing is provided in the bundle and can be used in test cases via
@@ -75,6 +76,8 @@
 
 	protected File publicKey1;
 
+	protected File publicKey2;
+
 	protected SshTestGitServer server;
 
 	private SshSessionFactory factory;
@@ -104,50 +107,71 @@
 		File serverDir = new File(getTemporaryDirectory(), "srv");
 		assertTrue(serverDir.mkdir());
 		// Create two key pairs. Let's not call them "id_rsa".
+		KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
+		generator.initialize(2048);
 		privateKey1 = new File(sshDir, "first_key");
 		privateKey2 = new File(sshDir, "second_key");
-		publicKey1 = createKeyPair(privateKey1);
-		createKeyPair(privateKey2);
-		ByteArrayOutputStream publicHostKey = new ByteArrayOutputStream();
+		publicKey1 = createKeyPair(generator.generateKeyPair(), privateKey1);
+		publicKey2 = createKeyPair(generator.generateKeyPair(), privateKey2);
+		// Create a host key
+		KeyPair hostKey = generator.generateKeyPair();
 		// Start a server with our test user and the first key.
 		server = new SshTestGitServer(TEST_USER, publicKey1.toPath(), db,
-				createHostKey(publicHostKey));
+				hostKey);
 		testPort = server.start();
 		assertTrue(testPort > 0);
 		knownHosts = new File(sshDir, "known_hosts");
-		Files.write(knownHosts.toPath(), Collections.singleton("[localhost]:"
-				+ testPort + ' '
-				+ publicHostKey.toString(US_ASCII.name())));
+		StringBuilder knownHostsLine = new StringBuilder();
+		knownHostsLine.append("[localhost]:").append(testPort).append(' ');
+		PublicKeyEntry.appendPublicKeyEntry(knownHostsLine,
+				hostKey.getPublic());
+		Files.write(knownHosts.toPath(),
+				Collections.singleton(knownHostsLine.toString()));
 		factory = createSessionFactory();
 		SshSessionFactory.setInstance(factory);
 	}
 
-	private static File createKeyPair(File privateKeyFile) throws Exception {
-		// Found no way to do this with MINA sshd except rolling it all
-		// ourselves...
-		JSch jsch = new JSch();
-		KeyPair pair = KeyPair.genKeyPair(jsch, KeyPair.RSA, 2048);
-		try (OutputStream out = new FileOutputStream(privateKeyFile)) {
-			pair.writePrivateKey(out);
+	private static File createKeyPair(KeyPair newKey, File privateKeyFile)
+			throws Exception {
+		// Write PKCS#8 PEM unencrypted. Both JSch and sshd can read that.
+		PrivateKey privateKey = newKey.getPrivate();
+		String format = privateKey.getFormat();
+		if (!"PKCS#8".equalsIgnoreCase(format)) {
+			throw new IOException("Cannot write " + privateKey.getAlgorithm()
+					+ " key in " + format + " format");
+		}
+		try (BufferedWriter writer = Files.newBufferedWriter(
+				privateKeyFile.toPath(), StandardCharsets.US_ASCII)) {
+			writer.write("-----BEGIN PRIVATE KEY-----");
+			writer.newLine();
+			write(writer, privateKey.getEncoded(), 64);
+			writer.write("-----END PRIVATE KEY-----");
+			writer.newLine();
 		}
 		File publicKeyFile = new File(privateKeyFile.getParentFile(),
 				privateKeyFile.getName() + ".pub");
+		StringBuilder builder = new StringBuilder();
+		PublicKeyEntry.appendPublicKeyEntry(builder, newKey.getPublic());
+		builder.append(' ').append(TEST_USER);
 		try (OutputStream out = new FileOutputStream(publicKeyFile)) {
-			pair.writePublicKey(out, TEST_USER);
+			out.write(builder.toString().getBytes(StandardCharsets.US_ASCII));
 		}
 		return publicKeyFile;
 	}
 
-	private static byte[] createHostKey(OutputStream publicKey)
-			throws Exception {
-		JSch jsch = new JSch();
-		KeyPair pair = KeyPair.genKeyPair(jsch, KeyPair.RSA, 2048);
-		pair.writePublicKey(publicKey, "");
-		try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
-			pair.writePrivateKey(out);
-			out.flush();
-			return out.toByteArray();
+	private static void write(BufferedWriter out, byte[] bytes, int lineLength)
+			throws IOException {
+		String data = Base64.getEncoder().encodeToString(bytes);
+		int last = data.length();
+		for (int i = 0; i < last; i += lineLength) {
+			if (i + lineLength <= last) {
+				out.write(data.substring(i, i + lineLength));
+			} else {
+				out.write(data.substring(i));
+			}
+			out.newLine();
 		}
+		Arrays.fill(bytes, (byte) 0);
 	}
 
 	/**
@@ -167,7 +191,8 @@
 	 */
 	protected static String createKnownHostsFile(File file, String host,
 			int port, File publicKey) throws IOException {
-		List<String> lines = Files.readAllLines(publicKey.toPath(), UTF_8);
+		List<String> lines = Files.readAllLines(publicKey.toPath(),
+				StandardCharsets.UTF_8);
 		assertEquals("Public key has too many lines", 1, lines.size());
 		String pubKey = lines.get(0);
 		// Strip off the comment.
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java
index de11e2c..64556ac 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java
@@ -34,6 +34,7 @@
 import org.eclipse.jgit.dircache.DirCacheCheckout;
 import org.eclipse.jgit.dircache.DirCacheEntry;
 import org.eclipse.jgit.internal.storage.file.FileRepository;
+import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
@@ -45,6 +46,7 @@
 import org.eclipse.jgit.treewalk.FileTreeIterator;
 import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.FileUtils;
+import org.junit.After;
 import org.junit.Before;
 
 /**
@@ -186,6 +188,13 @@
 		trash = db.getWorkTree();
 	}
 
+	@Override
+	@After
+	public void tearDown() throws Exception {
+		db.close();
+		super.tearDown();
+	}
+
 	/**
 	 * Represent the state of the index in one String. This representation is
 	 * useful when writing tests which do assertions on the state of the index.
@@ -513,6 +522,21 @@
 	}
 
 	/**
+	 * Create <code>DirCacheEntry</code>
+	 *
+	 * @param path
+	 * @param objectId
+	 * @return the DirCacheEntry
+	 */
+	protected DirCacheEntry createGitLink(String path, AnyObjectId objectId) {
+		final DirCacheEntry entry = new DirCacheEntry(path,
+				DirCacheEntry.STAGE_0);
+		entry.setFileMode(FileMode.GITLINK);
+		entry.setObjectId(objectId);
+		return entry;
+	}
+
+	/**
 	 * Assert files are equal
 	 *
 	 * @param expected
diff --git a/org.eclipse.jgit.lfs.test/BUILD b/org.eclipse.jgit.lfs.test/BUILD
index 061ecd7..4665f67 100644
--- a/org.eclipse.jgit.lfs.test/BUILD
+++ b/org.eclipse.jgit.lfs.test/BUILD
@@ -13,6 +13,7 @@
     deps = [
         ":helpers",
         "//lib:junit",
+        "//lib:slf4j-api",
         "//org.eclipse.jgit:jgit",
         "//org.eclipse.jgit.junit:junit",
         "//org.eclipse.jgit.lfs:jgit-lfs",
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 cbad1b6..7a0ed45 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
@@ -112,8 +112,8 @@
 					remoteUrl = config.getString(
 							ConfigConstants.CONFIG_KEY_REMOTE, remote,
 							ConfigConstants.CONFIG_KEY_URL);
+					break;
 				}
-				break;
 			}
 			if (lfsUrl == null && remoteUrl != null) {
 				try {
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target
index 452503a..931e196 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target
@@ -1,26 +1,26 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.10" sequenceNumber="1590935844">
+<target name="jgit-4.10" sequenceNumber="1605866255">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="org.eclipse.jetty.client" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.client.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.continuation" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.continuation.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.http" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.http.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.io" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.io.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.security" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.security.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.server" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.server.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.servlet" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.servlet.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.util" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.util.source" version="9.4.28.v20200408"/>
-      <repository id="jetty-9.4.25" location="https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.28.v20200408/"/>
+      <unit id="org.eclipse.jetty.client" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.client.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.continuation" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.continuation.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.http" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.http.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.io" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.io.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.security" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.security.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.server" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.server.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.servlet" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.servlet.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.util" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.util.source" version="9.4.30.v20200611"/>
+      <repository id="jetty-9.4.30" location="https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.30.v20200611/"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="com.google.gson" version="2.8.2.v20180104-1110"/>
@@ -39,16 +39,16 @@
       <unit id="net.bytebuddy.byte-buddy.source" version="1.9.0.v20181107-1410"/>
       <unit id="net.i2p.crypto.eddsa" version="0.3.0.v20181102-1323"/>
       <unit id="net.i2p.crypto.eddsa.source" version="0.3.0.v20181102-1323"/>
-      <unit id="org.apache.ant" version="1.10.8.v20200515-1239"/>
-      <unit id="org.apache.ant.source" version="1.10.8.v20200515-1239"/>
-      <unit id="org.apache.commons.codec" version="1.13.0.v20200108-0001"/>
-      <unit id="org.apache.commons.codec.source" version="1.13.0.v20200108-0001"/>
+      <unit id="org.apache.ant" version="1.10.9.v20201106-1946"/>
+      <unit id="org.apache.ant.source" version="1.10.9.v20201106-1946"/>
+      <unit id="org.apache.commons.codec" version="1.14.0.v20200818-1422"/>
+      <unit id="org.apache.commons.codec.source" version="1.14.0.v20200818-1422"/>
       <unit id="org.apache.commons.compress" version="1.19.0.v20200106-2343"/>
       <unit id="org.apache.commons.compress.source" version="1.19.0.v20200106-2343"/>
       <unit id="org.apache.commons.logging" version="1.2.0.v20180409-1502"/>
       <unit id="org.apache.commons.logging.source" version="1.2.0.v20180409-1502"/>
-      <unit id="org.apache.httpcomponents.httpclient" version="4.5.10.v20200114-1512"/>
-      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.10.v20200114-1512"/>
+      <unit id="org.apache.httpcomponents.httpclient" version="4.5.10.v20200830-2311"/>
+      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.10.v20200830-2311"/>
       <unit id="org.apache.httpcomponents.httpcore" version="4.4.12.v20200108-1212"/>
       <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.12.v20200108-1212"/>
       <unit id="org.apache.log4j" version="1.2.15.v201012070815"/>
@@ -78,13 +78,13 @@
       <unit id="org.mockito.source" version="2.23.0.v20200310-1642"/>
       <unit id="org.objenesis" version="2.6.0.v20180420-1519"/>
       <unit id="org.objenesis.source" version="2.6.0.v20180420-1519"/>
-      <unit id="org.slf4j.api" version="1.7.2.v20121108-1250"/>
-      <unit id="org.slf4j.api.source" version="1.7.2.v20121108-1250"/>
-      <unit id="org.slf4j.impl.log4j12" version="1.7.2.v20131105-2200"/>
-      <unit id="org.slf4j.impl.log4j12.source" version="1.7.2.v20131105-2200"/>
+      <unit id="org.slf4j.api" version="1.7.30.v20200204-2150"/>
+      <unit id="org.slf4j.api.source" version="1.7.30.v20200204-2150"/>
+      <unit id="org.slf4j.binding.log4j12" version="1.7.30.v20201108-2042"/>
+      <unit id="org.slf4j.binding.log4j12.source" version="1.7.30.v20201108-2042"/>
       <unit id="org.tukaani.xz" version="1.8.0.v20180207-1613"/>
       <unit id="org.tukaani.xz.source" version="1.8.0.v20180207-1613"/>
-      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/R20200529191137/repository"/>
+      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/S20201118210000/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.10.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.tpd
index 717bc0a..b87917b 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.tpd
@@ -1,7 +1,7 @@
 target "jgit-4.10" with source configurePhase
 
 include "projects/jetty-9.4.x.tpd"
-include "orbit/R20200529191137-2020-06.tpd"
+include "orbit/S20201118210000.tpd"
 
 location "https://download.eclipse.org/releases/2018-12/" {
 	org.eclipse.osgi lazy
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target
index 03e3641..8090999 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target
@@ -1,26 +1,26 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.11" sequenceNumber="1590935852">
+<target name="jgit-4.11" sequenceNumber="1605866333">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="org.eclipse.jetty.client" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.client.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.continuation" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.continuation.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.http" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.http.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.io" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.io.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.security" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.security.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.server" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.server.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.servlet" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.servlet.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.util" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.util.source" version="9.4.28.v20200408"/>
-      <repository id="jetty-9.4.25" location="https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.28.v20200408/"/>
+      <unit id="org.eclipse.jetty.client" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.client.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.continuation" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.continuation.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.http" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.http.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.io" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.io.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.security" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.security.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.server" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.server.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.servlet" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.servlet.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.util" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.util.source" version="9.4.30.v20200611"/>
+      <repository id="jetty-9.4.30" location="https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.30.v20200611/"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="com.google.gson" version="2.8.2.v20180104-1110"/>
@@ -39,16 +39,16 @@
       <unit id="net.bytebuddy.byte-buddy.source" version="1.9.0.v20181107-1410"/>
       <unit id="net.i2p.crypto.eddsa" version="0.3.0.v20181102-1323"/>
       <unit id="net.i2p.crypto.eddsa.source" version="0.3.0.v20181102-1323"/>
-      <unit id="org.apache.ant" version="1.10.8.v20200515-1239"/>
-      <unit id="org.apache.ant.source" version="1.10.8.v20200515-1239"/>
-      <unit id="org.apache.commons.codec" version="1.13.0.v20200108-0001"/>
-      <unit id="org.apache.commons.codec.source" version="1.13.0.v20200108-0001"/>
+      <unit id="org.apache.ant" version="1.10.9.v20201106-1946"/>
+      <unit id="org.apache.ant.source" version="1.10.9.v20201106-1946"/>
+      <unit id="org.apache.commons.codec" version="1.14.0.v20200818-1422"/>
+      <unit id="org.apache.commons.codec.source" version="1.14.0.v20200818-1422"/>
       <unit id="org.apache.commons.compress" version="1.19.0.v20200106-2343"/>
       <unit id="org.apache.commons.compress.source" version="1.19.0.v20200106-2343"/>
       <unit id="org.apache.commons.logging" version="1.2.0.v20180409-1502"/>
       <unit id="org.apache.commons.logging.source" version="1.2.0.v20180409-1502"/>
-      <unit id="org.apache.httpcomponents.httpclient" version="4.5.10.v20200114-1512"/>
-      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.10.v20200114-1512"/>
+      <unit id="org.apache.httpcomponents.httpclient" version="4.5.10.v20200830-2311"/>
+      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.10.v20200830-2311"/>
       <unit id="org.apache.httpcomponents.httpcore" version="4.4.12.v20200108-1212"/>
       <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.12.v20200108-1212"/>
       <unit id="org.apache.log4j" version="1.2.15.v201012070815"/>
@@ -78,13 +78,13 @@
       <unit id="org.mockito.source" version="2.23.0.v20200310-1642"/>
       <unit id="org.objenesis" version="2.6.0.v20180420-1519"/>
       <unit id="org.objenesis.source" version="2.6.0.v20180420-1519"/>
-      <unit id="org.slf4j.api" version="1.7.2.v20121108-1250"/>
-      <unit id="org.slf4j.api.source" version="1.7.2.v20121108-1250"/>
-      <unit id="org.slf4j.impl.log4j12" version="1.7.2.v20131105-2200"/>
-      <unit id="org.slf4j.impl.log4j12.source" version="1.7.2.v20131105-2200"/>
+      <unit id="org.slf4j.api" version="1.7.30.v20200204-2150"/>
+      <unit id="org.slf4j.api.source" version="1.7.30.v20200204-2150"/>
+      <unit id="org.slf4j.binding.log4j12" version="1.7.30.v20201108-2042"/>
+      <unit id="org.slf4j.binding.log4j12.source" version="1.7.30.v20201108-2042"/>
       <unit id="org.tukaani.xz" version="1.8.0.v20180207-1613"/>
       <unit id="org.tukaani.xz.source" version="1.8.0.v20180207-1613"/>
-      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/R20200529191137/repository"/>
+      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/S20201118210000/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.11.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.tpd
index 9abd9b2..844a1d7 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.tpd
@@ -1,7 +1,7 @@
 target "jgit-4.11" with source configurePhase
 
 include "projects/jetty-9.4.x.tpd"
-include "orbit/R20200529191137-2020-06.tpd"
+include "orbit/S20201118210000.tpd"
 
 location "https://download.eclipse.org/releases/2019-03/" {
 	org.eclipse.osgi lazy
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.target
index 2c09a17..69eb639 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.target
@@ -1,26 +1,26 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.12" sequenceNumber="1590935859">
+<target name="jgit-4.12" sequenceNumber="1605866333">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="org.eclipse.jetty.client" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.client.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.continuation" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.continuation.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.http" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.http.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.io" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.io.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.security" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.security.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.server" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.server.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.servlet" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.servlet.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.util" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.util.source" version="9.4.28.v20200408"/>
-      <repository id="jetty-9.4.25" location="https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.28.v20200408/"/>
+      <unit id="org.eclipse.jetty.client" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.client.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.continuation" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.continuation.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.http" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.http.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.io" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.io.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.security" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.security.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.server" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.server.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.servlet" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.servlet.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.util" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.util.source" version="9.4.30.v20200611"/>
+      <repository id="jetty-9.4.30" location="https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.30.v20200611/"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="com.google.gson" version="2.8.2.v20180104-1110"/>
@@ -39,16 +39,16 @@
       <unit id="net.bytebuddy.byte-buddy.source" version="1.9.0.v20181107-1410"/>
       <unit id="net.i2p.crypto.eddsa" version="0.3.0.v20181102-1323"/>
       <unit id="net.i2p.crypto.eddsa.source" version="0.3.0.v20181102-1323"/>
-      <unit id="org.apache.ant" version="1.10.8.v20200515-1239"/>
-      <unit id="org.apache.ant.source" version="1.10.8.v20200515-1239"/>
-      <unit id="org.apache.commons.codec" version="1.13.0.v20200108-0001"/>
-      <unit id="org.apache.commons.codec.source" version="1.13.0.v20200108-0001"/>
+      <unit id="org.apache.ant" version="1.10.9.v20201106-1946"/>
+      <unit id="org.apache.ant.source" version="1.10.9.v20201106-1946"/>
+      <unit id="org.apache.commons.codec" version="1.14.0.v20200818-1422"/>
+      <unit id="org.apache.commons.codec.source" version="1.14.0.v20200818-1422"/>
       <unit id="org.apache.commons.compress" version="1.19.0.v20200106-2343"/>
       <unit id="org.apache.commons.compress.source" version="1.19.0.v20200106-2343"/>
       <unit id="org.apache.commons.logging" version="1.2.0.v20180409-1502"/>
       <unit id="org.apache.commons.logging.source" version="1.2.0.v20180409-1502"/>
-      <unit id="org.apache.httpcomponents.httpclient" version="4.5.10.v20200114-1512"/>
-      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.10.v20200114-1512"/>
+      <unit id="org.apache.httpcomponents.httpclient" version="4.5.10.v20200830-2311"/>
+      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.10.v20200830-2311"/>
       <unit id="org.apache.httpcomponents.httpcore" version="4.4.12.v20200108-1212"/>
       <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.12.v20200108-1212"/>
       <unit id="org.apache.log4j" version="1.2.15.v201012070815"/>
@@ -78,13 +78,13 @@
       <unit id="org.mockito.source" version="2.23.0.v20200310-1642"/>
       <unit id="org.objenesis" version="2.6.0.v20180420-1519"/>
       <unit id="org.objenesis.source" version="2.6.0.v20180420-1519"/>
-      <unit id="org.slf4j.api" version="1.7.2.v20121108-1250"/>
-      <unit id="org.slf4j.api.source" version="1.7.2.v20121108-1250"/>
-      <unit id="org.slf4j.impl.log4j12" version="1.7.2.v20131105-2200"/>
-      <unit id="org.slf4j.impl.log4j12.source" version="1.7.2.v20131105-2200"/>
+      <unit id="org.slf4j.api" version="1.7.30.v20200204-2150"/>
+      <unit id="org.slf4j.api.source" version="1.7.30.v20200204-2150"/>
+      <unit id="org.slf4j.binding.log4j12" version="1.7.30.v20201108-2042"/>
+      <unit id="org.slf4j.binding.log4j12.source" version="1.7.30.v20201108-2042"/>
       <unit id="org.tukaani.xz" version="1.8.0.v20180207-1613"/>
       <unit id="org.tukaani.xz.source" version="1.8.0.v20180207-1613"/>
-      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/R20200529191137/repository"/>
+      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/S20201118210000/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.12.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.tpd
index e668a9d..afbf79d 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.tpd
@@ -1,7 +1,7 @@
 target "jgit-4.12" with source configurePhase
 
 include "projects/jetty-9.4.x.tpd"
-include "orbit/R20200529191137-2020-06.tpd"
+include "orbit/S20201118210000.tpd"
 
 location "https://download.eclipse.org/releases/2019-06/" {
 	org.eclipse.osgi lazy
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.target
index b92beb4..fdbed2d 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.target
@@ -1,26 +1,26 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.13" sequenceNumber="1590935871">
+<target name="jgit-4.13" sequenceNumber="1605866333">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="org.eclipse.jetty.client" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.client.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.continuation" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.continuation.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.http" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.http.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.io" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.io.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.security" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.security.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.server" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.server.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.servlet" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.servlet.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.util" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.util.source" version="9.4.28.v20200408"/>
-      <repository id="jetty-9.4.25" location="https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.28.v20200408/"/>
+      <unit id="org.eclipse.jetty.client" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.client.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.continuation" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.continuation.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.http" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.http.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.io" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.io.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.security" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.security.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.server" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.server.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.servlet" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.servlet.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.util" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.util.source" version="9.4.30.v20200611"/>
+      <repository id="jetty-9.4.30" location="https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.30.v20200611/"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="com.google.gson" version="2.8.2.v20180104-1110"/>
@@ -39,16 +39,16 @@
       <unit id="net.bytebuddy.byte-buddy.source" version="1.9.0.v20181107-1410"/>
       <unit id="net.i2p.crypto.eddsa" version="0.3.0.v20181102-1323"/>
       <unit id="net.i2p.crypto.eddsa.source" version="0.3.0.v20181102-1323"/>
-      <unit id="org.apache.ant" version="1.10.8.v20200515-1239"/>
-      <unit id="org.apache.ant.source" version="1.10.8.v20200515-1239"/>
-      <unit id="org.apache.commons.codec" version="1.13.0.v20200108-0001"/>
-      <unit id="org.apache.commons.codec.source" version="1.13.0.v20200108-0001"/>
+      <unit id="org.apache.ant" version="1.10.9.v20201106-1946"/>
+      <unit id="org.apache.ant.source" version="1.10.9.v20201106-1946"/>
+      <unit id="org.apache.commons.codec" version="1.14.0.v20200818-1422"/>
+      <unit id="org.apache.commons.codec.source" version="1.14.0.v20200818-1422"/>
       <unit id="org.apache.commons.compress" version="1.19.0.v20200106-2343"/>
       <unit id="org.apache.commons.compress.source" version="1.19.0.v20200106-2343"/>
       <unit id="org.apache.commons.logging" version="1.2.0.v20180409-1502"/>
       <unit id="org.apache.commons.logging.source" version="1.2.0.v20180409-1502"/>
-      <unit id="org.apache.httpcomponents.httpclient" version="4.5.10.v20200114-1512"/>
-      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.10.v20200114-1512"/>
+      <unit id="org.apache.httpcomponents.httpclient" version="4.5.10.v20200830-2311"/>
+      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.10.v20200830-2311"/>
       <unit id="org.apache.httpcomponents.httpcore" version="4.4.12.v20200108-1212"/>
       <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.12.v20200108-1212"/>
       <unit id="org.apache.log4j" version="1.2.15.v201012070815"/>
@@ -78,13 +78,13 @@
       <unit id="org.mockito.source" version="2.23.0.v20200310-1642"/>
       <unit id="org.objenesis" version="2.6.0.v20180420-1519"/>
       <unit id="org.objenesis.source" version="2.6.0.v20180420-1519"/>
-      <unit id="org.slf4j.api" version="1.7.2.v20121108-1250"/>
-      <unit id="org.slf4j.api.source" version="1.7.2.v20121108-1250"/>
-      <unit id="org.slf4j.impl.log4j12" version="1.7.2.v20131105-2200"/>
-      <unit id="org.slf4j.impl.log4j12.source" version="1.7.2.v20131105-2200"/>
+      <unit id="org.slf4j.api" version="1.7.30.v20200204-2150"/>
+      <unit id="org.slf4j.api.source" version="1.7.30.v20200204-2150"/>
+      <unit id="org.slf4j.binding.log4j12" version="1.7.30.v20201108-2042"/>
+      <unit id="org.slf4j.binding.log4j12.source" version="1.7.30.v20201108-2042"/>
       <unit id="org.tukaani.xz" version="1.8.0.v20180207-1613"/>
       <unit id="org.tukaani.xz.source" version="1.8.0.v20180207-1613"/>
-      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/R20200529191137/repository"/>
+      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/S20201118210000/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.13.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.tpd
index 0499e3f..d83e338 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.tpd
@@ -1,7 +1,7 @@
 target "jgit-4.13" with source configurePhase
 
 include "projects/jetty-9.4.x.tpd"
-include "orbit/R20200529191137-2020-06.tpd"
+include "orbit/S20201118210000.tpd"
 
 location "https://download.eclipse.org/releases/2019-09/" {
 	org.eclipse.osgi lazy
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.target
index 6ab40f0..c2ef9d2 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.target
@@ -1,26 +1,26 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.14-staging" sequenceNumber="1590935878">
+<target name="jgit-4.14" sequenceNumber="1605866331">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="org.eclipse.jetty.client" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.client.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.continuation" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.continuation.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.http" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.http.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.io" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.io.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.security" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.security.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.server" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.server.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.servlet" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.servlet.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.util" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.util.source" version="9.4.28.v20200408"/>
-      <repository id="jetty-9.4.25" location="https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.28.v20200408/"/>
+      <unit id="org.eclipse.jetty.client" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.client.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.continuation" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.continuation.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.http" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.http.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.io" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.io.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.security" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.security.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.server" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.server.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.servlet" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.servlet.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.util" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.util.source" version="9.4.30.v20200611"/>
+      <repository id="jetty-9.4.30" location="https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.30.v20200611/"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="com.google.gson" version="2.8.2.v20180104-1110"/>
@@ -39,16 +39,16 @@
       <unit id="net.bytebuddy.byte-buddy.source" version="1.9.0.v20181107-1410"/>
       <unit id="net.i2p.crypto.eddsa" version="0.3.0.v20181102-1323"/>
       <unit id="net.i2p.crypto.eddsa.source" version="0.3.0.v20181102-1323"/>
-      <unit id="org.apache.ant" version="1.10.8.v20200515-1239"/>
-      <unit id="org.apache.ant.source" version="1.10.8.v20200515-1239"/>
-      <unit id="org.apache.commons.codec" version="1.13.0.v20200108-0001"/>
-      <unit id="org.apache.commons.codec.source" version="1.13.0.v20200108-0001"/>
+      <unit id="org.apache.ant" version="1.10.9.v20201106-1946"/>
+      <unit id="org.apache.ant.source" version="1.10.9.v20201106-1946"/>
+      <unit id="org.apache.commons.codec" version="1.14.0.v20200818-1422"/>
+      <unit id="org.apache.commons.codec.source" version="1.14.0.v20200818-1422"/>
       <unit id="org.apache.commons.compress" version="1.19.0.v20200106-2343"/>
       <unit id="org.apache.commons.compress.source" version="1.19.0.v20200106-2343"/>
       <unit id="org.apache.commons.logging" version="1.2.0.v20180409-1502"/>
       <unit id="org.apache.commons.logging.source" version="1.2.0.v20180409-1502"/>
-      <unit id="org.apache.httpcomponents.httpclient" version="4.5.10.v20200114-1512"/>
-      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.10.v20200114-1512"/>
+      <unit id="org.apache.httpcomponents.httpclient" version="4.5.10.v20200830-2311"/>
+      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.10.v20200830-2311"/>
       <unit id="org.apache.httpcomponents.httpcore" version="4.4.12.v20200108-1212"/>
       <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.12.v20200108-1212"/>
       <unit id="org.apache.log4j" version="1.2.15.v201012070815"/>
@@ -78,13 +78,13 @@
       <unit id="org.mockito.source" version="2.23.0.v20200310-1642"/>
       <unit id="org.objenesis" version="2.6.0.v20180420-1519"/>
       <unit id="org.objenesis.source" version="2.6.0.v20180420-1519"/>
-      <unit id="org.slf4j.api" version="1.7.2.v20121108-1250"/>
-      <unit id="org.slf4j.api.source" version="1.7.2.v20121108-1250"/>
-      <unit id="org.slf4j.impl.log4j12" version="1.7.2.v20131105-2200"/>
-      <unit id="org.slf4j.impl.log4j12.source" version="1.7.2.v20131105-2200"/>
+      <unit id="org.slf4j.api" version="1.7.30.v20200204-2150"/>
+      <unit id="org.slf4j.api.source" version="1.7.30.v20200204-2150"/>
+      <unit id="org.slf4j.binding.log4j12" version="1.7.30.v20201108-2042"/>
+      <unit id="org.slf4j.binding.log4j12.source" version="1.7.30.v20201108-2042"/>
       <unit id="org.tukaani.xz" version="1.8.0.v20180207-1613"/>
       <unit id="org.tukaani.xz.source" version="1.8.0.v20180207-1613"/>
-      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/R20200529191137/repository"/>
+      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/S20201118210000/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.14.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.tpd
index c2fa119..f357ccd 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.tpd
@@ -1,7 +1,7 @@
-target "jgit-4.14-staging" with source configurePhase
+target "jgit-4.14" with source configurePhase
 
 include "projects/jetty-9.4.x.tpd"
-include "orbit/R20200529191137-2020-06.tpd"
+include "orbit/S20201118210000.tpd"
 
 location "https://download.eclipse.org/releases/2019-12/201912181000/" {
 	org.eclipse.osgi lazy
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.target
index a7ca0dc..4034d2a 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.target
@@ -1,26 +1,26 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.15" sequenceNumber="1590935883">
+<target name="jgit-4.15" sequenceNumber="1605866331">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="org.eclipse.jetty.client" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.client.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.continuation" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.continuation.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.http" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.http.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.io" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.io.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.security" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.security.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.server" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.server.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.servlet" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.servlet.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.util" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.util.source" version="9.4.28.v20200408"/>
-      <repository id="jetty-9.4.25" location="https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.28.v20200408/"/>
+      <unit id="org.eclipse.jetty.client" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.client.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.continuation" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.continuation.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.http" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.http.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.io" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.io.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.security" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.security.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.server" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.server.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.servlet" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.servlet.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.util" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.util.source" version="9.4.30.v20200611"/>
+      <repository id="jetty-9.4.30" location="https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.30.v20200611/"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="com.google.gson" version="2.8.2.v20180104-1110"/>
@@ -39,16 +39,16 @@
       <unit id="net.bytebuddy.byte-buddy.source" version="1.9.0.v20181107-1410"/>
       <unit id="net.i2p.crypto.eddsa" version="0.3.0.v20181102-1323"/>
       <unit id="net.i2p.crypto.eddsa.source" version="0.3.0.v20181102-1323"/>
-      <unit id="org.apache.ant" version="1.10.8.v20200515-1239"/>
-      <unit id="org.apache.ant.source" version="1.10.8.v20200515-1239"/>
-      <unit id="org.apache.commons.codec" version="1.13.0.v20200108-0001"/>
-      <unit id="org.apache.commons.codec.source" version="1.13.0.v20200108-0001"/>
+      <unit id="org.apache.ant" version="1.10.9.v20201106-1946"/>
+      <unit id="org.apache.ant.source" version="1.10.9.v20201106-1946"/>
+      <unit id="org.apache.commons.codec" version="1.14.0.v20200818-1422"/>
+      <unit id="org.apache.commons.codec.source" version="1.14.0.v20200818-1422"/>
       <unit id="org.apache.commons.compress" version="1.19.0.v20200106-2343"/>
       <unit id="org.apache.commons.compress.source" version="1.19.0.v20200106-2343"/>
       <unit id="org.apache.commons.logging" version="1.2.0.v20180409-1502"/>
       <unit id="org.apache.commons.logging.source" version="1.2.0.v20180409-1502"/>
-      <unit id="org.apache.httpcomponents.httpclient" version="4.5.10.v20200114-1512"/>
-      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.10.v20200114-1512"/>
+      <unit id="org.apache.httpcomponents.httpclient" version="4.5.10.v20200830-2311"/>
+      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.10.v20200830-2311"/>
       <unit id="org.apache.httpcomponents.httpcore" version="4.4.12.v20200108-1212"/>
       <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.12.v20200108-1212"/>
       <unit id="org.apache.log4j" version="1.2.15.v201012070815"/>
@@ -78,13 +78,13 @@
       <unit id="org.mockito.source" version="2.23.0.v20200310-1642"/>
       <unit id="org.objenesis" version="2.6.0.v20180420-1519"/>
       <unit id="org.objenesis.source" version="2.6.0.v20180420-1519"/>
-      <unit id="org.slf4j.api" version="1.7.2.v20121108-1250"/>
-      <unit id="org.slf4j.api.source" version="1.7.2.v20121108-1250"/>
-      <unit id="org.slf4j.impl.log4j12" version="1.7.2.v20131105-2200"/>
-      <unit id="org.slf4j.impl.log4j12.source" version="1.7.2.v20131105-2200"/>
+      <unit id="org.slf4j.api" version="1.7.30.v20200204-2150"/>
+      <unit id="org.slf4j.api.source" version="1.7.30.v20200204-2150"/>
+      <unit id="org.slf4j.binding.log4j12" version="1.7.30.v20201108-2042"/>
+      <unit id="org.slf4j.binding.log4j12.source" version="1.7.30.v20201108-2042"/>
       <unit id="org.tukaani.xz" version="1.8.0.v20180207-1613"/>
       <unit id="org.tukaani.xz.source" version="1.8.0.v20180207-1613"/>
-      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/R20200529191137/repository"/>
+      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/S20201118210000/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.15.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.tpd
index ff55a7d..881fe37 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.tpd
@@ -1,7 +1,7 @@
 target "jgit-4.15" with source configurePhase
 
 include "projects/jetty-9.4.x.tpd"
-include "orbit/R20200529191137-2020-06.tpd"
+include "orbit/S20201118210000.tpd"
 
 location "https://download.eclipse.org/releases/2020-03/202003181000/" {
 	org.eclipse.osgi lazy
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16-staging.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16-staging.tpd
deleted file mode 100644
index 777aaf3..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16-staging.tpd
+++ /dev/null
@@ -1,8 +0,0 @@
-target "jgit-4.15" with source configurePhase
-
-include "projects/jetty-9.4.x.tpd"
-include "orbit/R20200529191137-2020-06.tpd"
-
-location "https://download.eclipse.org/staging/2020-06/" {
-	org.eclipse.osgi lazy
-}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16-staging.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.target
similarity index 70%
copy from org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16-staging.target
copy to org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.target
index 0aff796..34d872c 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16-staging.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.target
@@ -1,26 +1,26 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.15" sequenceNumber="1590935890">
+<target name="jgit-4.16" sequenceNumber="1605866333">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="org.eclipse.jetty.client" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.client.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.continuation" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.continuation.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.http" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.http.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.io" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.io.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.security" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.security.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.server" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.server.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.servlet" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.servlet.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.util" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.util.source" version="9.4.28.v20200408"/>
-      <repository id="jetty-9.4.25" location="https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.28.v20200408/"/>
+      <unit id="org.eclipse.jetty.client" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.client.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.continuation" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.continuation.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.http" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.http.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.io" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.io.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.security" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.security.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.server" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.server.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.servlet" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.servlet.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.util" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.util.source" version="9.4.30.v20200611"/>
+      <repository id="jetty-9.4.30" location="https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.30.v20200611/"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="com.google.gson" version="2.8.2.v20180104-1110"/>
@@ -39,16 +39,16 @@
       <unit id="net.bytebuddy.byte-buddy.source" version="1.9.0.v20181107-1410"/>
       <unit id="net.i2p.crypto.eddsa" version="0.3.0.v20181102-1323"/>
       <unit id="net.i2p.crypto.eddsa.source" version="0.3.0.v20181102-1323"/>
-      <unit id="org.apache.ant" version="1.10.8.v20200515-1239"/>
-      <unit id="org.apache.ant.source" version="1.10.8.v20200515-1239"/>
-      <unit id="org.apache.commons.codec" version="1.13.0.v20200108-0001"/>
-      <unit id="org.apache.commons.codec.source" version="1.13.0.v20200108-0001"/>
+      <unit id="org.apache.ant" version="1.10.9.v20201106-1946"/>
+      <unit id="org.apache.ant.source" version="1.10.9.v20201106-1946"/>
+      <unit id="org.apache.commons.codec" version="1.14.0.v20200818-1422"/>
+      <unit id="org.apache.commons.codec.source" version="1.14.0.v20200818-1422"/>
       <unit id="org.apache.commons.compress" version="1.19.0.v20200106-2343"/>
       <unit id="org.apache.commons.compress.source" version="1.19.0.v20200106-2343"/>
       <unit id="org.apache.commons.logging" version="1.2.0.v20180409-1502"/>
       <unit id="org.apache.commons.logging.source" version="1.2.0.v20180409-1502"/>
-      <unit id="org.apache.httpcomponents.httpclient" version="4.5.10.v20200114-1512"/>
-      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.10.v20200114-1512"/>
+      <unit id="org.apache.httpcomponents.httpclient" version="4.5.10.v20200830-2311"/>
+      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.10.v20200830-2311"/>
       <unit id="org.apache.httpcomponents.httpcore" version="4.4.12.v20200108-1212"/>
       <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.12.v20200108-1212"/>
       <unit id="org.apache.log4j" version="1.2.15.v201012070815"/>
@@ -78,17 +78,17 @@
       <unit id="org.mockito.source" version="2.23.0.v20200310-1642"/>
       <unit id="org.objenesis" version="2.6.0.v20180420-1519"/>
       <unit id="org.objenesis.source" version="2.6.0.v20180420-1519"/>
-      <unit id="org.slf4j.api" version="1.7.2.v20121108-1250"/>
-      <unit id="org.slf4j.api.source" version="1.7.2.v20121108-1250"/>
-      <unit id="org.slf4j.impl.log4j12" version="1.7.2.v20131105-2200"/>
-      <unit id="org.slf4j.impl.log4j12.source" version="1.7.2.v20131105-2200"/>
+      <unit id="org.slf4j.api" version="1.7.30.v20200204-2150"/>
+      <unit id="org.slf4j.api.source" version="1.7.30.v20200204-2150"/>
+      <unit id="org.slf4j.binding.log4j12" version="1.7.30.v20201108-2042"/>
+      <unit id="org.slf4j.binding.log4j12.source" version="1.7.30.v20201108-2042"/>
       <unit id="org.tukaani.xz" version="1.8.0.v20180207-1613"/>
       <unit id="org.tukaani.xz.source" version="1.8.0.v20180207-1613"/>
-      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/R20200529191137/repository"/>
+      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/S20201118210000/repository"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.osgi" version="0.0.0"/>
-      <repository location="https://download.eclipse.org/staging/2020-06/"/>
+      <repository location="https://download.eclipse.org/releases/2020-06/"/>
     </location>
   </locations>
 </target>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.tpd
new file mode 100644
index 0000000..9a07597
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.tpd
@@ -0,0 +1,8 @@
+target "jgit-4.16" with source configurePhase
+
+include "projects/jetty-9.4.x.tpd"
+include "orbit/S20201118210000.tpd"
+
+location "https://download.eclipse.org/releases/2020-06/" {
+	org.eclipse.osgi lazy
+}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16-staging.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target
similarity index 70%
copy from org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16-staging.target
copy to org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target
index 0aff796..3384c23 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16-staging.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target
@@ -1,26 +1,26 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.15" sequenceNumber="1590935890">
+<target name="jgit-4.17" sequenceNumber="1605866541">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="org.eclipse.jetty.client" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.client.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.continuation" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.continuation.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.http" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.http.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.io" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.io.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.security" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.security.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.server" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.server.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.servlet" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.servlet.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.util" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.util.source" version="9.4.28.v20200408"/>
-      <repository id="jetty-9.4.25" location="https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.28.v20200408/"/>
+      <unit id="org.eclipse.jetty.client" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.client.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.continuation" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.continuation.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.http" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.http.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.io" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.io.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.security" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.security.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.server" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.server.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.servlet" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.servlet.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.util" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.util.source" version="9.4.30.v20200611"/>
+      <repository id="jetty-9.4.30" location="https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.30.v20200611/"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="com.google.gson" version="2.8.2.v20180104-1110"/>
@@ -39,16 +39,16 @@
       <unit id="net.bytebuddy.byte-buddy.source" version="1.9.0.v20181107-1410"/>
       <unit id="net.i2p.crypto.eddsa" version="0.3.0.v20181102-1323"/>
       <unit id="net.i2p.crypto.eddsa.source" version="0.3.0.v20181102-1323"/>
-      <unit id="org.apache.ant" version="1.10.8.v20200515-1239"/>
-      <unit id="org.apache.ant.source" version="1.10.8.v20200515-1239"/>
-      <unit id="org.apache.commons.codec" version="1.13.0.v20200108-0001"/>
-      <unit id="org.apache.commons.codec.source" version="1.13.0.v20200108-0001"/>
+      <unit id="org.apache.ant" version="1.10.9.v20201106-1946"/>
+      <unit id="org.apache.ant.source" version="1.10.9.v20201106-1946"/>
+      <unit id="org.apache.commons.codec" version="1.14.0.v20200818-1422"/>
+      <unit id="org.apache.commons.codec.source" version="1.14.0.v20200818-1422"/>
       <unit id="org.apache.commons.compress" version="1.19.0.v20200106-2343"/>
       <unit id="org.apache.commons.compress.source" version="1.19.0.v20200106-2343"/>
       <unit id="org.apache.commons.logging" version="1.2.0.v20180409-1502"/>
       <unit id="org.apache.commons.logging.source" version="1.2.0.v20180409-1502"/>
-      <unit id="org.apache.httpcomponents.httpclient" version="4.5.10.v20200114-1512"/>
-      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.10.v20200114-1512"/>
+      <unit id="org.apache.httpcomponents.httpclient" version="4.5.10.v20200830-2311"/>
+      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.10.v20200830-2311"/>
       <unit id="org.apache.httpcomponents.httpcore" version="4.4.12.v20200108-1212"/>
       <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.12.v20200108-1212"/>
       <unit id="org.apache.log4j" version="1.2.15.v201012070815"/>
@@ -78,17 +78,17 @@
       <unit id="org.mockito.source" version="2.23.0.v20200310-1642"/>
       <unit id="org.objenesis" version="2.6.0.v20180420-1519"/>
       <unit id="org.objenesis.source" version="2.6.0.v20180420-1519"/>
-      <unit id="org.slf4j.api" version="1.7.2.v20121108-1250"/>
-      <unit id="org.slf4j.api.source" version="1.7.2.v20121108-1250"/>
-      <unit id="org.slf4j.impl.log4j12" version="1.7.2.v20131105-2200"/>
-      <unit id="org.slf4j.impl.log4j12.source" version="1.7.2.v20131105-2200"/>
+      <unit id="org.slf4j.api" version="1.7.30.v20200204-2150"/>
+      <unit id="org.slf4j.api.source" version="1.7.30.v20200204-2150"/>
+      <unit id="org.slf4j.binding.log4j12" version="1.7.30.v20201108-2042"/>
+      <unit id="org.slf4j.binding.log4j12.source" version="1.7.30.v20201108-2042"/>
       <unit id="org.tukaani.xz" version="1.8.0.v20180207-1613"/>
       <unit id="org.tukaani.xz.source" version="1.8.0.v20180207-1613"/>
-      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/R20200529191137/repository"/>
+      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/S20201118210000/repository"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.osgi" version="0.0.0"/>
-      <repository location="https://download.eclipse.org/staging/2020-06/"/>
+      <repository location="https://download.eclipse.org/releases/2020-09/"/>
     </location>
   </locations>
 </target>
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
new file mode 100644
index 0000000..ce79cf4
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.tpd
@@ -0,0 +1,8 @@
+target "jgit-4.17" with source configurePhase
+
+include "projects/jetty-9.4.x.tpd"
+include "orbit/S20201118210000.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.16-staging.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18-staging.target
similarity index 71%
rename from org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16-staging.target
rename to org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18-staging.target
index 0aff796..5eeab95 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16-staging.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18-staging.target
@@ -1,26 +1,26 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.15" sequenceNumber="1590935890">
+<target name="jgit-4.18-staging" sequenceNumber="1605866541">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="org.eclipse.jetty.client" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.client.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.continuation" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.continuation.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.http" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.http.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.io" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.io.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.security" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.security.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.server" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.server.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.servlet" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.servlet.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.util" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.util.source" version="9.4.28.v20200408"/>
-      <repository id="jetty-9.4.25" location="https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.28.v20200408/"/>
+      <unit id="org.eclipse.jetty.client" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.client.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.continuation" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.continuation.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.http" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.http.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.io" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.io.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.security" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.security.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.server" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.server.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.servlet" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.servlet.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.util" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.util.source" version="9.4.30.v20200611"/>
+      <repository id="jetty-9.4.30" location="https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.30.v20200611/"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="com.google.gson" version="2.8.2.v20180104-1110"/>
@@ -39,16 +39,16 @@
       <unit id="net.bytebuddy.byte-buddy.source" version="1.9.0.v20181107-1410"/>
       <unit id="net.i2p.crypto.eddsa" version="0.3.0.v20181102-1323"/>
       <unit id="net.i2p.crypto.eddsa.source" version="0.3.0.v20181102-1323"/>
-      <unit id="org.apache.ant" version="1.10.8.v20200515-1239"/>
-      <unit id="org.apache.ant.source" version="1.10.8.v20200515-1239"/>
-      <unit id="org.apache.commons.codec" version="1.13.0.v20200108-0001"/>
-      <unit id="org.apache.commons.codec.source" version="1.13.0.v20200108-0001"/>
+      <unit id="org.apache.ant" version="1.10.9.v20201106-1946"/>
+      <unit id="org.apache.ant.source" version="1.10.9.v20201106-1946"/>
+      <unit id="org.apache.commons.codec" version="1.14.0.v20200818-1422"/>
+      <unit id="org.apache.commons.codec.source" version="1.14.0.v20200818-1422"/>
       <unit id="org.apache.commons.compress" version="1.19.0.v20200106-2343"/>
       <unit id="org.apache.commons.compress.source" version="1.19.0.v20200106-2343"/>
       <unit id="org.apache.commons.logging" version="1.2.0.v20180409-1502"/>
       <unit id="org.apache.commons.logging.source" version="1.2.0.v20180409-1502"/>
-      <unit id="org.apache.httpcomponents.httpclient" version="4.5.10.v20200114-1512"/>
-      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.10.v20200114-1512"/>
+      <unit id="org.apache.httpcomponents.httpclient" version="4.5.10.v20200830-2311"/>
+      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.10.v20200830-2311"/>
       <unit id="org.apache.httpcomponents.httpcore" version="4.4.12.v20200108-1212"/>
       <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.12.v20200108-1212"/>
       <unit id="org.apache.log4j" version="1.2.15.v201012070815"/>
@@ -78,17 +78,17 @@
       <unit id="org.mockito.source" version="2.23.0.v20200310-1642"/>
       <unit id="org.objenesis" version="2.6.0.v20180420-1519"/>
       <unit id="org.objenesis.source" version="2.6.0.v20180420-1519"/>
-      <unit id="org.slf4j.api" version="1.7.2.v20121108-1250"/>
-      <unit id="org.slf4j.api.source" version="1.7.2.v20121108-1250"/>
-      <unit id="org.slf4j.impl.log4j12" version="1.7.2.v20131105-2200"/>
-      <unit id="org.slf4j.impl.log4j12.source" version="1.7.2.v20131105-2200"/>
+      <unit id="org.slf4j.api" version="1.7.30.v20200204-2150"/>
+      <unit id="org.slf4j.api.source" version="1.7.30.v20200204-2150"/>
+      <unit id="org.slf4j.binding.log4j12" version="1.7.30.v20201108-2042"/>
+      <unit id="org.slf4j.binding.log4j12.source" version="1.7.30.v20201108-2042"/>
       <unit id="org.tukaani.xz" version="1.8.0.v20180207-1613"/>
       <unit id="org.tukaani.xz.source" version="1.8.0.v20180207-1613"/>
-      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/R20200529191137/repository"/>
+      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/S20201118210000/repository"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.osgi" version="0.0.0"/>
-      <repository location="https://download.eclipse.org/staging/2020-06/"/>
+      <repository location="https://download.eclipse.org/staging/2020-12/"/>
     </location>
   </locations>
 </target>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18-staging.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18-staging.tpd
new file mode 100644
index 0000000..0669490
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18-staging.tpd
@@ -0,0 +1,8 @@
+target "jgit-4.18-staging" with source configurePhase
+
+include "projects/jetty-9.4.x.tpd"
+include "orbit/S20201118210000.tpd"
+
+location "https://download.eclipse.org/staging/2020-12/" {
+	org.eclipse.osgi lazy
+}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target
index b07ddd0..a66fcc0 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target
@@ -1,26 +1,26 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.6" sequenceNumber="1590935806">
+<target name="jgit-4.6" sequenceNumber="1605866347">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="org.eclipse.jetty.client" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.client.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.continuation" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.continuation.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.http" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.http.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.io" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.io.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.security" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.security.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.server" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.server.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.servlet" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.servlet.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.util" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.util.source" version="9.4.28.v20200408"/>
-      <repository id="jetty-9.4.25" location="https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.28.v20200408/"/>
+      <unit id="org.eclipse.jetty.client" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.client.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.continuation" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.continuation.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.http" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.http.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.io" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.io.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.security" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.security.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.server" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.server.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.servlet" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.servlet.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.util" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.util.source" version="9.4.30.v20200611"/>
+      <repository id="jetty-9.4.30" location="https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.30.v20200611/"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="com.google.gson" version="2.8.2.v20180104-1110"/>
@@ -39,16 +39,16 @@
       <unit id="net.bytebuddy.byte-buddy.source" version="1.9.0.v20181107-1410"/>
       <unit id="net.i2p.crypto.eddsa" version="0.3.0.v20181102-1323"/>
       <unit id="net.i2p.crypto.eddsa.source" version="0.3.0.v20181102-1323"/>
-      <unit id="org.apache.ant" version="1.10.8.v20200515-1239"/>
-      <unit id="org.apache.ant.source" version="1.10.8.v20200515-1239"/>
-      <unit id="org.apache.commons.codec" version="1.13.0.v20200108-0001"/>
-      <unit id="org.apache.commons.codec.source" version="1.13.0.v20200108-0001"/>
+      <unit id="org.apache.ant" version="1.10.9.v20201106-1946"/>
+      <unit id="org.apache.ant.source" version="1.10.9.v20201106-1946"/>
+      <unit id="org.apache.commons.codec" version="1.14.0.v20200818-1422"/>
+      <unit id="org.apache.commons.codec.source" version="1.14.0.v20200818-1422"/>
       <unit id="org.apache.commons.compress" version="1.19.0.v20200106-2343"/>
       <unit id="org.apache.commons.compress.source" version="1.19.0.v20200106-2343"/>
       <unit id="org.apache.commons.logging" version="1.2.0.v20180409-1502"/>
       <unit id="org.apache.commons.logging.source" version="1.2.0.v20180409-1502"/>
-      <unit id="org.apache.httpcomponents.httpclient" version="4.5.10.v20200114-1512"/>
-      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.10.v20200114-1512"/>
+      <unit id="org.apache.httpcomponents.httpclient" version="4.5.10.v20200830-2311"/>
+      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.10.v20200830-2311"/>
       <unit id="org.apache.httpcomponents.httpcore" version="4.4.12.v20200108-1212"/>
       <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.12.v20200108-1212"/>
       <unit id="org.apache.log4j" version="1.2.15.v201012070815"/>
@@ -78,13 +78,13 @@
       <unit id="org.mockito.source" version="2.23.0.v20200310-1642"/>
       <unit id="org.objenesis" version="2.6.0.v20180420-1519"/>
       <unit id="org.objenesis.source" version="2.6.0.v20180420-1519"/>
-      <unit id="org.slf4j.api" version="1.7.2.v20121108-1250"/>
-      <unit id="org.slf4j.api.source" version="1.7.2.v20121108-1250"/>
-      <unit id="org.slf4j.impl.log4j12" version="1.7.2.v20131105-2200"/>
-      <unit id="org.slf4j.impl.log4j12.source" version="1.7.2.v20131105-2200"/>
+      <unit id="org.slf4j.api" version="1.7.30.v20200204-2150"/>
+      <unit id="org.slf4j.api.source" version="1.7.30.v20200204-2150"/>
+      <unit id="org.slf4j.binding.log4j12" version="1.7.30.v20201108-2042"/>
+      <unit id="org.slf4j.binding.log4j12.source" version="1.7.30.v20201108-2042"/>
       <unit id="org.tukaani.xz" version="1.8.0.v20180207-1613"/>
       <unit id="org.tukaani.xz.source" version="1.8.0.v20180207-1613"/>
-      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/R20200529191137/repository"/>
+      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/S20201118210000/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.6.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.tpd
index d0c3c1b..aa58b68 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.tpd
@@ -1,7 +1,7 @@
 target "jgit-4.6" with source configurePhase
 
 include "projects/jetty-9.4.x.tpd"
-include "orbit/R20200529191137-2020-06.tpd"
+include "orbit/S20201118210000.tpd"
 
 location "https://download.eclipse.org/releases/neon/" {
 	org.eclipse.osgi lazy
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target
index 351b0fc..4b5410a 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target
@@ -1,26 +1,26 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.7" sequenceNumber="1590935820">
+<target name="jgit-4.7" sequenceNumber="1605866338">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="org.eclipse.jetty.client" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.client.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.continuation" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.continuation.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.http" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.http.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.io" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.io.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.security" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.security.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.server" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.server.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.servlet" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.servlet.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.util" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.util.source" version="9.4.28.v20200408"/>
-      <repository id="jetty-9.4.25" location="https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.28.v20200408/"/>
+      <unit id="org.eclipse.jetty.client" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.client.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.continuation" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.continuation.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.http" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.http.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.io" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.io.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.security" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.security.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.server" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.server.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.servlet" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.servlet.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.util" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.util.source" version="9.4.30.v20200611"/>
+      <repository id="jetty-9.4.30" location="https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.30.v20200611/"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="com.google.gson" version="2.8.2.v20180104-1110"/>
@@ -39,16 +39,16 @@
       <unit id="net.bytebuddy.byte-buddy.source" version="1.9.0.v20181107-1410"/>
       <unit id="net.i2p.crypto.eddsa" version="0.3.0.v20181102-1323"/>
       <unit id="net.i2p.crypto.eddsa.source" version="0.3.0.v20181102-1323"/>
-      <unit id="org.apache.ant" version="1.10.8.v20200515-1239"/>
-      <unit id="org.apache.ant.source" version="1.10.8.v20200515-1239"/>
-      <unit id="org.apache.commons.codec" version="1.13.0.v20200108-0001"/>
-      <unit id="org.apache.commons.codec.source" version="1.13.0.v20200108-0001"/>
+      <unit id="org.apache.ant" version="1.10.9.v20201106-1946"/>
+      <unit id="org.apache.ant.source" version="1.10.9.v20201106-1946"/>
+      <unit id="org.apache.commons.codec" version="1.14.0.v20200818-1422"/>
+      <unit id="org.apache.commons.codec.source" version="1.14.0.v20200818-1422"/>
       <unit id="org.apache.commons.compress" version="1.19.0.v20200106-2343"/>
       <unit id="org.apache.commons.compress.source" version="1.19.0.v20200106-2343"/>
       <unit id="org.apache.commons.logging" version="1.2.0.v20180409-1502"/>
       <unit id="org.apache.commons.logging.source" version="1.2.0.v20180409-1502"/>
-      <unit id="org.apache.httpcomponents.httpclient" version="4.5.10.v20200114-1512"/>
-      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.10.v20200114-1512"/>
+      <unit id="org.apache.httpcomponents.httpclient" version="4.5.10.v20200830-2311"/>
+      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.10.v20200830-2311"/>
       <unit id="org.apache.httpcomponents.httpcore" version="4.4.12.v20200108-1212"/>
       <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.12.v20200108-1212"/>
       <unit id="org.apache.log4j" version="1.2.15.v201012070815"/>
@@ -78,13 +78,13 @@
       <unit id="org.mockito.source" version="2.23.0.v20200310-1642"/>
       <unit id="org.objenesis" version="2.6.0.v20180420-1519"/>
       <unit id="org.objenesis.source" version="2.6.0.v20180420-1519"/>
-      <unit id="org.slf4j.api" version="1.7.2.v20121108-1250"/>
-      <unit id="org.slf4j.api.source" version="1.7.2.v20121108-1250"/>
-      <unit id="org.slf4j.impl.log4j12" version="1.7.2.v20131105-2200"/>
-      <unit id="org.slf4j.impl.log4j12.source" version="1.7.2.v20131105-2200"/>
+      <unit id="org.slf4j.api" version="1.7.30.v20200204-2150"/>
+      <unit id="org.slf4j.api.source" version="1.7.30.v20200204-2150"/>
+      <unit id="org.slf4j.binding.log4j12" version="1.7.30.v20201108-2042"/>
+      <unit id="org.slf4j.binding.log4j12.source" version="1.7.30.v20201108-2042"/>
       <unit id="org.tukaani.xz" version="1.8.0.v20180207-1613"/>
       <unit id="org.tukaani.xz.source" version="1.8.0.v20180207-1613"/>
-      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/R20200529191137/repository"/>
+      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/S20201118210000/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.7.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.tpd
index 19790ff..e2264e0 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.tpd
@@ -1,7 +1,7 @@
 target "jgit-4.7" with source configurePhase
 
 include "projects/jetty-9.4.x.tpd"
-include "orbit/R20200529191137-2020-06.tpd"
+include "orbit/S20201118210000.tpd"
 
 location "https://download.eclipse.org/releases/oxygen/" {
 	org.eclipse.osgi lazy
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target
index be6108d..d776427 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target
@@ -1,26 +1,26 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.8" sequenceNumber="1590935828">
+<target name="jgit-4.8" sequenceNumber="1605866333">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="org.eclipse.jetty.client" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.client.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.continuation" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.continuation.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.http" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.http.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.io" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.io.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.security" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.security.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.server" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.server.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.servlet" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.servlet.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.util" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.util.source" version="9.4.28.v20200408"/>
-      <repository id="jetty-9.4.25" location="https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.28.v20200408/"/>
+      <unit id="org.eclipse.jetty.client" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.client.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.continuation" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.continuation.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.http" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.http.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.io" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.io.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.security" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.security.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.server" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.server.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.servlet" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.servlet.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.util" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.util.source" version="9.4.30.v20200611"/>
+      <repository id="jetty-9.4.30" location="https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.30.v20200611/"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="com.google.gson" version="2.8.2.v20180104-1110"/>
@@ -39,16 +39,16 @@
       <unit id="net.bytebuddy.byte-buddy.source" version="1.9.0.v20181107-1410"/>
       <unit id="net.i2p.crypto.eddsa" version="0.3.0.v20181102-1323"/>
       <unit id="net.i2p.crypto.eddsa.source" version="0.3.0.v20181102-1323"/>
-      <unit id="org.apache.ant" version="1.10.8.v20200515-1239"/>
-      <unit id="org.apache.ant.source" version="1.10.8.v20200515-1239"/>
-      <unit id="org.apache.commons.codec" version="1.13.0.v20200108-0001"/>
-      <unit id="org.apache.commons.codec.source" version="1.13.0.v20200108-0001"/>
+      <unit id="org.apache.ant" version="1.10.9.v20201106-1946"/>
+      <unit id="org.apache.ant.source" version="1.10.9.v20201106-1946"/>
+      <unit id="org.apache.commons.codec" version="1.14.0.v20200818-1422"/>
+      <unit id="org.apache.commons.codec.source" version="1.14.0.v20200818-1422"/>
       <unit id="org.apache.commons.compress" version="1.19.0.v20200106-2343"/>
       <unit id="org.apache.commons.compress.source" version="1.19.0.v20200106-2343"/>
       <unit id="org.apache.commons.logging" version="1.2.0.v20180409-1502"/>
       <unit id="org.apache.commons.logging.source" version="1.2.0.v20180409-1502"/>
-      <unit id="org.apache.httpcomponents.httpclient" version="4.5.10.v20200114-1512"/>
-      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.10.v20200114-1512"/>
+      <unit id="org.apache.httpcomponents.httpclient" version="4.5.10.v20200830-2311"/>
+      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.10.v20200830-2311"/>
       <unit id="org.apache.httpcomponents.httpcore" version="4.4.12.v20200108-1212"/>
       <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.12.v20200108-1212"/>
       <unit id="org.apache.log4j" version="1.2.15.v201012070815"/>
@@ -78,13 +78,13 @@
       <unit id="org.mockito.source" version="2.23.0.v20200310-1642"/>
       <unit id="org.objenesis" version="2.6.0.v20180420-1519"/>
       <unit id="org.objenesis.source" version="2.6.0.v20180420-1519"/>
-      <unit id="org.slf4j.api" version="1.7.2.v20121108-1250"/>
-      <unit id="org.slf4j.api.source" version="1.7.2.v20121108-1250"/>
-      <unit id="org.slf4j.impl.log4j12" version="1.7.2.v20131105-2200"/>
-      <unit id="org.slf4j.impl.log4j12.source" version="1.7.2.v20131105-2200"/>
+      <unit id="org.slf4j.api" version="1.7.30.v20200204-2150"/>
+      <unit id="org.slf4j.api.source" version="1.7.30.v20200204-2150"/>
+      <unit id="org.slf4j.binding.log4j12" version="1.7.30.v20201108-2042"/>
+      <unit id="org.slf4j.binding.log4j12.source" version="1.7.30.v20201108-2042"/>
       <unit id="org.tukaani.xz" version="1.8.0.v20180207-1613"/>
       <unit id="org.tukaani.xz.source" version="1.8.0.v20180207-1613"/>
-      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/R20200529191137/repository"/>
+      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/S20201118210000/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.8.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.tpd
index 5fe27fc..c92ce53 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.tpd
@@ -1,7 +1,7 @@
 target "jgit-4.8" with source configurePhase
 
 include "projects/jetty-9.4.x.tpd"
-include "orbit/R20200529191137-2020-06.tpd"
+include "orbit/S20201118210000.tpd"
 
 location "https://download.eclipse.org/releases/photon/" {
 	org.eclipse.osgi lazy
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target
index b9ad2cb..56002b7 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target
@@ -1,26 +1,26 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.9" sequenceNumber="1590935836">
+<target name="jgit-4.9" sequenceNumber="1605866333">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="org.eclipse.jetty.client" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.client.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.continuation" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.continuation.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.http" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.http.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.io" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.io.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.security" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.security.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.server" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.server.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.servlet" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.servlet.source" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.util" version="9.4.28.v20200408"/>
-      <unit id="org.eclipse.jetty.util.source" version="9.4.28.v20200408"/>
-      <repository id="jetty-9.4.25" location="https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.28.v20200408/"/>
+      <unit id="org.eclipse.jetty.client" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.client.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.continuation" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.continuation.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.http" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.http.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.io" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.io.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.security" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.security.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.server" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.server.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.servlet" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.servlet.source" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.util" version="9.4.30.v20200611"/>
+      <unit id="org.eclipse.jetty.util.source" version="9.4.30.v20200611"/>
+      <repository id="jetty-9.4.30" location="https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.30.v20200611/"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="com.google.gson" version="2.8.2.v20180104-1110"/>
@@ -39,16 +39,16 @@
       <unit id="net.bytebuddy.byte-buddy.source" version="1.9.0.v20181107-1410"/>
       <unit id="net.i2p.crypto.eddsa" version="0.3.0.v20181102-1323"/>
       <unit id="net.i2p.crypto.eddsa.source" version="0.3.0.v20181102-1323"/>
-      <unit id="org.apache.ant" version="1.10.8.v20200515-1239"/>
-      <unit id="org.apache.ant.source" version="1.10.8.v20200515-1239"/>
-      <unit id="org.apache.commons.codec" version="1.13.0.v20200108-0001"/>
-      <unit id="org.apache.commons.codec.source" version="1.13.0.v20200108-0001"/>
+      <unit id="org.apache.ant" version="1.10.9.v20201106-1946"/>
+      <unit id="org.apache.ant.source" version="1.10.9.v20201106-1946"/>
+      <unit id="org.apache.commons.codec" version="1.14.0.v20200818-1422"/>
+      <unit id="org.apache.commons.codec.source" version="1.14.0.v20200818-1422"/>
       <unit id="org.apache.commons.compress" version="1.19.0.v20200106-2343"/>
       <unit id="org.apache.commons.compress.source" version="1.19.0.v20200106-2343"/>
       <unit id="org.apache.commons.logging" version="1.2.0.v20180409-1502"/>
       <unit id="org.apache.commons.logging.source" version="1.2.0.v20180409-1502"/>
-      <unit id="org.apache.httpcomponents.httpclient" version="4.5.10.v20200114-1512"/>
-      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.10.v20200114-1512"/>
+      <unit id="org.apache.httpcomponents.httpclient" version="4.5.10.v20200830-2311"/>
+      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.10.v20200830-2311"/>
       <unit id="org.apache.httpcomponents.httpcore" version="4.4.12.v20200108-1212"/>
       <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.12.v20200108-1212"/>
       <unit id="org.apache.log4j" version="1.2.15.v201012070815"/>
@@ -78,13 +78,13 @@
       <unit id="org.mockito.source" version="2.23.0.v20200310-1642"/>
       <unit id="org.objenesis" version="2.6.0.v20180420-1519"/>
       <unit id="org.objenesis.source" version="2.6.0.v20180420-1519"/>
-      <unit id="org.slf4j.api" version="1.7.2.v20121108-1250"/>
-      <unit id="org.slf4j.api.source" version="1.7.2.v20121108-1250"/>
-      <unit id="org.slf4j.impl.log4j12" version="1.7.2.v20131105-2200"/>
-      <unit id="org.slf4j.impl.log4j12.source" version="1.7.2.v20131105-2200"/>
+      <unit id="org.slf4j.api" version="1.7.30.v20200204-2150"/>
+      <unit id="org.slf4j.api.source" version="1.7.30.v20200204-2150"/>
+      <unit id="org.slf4j.binding.log4j12" version="1.7.30.v20201108-2042"/>
+      <unit id="org.slf4j.binding.log4j12.source" version="1.7.30.v20201108-2042"/>
       <unit id="org.tukaani.xz" version="1.8.0.v20180207-1613"/>
       <unit id="org.tukaani.xz.source" version="1.8.0.v20180207-1613"/>
-      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/R20200529191137/repository"/>
+      <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/S20201118210000/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.9.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.tpd
index 4e39322..06ccecb 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.tpd
@@ -1,7 +1,7 @@
 target "jgit-4.9" with source configurePhase
 
 include "projects/jetty-9.4.x.tpd"
-include "orbit/R20200529191137-2020-06.tpd"
+include "orbit/S20201118210000.tpd"
 
 location "https://download.eclipse.org/releases/2018-09/" {
 	org.eclipse.osgi lazy
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/S20200519202422.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20200831200620-2020-09.tpd
similarity index 80%
rename from org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/S20200519202422.tpd
rename to org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20200831200620-2020-09.tpd
index abe97e1..22e2b01 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/S20200519202422.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20200831200620-2020-09.tpd
@@ -1,7 +1,7 @@
-target "S20200519202422" with source configurePhase
+target "R20200831200620-2020-09" with source configurePhase
 // see https://download.eclipse.org/tools/orbit/downloads/
 
-location "https://download.eclipse.org/tools/orbit/downloads/drops/S20200519202422/repository" {
+location "https://download.eclipse.org/tools/orbit/downloads/drops/R20200831200620/repository" {
 	com.google.gson [2.8.2.v20180104-1110,2.8.2.v20180104-1110]
 	com.google.gson.source [2.8.2.v20180104-1110,2.8.2.v20180104-1110]
 	com.jcraft.jsch [0.1.55.v20190404-1902,0.1.55.v20190404-1902]
@@ -20,14 +20,14 @@
 	net.i2p.crypto.eddsa.source [0.3.0.v20181102-1323,0.3.0.v20181102-1323]
 	org.apache.ant [1.10.8.v20200515-1239,1.10.8.v20200515-1239]
 	org.apache.ant.source [1.10.8.v20200515-1239,1.10.8.v20200515-1239]
-	org.apache.commons.codec [1.13.0.v20200108-0001,1.13.0.v20200108-0001]
-	org.apache.commons.codec.source [1.13.0.v20200108-0001,1.13.0.v20200108-0001]
+	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.19.0.v20200106-2343,1.19.0.v20200106-2343]
 	org.apache.commons.compress.source [1.19.0.v20200106-2343,1.19.0.v20200106-2343]
 	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.10.v20200114-1512,4.5.10.v20200114-1512]
-	org.apache.httpcomponents.httpclient.source [4.5.10.v20200114-1512,4.5.10.v20200114-1512]
+	org.apache.httpcomponents.httpclient [4.5.10.v20200830-2311,4.5.10.v20200830-2311]
+	org.apache.httpcomponents.httpclient.source [4.5.10.v20200830-2311,4.5.10.v20200830-2311]
 	org.apache.httpcomponents.httpcore [4.4.12.v20200108-1212,4.4.12.v20200108-1212]
 	org.apache.httpcomponents.httpcore.source [4.4.12.v20200108-1212,4.4.12.v20200108-1212]
 	org.apache.log4j [1.2.15.v201012070815,1.2.15.v201012070815]
@@ -38,12 +38,12 @@
 	org.apache.sshd.sftp.source [2.4.0.v20200319-1547,2.4.0.v20200319-1547]
 	org.assertj [3.14.0.v20200120-1926,3.14.0.v20200120-1926]
 	org.assertj.source [3.14.0.v20200120-1926,3.14.0.v20200120-1926]
-	org.bouncycastle.bcpg [1.65.0.v20200502-2229,1.65.0.v20200502-2229]
-	org.bouncycastle.bcpg.source [1.65.0.v20200502-2229,1.65.0.v20200502-2229]
-	org.bouncycastle.bcpkix [1.65.0.v20200502-2229,1.65.0.v20200502-2229]
-	org.bouncycastle.bcpkix.source [1.65.0.v20200502-2229,1.65.0.v20200502-2229]
-	org.bouncycastle.bcprov [1.65.0.v20200502-2229,1.65.0.v20200502-2229]
-	org.bouncycastle.bcprov.source [1.65.0.v20200502-2229,1.65.0.v20200502-2229]
+	org.bouncycastle.bcpg [1.65.0.v20200527-1955,1.65.0.v20200527-1955]
+	org.bouncycastle.bcpg.source [1.65.0.v20200527-1955,1.65.0.v20200527-1955]
+	org.bouncycastle.bcpkix [1.65.0.v20200527-1955,1.65.0.v20200527-1955]
+	org.bouncycastle.bcpkix.source [1.65.0.v20200527-1955,1.65.0.v20200527-1955]
+	org.bouncycastle.bcprov [1.65.1.v20200529-1514,1.65.1.v20200529-1514]
+	org.bouncycastle.bcprov.source [1.65.1.v20200529-1514,1.65.1.v20200529-1514]
 	org.hamcrest [1.1.0.v20090501071000,1.1.0.v20090501071000]
 	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]
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/S20200519202422.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/S20201118210000.tpd
similarity index 71%
copy from org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/S20200519202422.tpd
copy to org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/S20201118210000.tpd
index abe97e1..a00a5e7 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/S20200519202422.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/S20201118210000.tpd
@@ -1,7 +1,7 @@
-target "S20200519202422" with source configurePhase
+target "S20201118210000" with source configurePhase
 // see https://download.eclipse.org/tools/orbit/downloads/
 
-location "https://download.eclipse.org/tools/orbit/downloads/drops/S20200519202422/repository" {
+location "https://download.eclipse.org/tools/orbit/downloads/drops/S20201118210000/repository" {
 	com.google.gson [2.8.2.v20180104-1110,2.8.2.v20180104-1110]
 	com.google.gson.source [2.8.2.v20180104-1110,2.8.2.v20180104-1110]
 	com.jcraft.jsch [0.1.55.v20190404-1902,0.1.55.v20190404-1902]
@@ -18,16 +18,16 @@
 	net.bytebuddy.byte-buddy.source [1.9.0.v20181107-1410,1.9.0.v20181107-1410]
 	net.i2p.crypto.eddsa [0.3.0.v20181102-1323,0.3.0.v20181102-1323]
 	net.i2p.crypto.eddsa.source [0.3.0.v20181102-1323,0.3.0.v20181102-1323]
-	org.apache.ant [1.10.8.v20200515-1239,1.10.8.v20200515-1239]
-	org.apache.ant.source [1.10.8.v20200515-1239,1.10.8.v20200515-1239]
-	org.apache.commons.codec [1.13.0.v20200108-0001,1.13.0.v20200108-0001]
-	org.apache.commons.codec.source [1.13.0.v20200108-0001,1.13.0.v20200108-0001]
+	org.apache.ant [1.10.9.v20201106-1946,1.10.9.v20201106-1946]
+	org.apache.ant.source [1.10.9.v20201106-1946,1.10.9.v20201106-1946]
+	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.19.0.v20200106-2343,1.19.0.v20200106-2343]
 	org.apache.commons.compress.source [1.19.0.v20200106-2343,1.19.0.v20200106-2343]
 	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.10.v20200114-1512,4.5.10.v20200114-1512]
-	org.apache.httpcomponents.httpclient.source [4.5.10.v20200114-1512,4.5.10.v20200114-1512]
+	org.apache.httpcomponents.httpclient [4.5.10.v20200830-2311,4.5.10.v20200830-2311]
+	org.apache.httpcomponents.httpclient.source [4.5.10.v20200830-2311,4.5.10.v20200830-2311]
 	org.apache.httpcomponents.httpcore [4.4.12.v20200108-1212,4.4.12.v20200108-1212]
 	org.apache.httpcomponents.httpcore.source [4.4.12.v20200108-1212,4.4.12.v20200108-1212]
 	org.apache.log4j [1.2.15.v201012070815,1.2.15.v201012070815]
@@ -38,12 +38,12 @@
 	org.apache.sshd.sftp.source [2.4.0.v20200319-1547,2.4.0.v20200319-1547]
 	org.assertj [3.14.0.v20200120-1926,3.14.0.v20200120-1926]
 	org.assertj.source [3.14.0.v20200120-1926,3.14.0.v20200120-1926]
-	org.bouncycastle.bcpg [1.65.0.v20200502-2229,1.65.0.v20200502-2229]
-	org.bouncycastle.bcpg.source [1.65.0.v20200502-2229,1.65.0.v20200502-2229]
-	org.bouncycastle.bcpkix [1.65.0.v20200502-2229,1.65.0.v20200502-2229]
-	org.bouncycastle.bcpkix.source [1.65.0.v20200502-2229,1.65.0.v20200502-2229]
-	org.bouncycastle.bcprov [1.65.0.v20200502-2229,1.65.0.v20200502-2229]
-	org.bouncycastle.bcprov.source [1.65.0.v20200502-2229,1.65.0.v20200502-2229]
+	org.bouncycastle.bcpg [1.65.0.v20200527-1955,1.65.0.v20200527-1955]
+	org.bouncycastle.bcpg.source [1.65.0.v20200527-1955,1.65.0.v20200527-1955]
+	org.bouncycastle.bcpkix [1.65.0.v20200527-1955,1.65.0.v20200527-1955]
+	org.bouncycastle.bcpkix.source [1.65.0.v20200527-1955,1.65.0.v20200527-1955]
+	org.bouncycastle.bcprov [1.65.1.v20200529-1514,1.65.1.v20200529-1514]
+	org.bouncycastle.bcprov.source [1.65.1.v20200529-1514,1.65.1.v20200529-1514]
 	org.hamcrest [1.1.0.v20090501071000,1.1.0.v20090501071000]
 	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]
@@ -57,10 +57,10 @@
 	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.2.v20121108-1250,1.7.2.v20121108-1250]
-	org.slf4j.api.source [1.7.2.v20121108-1250,1.7.2.v20121108-1250]
-	org.slf4j.impl.log4j12 [1.7.2.v20131105-2200,1.7.2.v20131105-2200]
-	org.slf4j.impl.log4j12.source [1.7.2.v20131105-2200,1.7.2.v20131105-2200]
+	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.log4j12 [1.7.30.v20201108-2042,1.7.30.v20201108-2042]
+	org.slf4j.binding.log4j12.source [1.7.30.v20201108-2042,1.7.30.v20201108-2042]
 	org.tukaani.xz [1.8.0.v20180207-1613,1.8.0.v20180207-1613]
 	org.tukaani.xz.source [1.8.0.v20180207-1613,1.8.0.v20180207-1613]
 }
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.4.x.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.4.x.tpd
index 0eea06d..70c426c 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.4.x.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.4.x.tpd
@@ -1,20 +1,20 @@
 target "jetty-9.4.x" with source configurePhase
 
-location jetty-9.4.25 "https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.28.v20200408/" {
-	org.eclipse.jetty.client [9.4.28.v20200408,9.4.28.v20200408]
-	org.eclipse.jetty.client.source [9.4.28.v20200408,9.4.28.v20200408]
-	org.eclipse.jetty.continuation [9.4.28.v20200408,9.4.28.v20200408]
-	org.eclipse.jetty.continuation.source [9.4.28.v20200408,9.4.28.v20200408]
-	org.eclipse.jetty.http [9.4.28.v20200408,9.4.28.v20200408]
-	org.eclipse.jetty.http.source [9.4.28.v20200408,9.4.28.v20200408]
-	org.eclipse.jetty.io [9.4.28.v20200408,9.4.28.v20200408]
-	org.eclipse.jetty.io.source [9.4.28.v20200408,9.4.28.v20200408]
-	org.eclipse.jetty.security [9.4.28.v20200408,9.4.28.v20200408]
-	org.eclipse.jetty.security.source [9.4.28.v20200408,9.4.28.v20200408]
-	org.eclipse.jetty.server [9.4.28.v20200408,9.4.28.v20200408]
-	org.eclipse.jetty.server.source [9.4.28.v20200408,9.4.28.v20200408]
-	org.eclipse.jetty.servlet [9.4.28.v20200408,9.4.28.v20200408]
-	org.eclipse.jetty.servlet.source [9.4.28.v20200408,9.4.28.v20200408]
-	org.eclipse.jetty.util [9.4.28.v20200408,9.4.28.v20200408]
-	org.eclipse.jetty.util.source [9.4.28.v20200408,9.4.28.v20200408]
+location jetty-9.4.30 "https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.30.v20200611/" {
+	org.eclipse.jetty.client [9.4.30.v20200611,9.4.30.v20200611]
+	org.eclipse.jetty.client.source [9.4.30.v20200611,9.4.30.v20200611]
+	org.eclipse.jetty.continuation [9.4.30.v20200611,9.4.30.v20200611]
+	org.eclipse.jetty.continuation.source [9.4.30.v20200611,9.4.30.v20200611]
+	org.eclipse.jetty.http [9.4.30.v20200611,9.4.30.v20200611]
+	org.eclipse.jetty.http.source [9.4.30.v20200611,9.4.30.v20200611]
+	org.eclipse.jetty.io [9.4.30.v20200611,9.4.30.v20200611]
+	org.eclipse.jetty.io.source [9.4.30.v20200611,9.4.30.v20200611]
+	org.eclipse.jetty.security [9.4.30.v20200611,9.4.30.v20200611]
+	org.eclipse.jetty.security.source [9.4.30.v20200611,9.4.30.v20200611]
+	org.eclipse.jetty.server [9.4.30.v20200611,9.4.30.v20200611]
+	org.eclipse.jetty.server.source [9.4.30.v20200611,9.4.30.v20200611]
+	org.eclipse.jetty.servlet [9.4.30.v20200611,9.4.30.v20200611]
+	org.eclipse.jetty.servlet.source [9.4.30.v20200611,9.4.30.v20200611]
+	org.eclipse.jetty.util [9.4.30.v20200611,9.4.30.v20200611]
+	org.eclipse.jetty.util.source [9.4.30.v20200611,9.4.30.v20200611]
 }
diff --git a/org.eclipse.jgit.packaging/pom.xml b/org.eclipse.jgit.packaging/pom.xml
index c106f87..165854d 100644
--- a/org.eclipse.jgit.packaging/pom.xml
+++ b/org.eclipse.jgit.packaging/pom.xml
@@ -212,7 +212,7 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-resources-plugin</artifactId>
-          <version>3.1.0</version>
+          <version>3.2.0</version>
           <configuration>
             <encoding>ISO-8859-1</encoding>
           </configuration>
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/LsRemoteTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/LsRemoteTest.java
index 4ecaeb6..46eec74 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/LsRemoteTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/LsRemoteTest.java
@@ -33,7 +33,7 @@
 		git.add().addFilepattern("Test.txt").call();
 		git.commit().setMessage("Initial commit").call();
 
-		// create a master branch and switch to it
+		// create a test branch and switch to it
 		git.branchCreate().setName("test").call();
 		RefUpdate rup = db.updateRef(Constants.HEAD);
 		rup.link("refs/heads/test");
@@ -104,4 +104,22 @@
 				"" }, result.toArray());
 	}
 
+	@Test
+	public void testLsRemoteSymRefs() throws Exception {
+		final List<String> result = CLIGitCommand.execute(
+				"git ls-remote --symref " + shellQuote(db.getDirectory()), db);
+		assertArrayEquals(new String[] {
+				"ref: refs/heads/test	HEAD",
+				"d0b1ef2b3dea02bb2ca824445c04e6def012c32c	HEAD",
+				"d0b1ef2b3dea02bb2ca824445c04e6def012c32c	refs/heads/master",
+				"d0b1ef2b3dea02bb2ca824445c04e6def012c32c	refs/heads/test",
+				"efc02078d83a5226986ae917323acec7e1e8b7cb	refs/tags/tag1",
+				"d0b1ef2b3dea02bb2ca824445c04e6def012c32c	refs/tags/tag1^{}",
+				"4e4b837e0fd4ba83c003678b03592dc1509a4115	refs/tags/tag2",
+				"d0b1ef2b3dea02bb2ca824445c04e6def012c32c	refs/tags/tag2^{}",
+				"489384bf8ace47522fe32093d2ceb85b65a6cbb1	refs/tags/tag3",
+				"d0b1ef2b3dea02bb2ca824445c04e6def012c32c	refs/tags/tag3^{}",
+				"" }, result.toArray());
+	}
+
 }
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 f3e1fe3..6112a27 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
@@ -256,6 +256,7 @@
 usage_LsRemote=List references in a remote repository
 usage_lsRemoteHeads=Show only refs starting with refs/heads
 usage_lsRemoteTags=Show only refs starting with refs/tags
+usage_lsRemoteSymref=In addition to the object pointed at, show the underlying ref pointed at when showing a symbolic ref.
 usage_LsTree=List the contents of a tree object
 usage_MakeCacheTree=Show the current cache tree structure
 usage_Match=Only consider tags matching the given glob(7) pattern or patterns, excluding the "refs/tags/" prefix.
@@ -294,6 +295,7 @@
 usage_StopTrackingAFile=Stop tracking a file
 usage_TextHashFunctions=Scan repository to compute maximum number of collisions for hash functions
 usage_UpdateRemoteRepositoryFromLocalRefs=Update remote repository from local refs
+usage_UseAll=Use all refs found in refs/
 usage_UseTags=Use any tag including lightweight tags
 usage_WriteDirCache=Write the DirCache
 usage_abbrevCommits=abbreviate commits to N + 1 digits
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 610b647..8aa119a 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
@@ -32,6 +32,9 @@
 	@Option(name = "--long", usage = "usage_LongFormat")
 	private boolean longDesc;
 
+	@Option(name = "--all", usage = "usage_UseTags")
+	private boolean useAll;
+
 	@Option(name = "--tags", usage = "usage_UseTags")
 	private boolean useTags;
 
@@ -50,6 +53,7 @@
 				cmd.setTarget(tree);
 			}
 			cmd.setLong(longDesc);
+			cmd.setAll(useAll);
 			cmd.setTags(useTags);
 			cmd.setAlways(always);
 			cmd.setMatch(patterns.toArray(new String[0]));
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsRemote.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsRemote.java
index 36812c0..055b48a 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsRemote.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsRemote.java
@@ -34,6 +34,9 @@
 	@Option(name = "--timeout", metaVar = "metaVar_service", usage = "usage_abortConnectionIfNoActivity")
 	int timeout = -1;
 
+	@Option(name = "--symref", usage = "usage_lsRemoteSymref")
+	private boolean symref;
+
 	@Argument(index = 0, metaVar = "metaVar_uriish", required = true)
 	private String remote;
 
@@ -47,6 +50,9 @@
 		try {
 			refs.addAll(command.call());
 			for (Ref r : refs) {
+				if (symref && r.isSymbolic()) {
+					show(r.getTarget(), r.getName());
+				}
 				show(r.getObjectId(), r.getName());
 				if (r.getPeeledObjectId() != null) {
 					show(r.getPeeledObjectId(), r.getName() + "^{}"); //$NON-NLS-1$
@@ -70,4 +76,13 @@
 		outw.print(name);
 		outw.println();
 	}
+
+	private void show(Ref ref, String name)
+			throws IOException {
+		outw.print("ref: ");
+		outw.print(ref.getName());
+		outw.print('\t');
+		outw.print(name);
+		outw.println();
+	}
 }
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 635ff75..fe3a261 100644
--- a/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF
@@ -15,6 +15,9 @@
  org.apache.sshd.common.session;version="[2.4.0,2.5.0)",
  org.apache.sshd.common.util.net;version="[2.4.0,2.5.0)",
  org.apache.sshd.common.util.security;version="[2.4.0,2.5.0)",
+ org.apache.sshd.server;version="[2.4.0,2.5.0)",
+ org.apache.sshd.server.forward;version="[2.4.0,2.5.0)",
+ org.eclipse.jgit.api;version="[6.0.0,6.1.0)",
  org.eclipse.jgit.api.errors;version="[6.0.0,6.1.0)",
  org.eclipse.jgit.internal.transport.sshd.proxy;version="[6.0.0,6.1.0)",
  org.eclipse.jgit.junit;version="[6.0.0,6.1.0)",
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 bfee042..3427da6 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
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2018, 2020 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
@@ -11,16 +11,40 @@
 
 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 java.io.BufferedWriter;
 import java.io.File;
 import java.io.IOException;
 import java.io.UncheckedIOException;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
+import java.nio.file.StandardOpenOption;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PublicKey;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.stream.Collectors;
+
 import org.apache.sshd.client.config.hosts.KnownHostEntry;
+import org.apache.sshd.client.config.hosts.KnownHostHashValue;
+import org.apache.sshd.common.PropertyResolverUtils;
+import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
+import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.util.net.SshdSocketAddress;
+import org.apache.sshd.server.ServerAuthenticationManager;
+import org.apache.sshd.server.ServerFactoryManager;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.forward.StaticDecisionForwardingFilter;
+import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.errors.TransportException;
 import org.eclipse.jgit.junit.ssh.SshTestBase;
 import org.eclipse.jgit.lib.Constants;
@@ -156,7 +180,7 @@
 				"IdentityFile " + privateKey1.getAbsolutePath());
 	}
 
-	@Test (expected = TransportException.class)
+	@Test
 	public void testHugePreamble() throws Exception {
 		// Test that the connection fails when the preamble is longer than 64k.
 		StringBuilder b = new StringBuilder();
@@ -169,10 +193,418 @@
 			lines[i] = line;
 		}
 		server.setPreamble(lines);
-		cloneWith(
-				"ssh://" + TEST_USER + "@localhost:" + testPort
-						+ "/doesntmatter",
-				defaultCloneDir, null,
+		TransportException e = assertThrows(TransportException.class,
+				() -> cloneWith(
+						"ssh://" + TEST_USER + "@localhost:" + testPort
+								+ "/doesntmatter",
+						defaultCloneDir, null,
+						"IdentityFile " + privateKey1.getAbsolutePath()));
+		// The assertions test that we don't run into bug 565394 / SSHD-1050
+		assertFalse(e.getMessage().contains("timeout"));
+		assertTrue(e.getMessage().contains("65536")
+				|| e.getMessage().contains("closed"));
+	}
+
+	/**
+	 * Test for SSHD-1028. If the server doesn't close sessions, the second
+	 * fetch will fail. Occurs on sshd 2.5.[01].
+	 *
+	 * @throws Exception
+	 *             on errors
+	 * @see <a href=
+	 *      "https://issues.apache.org/jira/projects/SSHD/issues/SSHD-1028">SSHD-1028</a>
+	 */
+	@Test
+	public void testCloneAndFetchWithSessionLimit() throws Exception {
+		PropertyResolverUtils.updateProperty(server.getPropertyResolver(),
+				ServerFactoryManager.MAX_CONCURRENT_SESSIONS, 2);
+		File localClone = cloneWith("ssh://localhost/doesntmatter",
+				defaultCloneDir, null, //
+				"Host localhost", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
 				"IdentityFile " + privateKey1.getAbsolutePath());
+		// Fetch a couple of times
+		try (Git git = Git.open(localClone)) {
+			git.fetch().call();
+			git.fetch().call();
+		}
+	}
+
+	/**
+	 * Creates a simple proxy server. Accepts only publickey authentication from
+	 * the given user with the given key, allows all forwardings. Adds the
+	 * proxy's host key to {@link #knownHosts}.
+	 *
+	 * @param user
+	 *            to accept
+	 * @param userKey
+	 *            public key of that user at this server
+	 * @param report
+	 *            single-element array to report back the forwarded address.
+	 * @return the started server
+	 * @throws Exception
+	 */
+	private SshServer createProxy(String user, File userKey,
+			SshdSocketAddress[] report) throws Exception {
+		SshServer proxy = SshServer.setUpDefaultServer();
+		// Give the server its own host key
+		KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
+		generator.initialize(2048);
+		KeyPair proxyHostKey = generator.generateKeyPair();
+		proxy.setKeyPairProvider(
+				session -> Collections.singletonList(proxyHostKey));
+		// Allow (only) publickey authentication
+		proxy.setUserAuthFactories(Collections.singletonList(
+				ServerAuthenticationManager.DEFAULT_USER_AUTH_PUBLIC_KEY_FACTORY));
+		// Install the user's public key
+		PublicKey userProxyKey = AuthorizedKeyEntry
+				.readAuthorizedKeys(userKey.toPath()).get(0)
+				.resolvePublicKey(null, PublicKeyEntryResolver.IGNORING);
+		proxy.setPublickeyAuthenticator(
+				(userName, publicKey, session) -> user.equals(userName)
+						&& KeyUtils.compareKeys(userProxyKey, publicKey));
+		// Allow forwarding
+		proxy.setForwardingFilter(new StaticDecisionForwardingFilter(true) {
+
+			@Override
+			protected boolean checkAcceptance(String request, Session session,
+					SshdSocketAddress target) {
+				report[0] = target;
+				return super.checkAcceptance(request, session, target);
+			}
+		});
+		proxy.start();
+		// Add the proxy's host key to knownhosts
+		try (BufferedWriter writer = Files.newBufferedWriter(
+				knownHosts.toPath(), StandardCharsets.US_ASCII,
+				StandardOpenOption.WRITE, StandardOpenOption.APPEND)) {
+			writer.append('\n');
+			KnownHostHashValue.appendHostPattern(writer, "localhost",
+					proxy.getPort());
+			writer.append(',');
+			KnownHostHashValue.appendHostPattern(writer, "127.0.0.1",
+					proxy.getPort());
+			writer.append(' ');
+			PublicKeyEntry.appendPublicKeyEntry(writer,
+					proxyHostKey.getPublic());
+			writer.append('\n');
+		}
+		return proxy;
+	}
+
+	@Test
+	public void testJumpHost() throws Exception {
+		SshdSocketAddress[] forwarded = { null };
+		try (SshServer proxy = createProxy(TEST_USER + 'X', publicKey2,
+				forwarded)) {
+			try {
+				// Now try to clone via the proxy
+				cloneWith("ssh://server/doesntmatter", defaultCloneDir, null, //
+						"Host server", //
+						"HostName localhost", //
+						"Port " + testPort, //
+						"User " + TEST_USER, //
+						"IdentityFile " + privateKey1.getAbsolutePath(), //
+						"ProxyJump " + TEST_USER + "X@proxy:" + proxy.getPort(), //
+						"", //
+						"Host proxy", //
+						"Hostname localhost", //
+						"IdentityFile " + privateKey2.getAbsolutePath());
+				assertNotNull(forwarded[0]);
+				assertEquals(testPort, forwarded[0].getPort());
+			} finally {
+				proxy.stop();
+			}
+		}
+	}
+
+	@Test
+	public void testJumpHostWrongKeyAtProxy() throws Exception {
+		// Test that we find the proxy server's URI in the exception message
+		SshdSocketAddress[] forwarded = { null };
+		try (SshServer proxy = createProxy(TEST_USER + 'X', publicKey2,
+				forwarded)) {
+			try {
+				// Now try to clone via the proxy
+				TransportException e = assertThrows(TransportException.class,
+						() -> cloneWith("ssh://server/doesntmatter",
+								defaultCloneDir, null, //
+								"Host server", //
+								"HostName localhost", //
+								"Port " + testPort, //
+								"User " + TEST_USER, //
+								"IdentityFile " + privateKey1.getAbsolutePath(),
+								"ProxyJump " + TEST_USER + "X@proxy:"
+										+ proxy.getPort(), //
+								"", //
+								"Host proxy", //
+								"Hostname localhost", //
+								"IdentityFile "
+										+ privateKey1.getAbsolutePath()));
+				String message = e.getMessage();
+				assertTrue(message.contains("localhost:" + proxy.getPort()));
+				assertTrue(message.contains("proxy:" + proxy.getPort()));
+			} finally {
+				proxy.stop();
+			}
+		}
+	}
+
+	@Test
+	public void testJumpHostWrongKeyAtServer() throws Exception {
+		// Test that we find the target server's URI in the exception message
+		SshdSocketAddress[] forwarded = { null };
+		try (SshServer proxy = createProxy(TEST_USER + 'X', publicKey2,
+				forwarded)) {
+			try {
+				// Now try to clone via the proxy
+				TransportException e = assertThrows(TransportException.class,
+						() -> cloneWith("ssh://server/doesntmatter",
+								defaultCloneDir, null, //
+								"Host server", //
+								"HostName localhost", //
+								"Port " + testPort, //
+								"User " + TEST_USER, //
+								"IdentityFile " + privateKey2.getAbsolutePath(),
+								"ProxyJump " + TEST_USER + "X@proxy:"
+										+ proxy.getPort(), //
+								"", //
+								"Host proxy", //
+								"Hostname localhost", //
+								"IdentityFile "
+										+ privateKey2.getAbsolutePath()));
+				String message = e.getMessage();
+				assertTrue(message.contains("localhost:" + testPort));
+				assertTrue(message.contains("ssh://server"));
+			} finally {
+				proxy.stop();
+			}
+		}
+	}
+
+	@Test
+	public void testJumpHostNonSsh() throws Exception {
+		SshdSocketAddress[] forwarded = { null };
+		try (SshServer proxy = createProxy(TEST_USER + 'X', publicKey2,
+				forwarded)) {
+			try {
+				TransportException e = assertThrows(TransportException.class,
+						() -> cloneWith("ssh://server/doesntmatter",
+								defaultCloneDir, null, //
+								"Host server", //
+								"HostName localhost", //
+								"Port " + testPort, //
+								"User " + TEST_USER, //
+								"IdentityFile " + privateKey1.getAbsolutePath(), //
+								"ProxyJump http://" + TEST_USER + "X@proxy:"
+										+ proxy.getPort(), //
+								"", //
+								"Host proxy", //
+								"Hostname localhost", //
+								"IdentityFile "
+										+ privateKey2.getAbsolutePath()));
+				// Find the expected message
+				Throwable t = e;
+				while (t != null) {
+					if (t instanceof URISyntaxException) {
+						break;
+					}
+					t = t.getCause();
+				}
+				assertNotNull(t);
+				assertTrue(t.getMessage().contains("Non-ssh"));
+			} finally {
+				proxy.stop();
+			}
+		}
+	}
+
+	@Test
+	public void testJumpHostWithPath() throws Exception {
+		SshdSocketAddress[] forwarded = { null };
+		try (SshServer proxy = createProxy(TEST_USER + 'X', publicKey2,
+				forwarded)) {
+			try {
+				TransportException e = assertThrows(TransportException.class,
+						() -> cloneWith("ssh://server/doesntmatter",
+								defaultCloneDir, null, //
+								"Host server", //
+								"HostName localhost", //
+								"Port " + testPort, //
+								"User " + TEST_USER, //
+								"IdentityFile " + privateKey1.getAbsolutePath(), //
+								"ProxyJump ssh://" + TEST_USER + "X@proxy:"
+										+ proxy.getPort() + "/wrongPath", //
+								"", //
+								"Host proxy", //
+								"Hostname localhost", //
+								"IdentityFile "
+										+ privateKey2.getAbsolutePath()));
+				// Find the expected message
+				Throwable t = e;
+				while (t != null) {
+					if (t instanceof URISyntaxException) {
+						break;
+					}
+					t = t.getCause();
+				}
+				assertNotNull(t);
+				assertTrue(t.getMessage().contains("wrongPath"));
+			} finally {
+				proxy.stop();
+			}
+		}
+	}
+
+	@Test
+	public void testJumpHostWithPathShort() throws Exception {
+		SshdSocketAddress[] forwarded = { null };
+		try (SshServer proxy = createProxy(TEST_USER + 'X', publicKey2,
+				forwarded)) {
+			try {
+				TransportException e = assertThrows(TransportException.class,
+						() -> cloneWith("ssh://server/doesntmatter",
+								defaultCloneDir, null, //
+								"Host server", //
+								"HostName localhost", //
+								"Port " + testPort, //
+								"User " + TEST_USER, //
+								"IdentityFile " + privateKey1.getAbsolutePath(), //
+								"ProxyJump " + TEST_USER + "X@proxy:wrongPath", //
+								"", //
+								"Host proxy", //
+								"Hostname localhost", //
+								"Port " + proxy.getPort(), //
+								"IdentityFile "
+										+ privateKey2.getAbsolutePath()));
+				// Find the expected message
+				Throwable t = e;
+				while (t != null) {
+					if (t instanceof URISyntaxException) {
+						break;
+					}
+					t = t.getCause();
+				}
+				assertNotNull(t);
+				assertTrue(t.getMessage().contains("wrongPath"));
+			} finally {
+				proxy.stop();
+			}
+		}
+	}
+
+	@Test
+	public void testJumpHostChain() throws Exception {
+		SshdSocketAddress[] forwarded1 = { null };
+		SshdSocketAddress[] forwarded2 = { null };
+		try (SshServer proxy1 = createProxy(TEST_USER + 'X', publicKey2,
+				forwarded1);
+				SshServer proxy2 = createProxy("foo", publicKey1, forwarded2)) {
+			try {
+				// Clone proxy1 -> proxy2 -> server
+				cloneWith("ssh://server/doesntmatter", defaultCloneDir, null, //
+						"Host server", //
+						"HostName localhost", //
+						"Port " + testPort, //
+						"User " + TEST_USER, //
+						"IdentityFile " + privateKey1.getAbsolutePath(), //
+						"ProxyJump proxy2," + TEST_USER + "X@proxy:"
+								+ proxy1.getPort(), //
+						"", //
+						"Host proxy", //
+						"Hostname localhost", //
+						"IdentityFile " + privateKey2.getAbsolutePath(), //
+						"", //
+						"Host proxy2", //
+						"Hostname localhost", //
+						"User foo", //
+						"Port " + proxy2.getPort(), //
+						"IdentityFile " + privateKey1.getAbsolutePath());
+				assertNotNull(forwarded1[0]);
+				assertEquals(proxy2.getPort(), forwarded1[0].getPort());
+				assertNotNull(forwarded2[0]);
+				assertEquals(testPort, forwarded2[0].getPort());
+			} finally {
+				proxy1.stop();
+				proxy2.stop();
+			}
+		}
+	}
+
+	@Test
+	public void testJumpHostCascade() throws Exception {
+		SshdSocketAddress[] forwarded1 = { null };
+		SshdSocketAddress[] forwarded2 = { null };
+		try (SshServer proxy1 = createProxy(TEST_USER + 'X', publicKey2,
+				forwarded1);
+				SshServer proxy2 = createProxy("foo", publicKey1, forwarded2)) {
+			try {
+				// Clone proxy2 -> proxy1 -> server
+				cloneWith("ssh://server/doesntmatter", defaultCloneDir, null, //
+						"Host server", //
+						"HostName localhost", //
+						"Port " + testPort, //
+						"User " + TEST_USER, //
+						"IdentityFile " + privateKey1.getAbsolutePath(), //
+						"ProxyJump " + TEST_USER + "X@proxy", //
+						"", //
+						"Host proxy", //
+						"Hostname localhost", //
+						"Port " + proxy1.getPort(), //
+						"ProxyJump ssh://proxy2:" + proxy2.getPort(), //
+						"IdentityFile " + privateKey2.getAbsolutePath(), //
+						"", //
+						"Host proxy2", //
+						"Hostname localhost", //
+						"User foo", //
+						"IdentityFile " + privateKey1.getAbsolutePath());
+				assertNotNull(forwarded1[0]);
+				assertEquals(testPort, forwarded1[0].getPort());
+				assertNotNull(forwarded2[0]);
+				assertEquals(proxy1.getPort(), forwarded2[0].getPort());
+			} finally {
+				proxy1.stop();
+				proxy2.stop();
+			}
+		}
+	}
+
+	@Test
+	public void testJumpHostRecursion() throws Exception {
+		SshdSocketAddress[] forwarded1 = { null };
+		SshdSocketAddress[] forwarded2 = { null };
+		try (SshServer proxy1 = createProxy(TEST_USER + 'X', publicKey2,
+				forwarded1);
+				SshServer proxy2 = createProxy("foo", publicKey1, forwarded2)) {
+			try {
+				TransportException e = assertThrows(TransportException.class,
+						() -> cloneWith(
+						"ssh://server/doesntmatter", defaultCloneDir, null, //
+						"Host server", //
+						"HostName localhost", //
+						"Port " + testPort, //
+						"User " + TEST_USER, //
+						"IdentityFile " + privateKey1.getAbsolutePath(), //
+						"ProxyJump " + TEST_USER + "X@proxy", //
+						"", //
+						"Host proxy", //
+						"Hostname localhost", //
+						"Port " + proxy1.getPort(), //
+						"ProxyJump ssh://proxy2:" + proxy2.getPort(), //
+						"IdentityFile " + privateKey2.getAbsolutePath(), //
+						"", //
+						"Host proxy2", //
+						"Hostname localhost", //
+						"User foo", //
+						"ProxyJump " + TEST_USER + "X@proxy", //
+						"IdentityFile " + privateKey1.getAbsolutePath()));
+				assertTrue(e.getMessage().contains("proxy"));
+			} finally {
+				proxy1.stop();
+				proxy2.stop();
+			}
+		}
 	}
 }
diff --git a/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF b/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF
index a9f1f1c..96b40ad 100644
--- a/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF
@@ -45,6 +45,7 @@
  org.apache.sshd.client.future;version="[2.4.0,2.5.0)",
  org.apache.sshd.client.keyverifier;version="[2.4.0,2.5.0)",
  org.apache.sshd.client.session;version="[2.4.0,2.5.0)",
+ org.apache.sshd.client.session.forward;version="[2.4.0,2.5.0)",
  org.apache.sshd.client.subsystem.sftp;version="[2.4.0,2.5.0)",
  org.apache.sshd.common;version="[2.4.0,2.5.0)",
  org.apache.sshd.common.auth;version="[2.4.0,2.5.0)",
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 4f85ebe..f810fd4 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,10 +1,14 @@
-authenticationCanceled=Authentication canceled: no password
+authenticationCanceled=SSH authentication canceled: no password given
+authenticationOnClosedSession=Authentication canceled: session is already closing or closed
 closeListenerFailed=Ssh session close listener failed
 configInvalidPath=Invalid path in ssh config key {0}: {1}
 configInvalidPattern=Invalid pattern in ssh config key {0}: {1}
 configInvalidPositive=Ssh config entry {0} must be a strictly positive number but is ''{1}''
+configInvalidProxyJump=Ssh config, host ''{0}'': Cannot parse ProxyJump ''{1}''
 configNoKnownHostKeyAlgorithms=No implementations for any of the algorithms ''{0}'' given in HostKeyAlgorithms in the ssh config; using the default.
 configNoRemainingHostKeyAlgorithms=Ssh config removed all host key algorithms: HostKeyAlgorithms ''{0}''
+configProxyJumpNotSsh=Non-ssh URI in ProxyJump ssh config
+configProxyJumpWithPath=ProxyJump ssh config: jump host specification must not have a path
 ftpCloseFailed=Closing the SFTP channel failed
 gssapiFailure=GSS-API error for mechanism OID {0}
 gssapiInitFailure=GSS-API initialization failure for mechanism {0}
@@ -45,12 +49,14 @@
 knownHostsUnknownKeyType=Cannot read server key from known hosts file {0}; line {1}
 knownHostsUserAskCreationMsg=File {0} does not exist.
 knownHostsUserAskCreationPrompt=Create file {0} ?
+loginDenied=Cannot log in at {0}:{1}
 passwordPrompt=Password
 proxyCannotAuthenticate=Cannot authenticate to proxy {0}
 proxyHttpFailure=HTTP Proxy connection to {0} failed with code {1}: {2}
 proxyHttpInvalidUserName=HTTP proxy connection {0} with invalid user name; must not contain colons: {1}
 proxyHttpUnexpectedReply=Unexpected HTTP proxy response from {0}: {1}
 proxyHttpUnspecifiedFailureReason=unspecified reason
+proxyJumpAbort=ProxyJump chain too long at {0}
 proxyPasswordPrompt=Proxy password
 proxySocksAuthenticationFailed=Authentication to SOCKS5 proxy {0} failed
 proxySocksFailureForbidden=SOCKS5 proxy {0}: connection to {1} not allowed by ruleset
@@ -75,7 +81,9 @@
 serverIdTooLong=Server identification is longer than 255 characters (including line ending): {0}
 serverIdWithNul=Server identification contains a NUL character: {0}
 sessionCloseFailed=Closing the session failed
+sessionWithoutUsername=SSH session created without user name; cannot authenticate
 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
 sshProcessStillRunning={0} is not yet completed, cannot get exit code
+sshProxySessionCloseFailed=Error while closing proxy session {0}
 unknownProxyProtocol=Ignoring unknown proxy protocol {0}
\ No newline at end of file
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/AuthenticationCanceledException.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/AuthenticationCanceledException.java
new file mode 100644
index 0000000..aa46235
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/AuthenticationCanceledException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2020, 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;
+
+import java.util.concurrent.CancellationException;
+
+/**
+ * An exception to report that the user canceled the SSH authentication.
+ */
+public class AuthenticationCanceledException extends CancellationException {
+
+	// If this is not a CancellationException sshd will try other authentication
+	// mechanisms.
+
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * Creates a new {@link AuthenticationCanceledException}.
+	 */
+	public AuthenticationCanceledException() {
+		super(SshdText.get().authenticationCanceled);
+	}
+}
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 420a1d1..0d6f302 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
@@ -18,21 +18,30 @@
 import java.security.GeneralSecurityException;
 import java.security.PublicKey;
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 import org.apache.sshd.client.ClientFactoryManager;
 import org.apache.sshd.client.config.hosts.HostConfigEntry;
+import org.apache.sshd.client.future.AuthFuture;
 import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
 import org.apache.sshd.client.session.ClientSessionImpl;
+import org.apache.sshd.client.session.ClientUserAuthService;
+import org.apache.sshd.common.AttributeRepository;
 import org.apache.sshd.common.FactoryManager;
+import org.apache.sshd.common.PropertyResolver;
 import org.apache.sshd.common.PropertyResolverUtils;
 import org.apache.sshd.common.SshException;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.io.IoSession;
 import org.apache.sshd.common.io.IoWriteFuture;
+import org.apache.sshd.common.kex.KexState;
 import org.apache.sshd.common.util.Readable;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.eclipse.jgit.errors.InvalidPatternException;
@@ -68,6 +77,17 @@
 	private volatile StatefulProxyConnector proxyHandler;
 
 	/**
+	 * Work-around for bug 565394 / SSHD-1050; remove when using sshd 2.6.0.
+	 */
+	private volatile AuthFuture authFuture;
+
+	/** Records exceptions before there is an authFuture. */
+	private List<Throwable> earlyErrors = new ArrayList<>();
+
+	/** Guards setting an earlyError and the authFuture together. */
+	private final Object errorLock = new Object();
+
+	/**
 	 * @param manager
 	 * @param session
 	 * @throws Exception
@@ -77,6 +97,125 @@
 		super(manager, session);
 	}
 
+	// BEGIN Work-around for bug 565394 / SSHD-1050
+	// Remove when using sshd 2.6.0.
+
+	@Override
+	public AuthFuture auth() throws IOException {
+		if (getUsername() == null) {
+			throw new IllegalStateException(
+					SshdText.get().sessionWithoutUsername);
+		}
+		ClientUserAuthService authService = getUserAuthService();
+		String serviceName = nextServiceName();
+		List<Throwable> errors = null;
+		AuthFuture future;
+		// Guard both getting early errors and setting authFuture
+		synchronized (errorLock) {
+			future = authService.auth(serviceName);
+			if (future == null) {
+				// Internal error; no translation.
+				throw new IllegalStateException(
+						"No auth future generated by service '" //$NON-NLS-1$
+								+ serviceName + '\'');
+			}
+			errors = earlyErrors;
+			earlyErrors = null;
+			authFuture = future;
+		}
+		if (errors != null && !errors.isEmpty()) {
+			Iterator<Throwable> iter = errors.iterator();
+			Throwable first = iter.next();
+			iter.forEachRemaining(t -> {
+				if (t != first && t != null) {
+					first.addSuppressed(t);
+				}
+			});
+			// Mark the future as having had an exception; just to be on the
+			// safe side. Actually, there shouldn't be anyone waiting on this
+			// future yet.
+			future.setException(first);
+			if (log.isDebugEnabled()) {
+				log.debug("auth({}) early exception type={}: {}", //$NON-NLS-1$
+						this, first.getClass().getSimpleName(),
+						first.getMessage());
+			}
+			if (first instanceof SshException) {
+				throw new SshException(
+						((SshException) first).getDisconnectCode(),
+						first.getMessage(), first);
+			}
+			throw new IOException(first.getMessage(), first);
+		}
+		return future;
+	}
+
+	@Override
+	protected void signalAuthFailure(AuthFuture future, Throwable t) {
+		signalAuthFailure(t);
+	}
+
+	private void signalAuthFailure(Throwable t) {
+		AuthFuture future = authFuture;
+		if (future == null) {
+			synchronized (errorLock) {
+				if (earlyErrors != null) {
+					earlyErrors.add(t);
+				}
+				future = authFuture;
+			}
+		}
+		if (future != null) {
+			future.setException(t);
+		}
+		if (log.isDebugEnabled()) {
+			boolean signalled = future != null && t == future.getException();
+			log.debug("signalAuthFailure({}) type={}, signalled={}: {}", this, //$NON-NLS-1$
+					t.getClass().getSimpleName(), Boolean.valueOf(signalled),
+					t.getMessage());
+		}
+	}
+
+	@Override
+	public void exceptionCaught(Throwable t) {
+		signalAuthFailure(t);
+		super.exceptionCaught(t);
+	}
+
+	@Override
+	protected void preClose() {
+		signalAuthFailure(
+				new SshException(SshdText.get().authenticationOnClosedSession));
+		super.preClose();
+	}
+
+	@Override
+	protected void handleDisconnect(int code, String msg, String lang,
+			Buffer buffer) throws Exception {
+		signalAuthFailure(new SshException(code, msg));
+		super.handleDisconnect(code, msg, lang, buffer);
+	}
+
+	@Override
+	protected <C extends Collection<ClientSessionEvent>> C updateCurrentSessionState(
+			C newState) {
+		if (closeFuture.isClosed()) {
+			newState.add(ClientSessionEvent.CLOSED);
+		}
+		if (isAuthenticated()) { // authFuture.isSuccess()
+			newState.add(ClientSessionEvent.AUTHED);
+		}
+		if (KexState.DONE.equals(getKexState())) {
+			AuthFuture future = authFuture;
+			if (future == null || future.isFailure()) {
+				newState.add(ClientSessionEvent.WAIT_AUTH);
+			}
+		}
+		return newState;
+	}
+
+	// END Work-around for bug 565394 / SSHD-1050
+
 	/**
 	 * Retrieves the {@link HostConfigEntry} this session was created for.
 	 *
@@ -419,4 +558,122 @@
 		return b.toString();
 	}
 
+	@Override
+	public <T> T getAttribute(AttributeKey<T> key) {
+		T value = super.getAttribute(key);
+		if (value == null) {
+			IoSession ioSession = getIoSession();
+			if (ioSession != null) {
+				Object obj = ioSession.getAttribute(AttributeRepository.class);
+				if (obj instanceof AttributeRepository) {
+					AttributeRepository sessionAttributes = (AttributeRepository) obj;
+					value = sessionAttributes.resolveAttribute(key);
+				}
+			}
+		}
+		return value;
+	}
+
+	@Override
+	public PropertyResolver getParentPropertyResolver() {
+		IoSession ioSession = getIoSession();
+		if (ioSession != null) {
+			Object obj = ioSession.getAttribute(AttributeRepository.class);
+			if (obj instanceof PropertyResolver) {
+				return (PropertyResolver) obj;
+			}
+		}
+		return super.getParentPropertyResolver();
+	}
+
+	/**
+	 * An {@link AttributeRepository} that chains together two other attribute
+	 * sources in a hierarchy.
+	 */
+	public static class ChainingAttributes implements AttributeRepository {
+
+		private final AttributeRepository delegate;
+
+		private final AttributeRepository parent;
+
+		/**
+		 * Create a new {@link ChainingAttributes} attribute source.
+		 *
+		 * @param self
+		 *            to search for attributes first
+		 * @param parent
+		 *            to search for attributes if not found in {@code self}
+		 */
+		public ChainingAttributes(AttributeRepository self,
+				AttributeRepository parent) {
+			this.delegate = self;
+			this.parent = parent;
+		}
+
+		@Override
+		public int getAttributesCount() {
+			return delegate.getAttributesCount();
+		}
+
+		@Override
+		public <T> T getAttribute(AttributeKey<T> key) {
+			return delegate.getAttribute(Objects.requireNonNull(key));
+		}
+
+		@Override
+		public Collection<AttributeKey<?>> attributeKeys() {
+			return delegate.attributeKeys();
+		}
+
+		@Override
+		public <T> T resolveAttribute(AttributeKey<T> key) {
+			T value = getAttribute(Objects.requireNonNull(key));
+			if (value == null) {
+				return parent.getAttribute(key);
+			}
+			return value;
+		}
+	}
+
+	/**
+	 * A {@link ChainingAttributes} repository that doubles as a
+	 * {@link PropertyResolver}. The property map can be set via the attribute
+	 * key {@link SessionAttributes#PROPERTIES}.
+	 */
+	public static class SessionAttributes extends ChainingAttributes
+			implements PropertyResolver {
+
+		/** Key for storing a map of properties in the attributes. */
+		public static final AttributeKey<Map<String, Object>> PROPERTIES = new AttributeKey<>();
+
+		private final PropertyResolver parentProperties;
+
+		/**
+		 * Creates a new {@link SessionAttributes} attribute and property
+		 * source.
+		 *
+		 * @param self
+		 *            to search for attributes first
+		 * @param parent
+		 *            to search for attributes if not found in {@code self}
+		 * @param parentProperties
+		 *            to search for properties if not found in {@code self}
+		 */
+		public SessionAttributes(AttributeRepository self,
+				AttributeRepository parent, PropertyResolver parentProperties) {
+			super(self, parent);
+			this.parentProperties = parentProperties;
+		}
+
+		@Override
+		public PropertyResolver getParentPropertyResolver() {
+			return parentProperties;
+		}
+
+		@Override
+		public Map<String, Object> getProperties() {
+			Map<String, Object> props = getAttribute(PROPERTIES);
+			return props == null ? Collections.emptyMap() : props;
+		}
+	}
 }
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPasswordAuthentication.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPasswordAuthentication.java
index 0a7082c..4abd6e9 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPasswordAuthentication.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPasswordAuthentication.java
@@ -9,8 +9,6 @@
  */
 package org.eclipse.jgit.internal.transport.sshd;
 
-import java.util.concurrent.CancellationException;
-
 import org.apache.sshd.client.ClientAuthenticationManager;
 import org.apache.sshd.client.auth.keyboard.UserInteraction;
 import org.apache.sshd.client.auth.password.UserAuthPassword;
@@ -49,7 +47,7 @@
 		}
 		String password = getPassword(session, interaction);
 		if (password == null) {
-			throw new CancellationException();
+			throw new AuthenticationCanceledException();
 		}
 		// sendPassword takes a buffer as first argument, but actually doesn't
 		// use it and creates its own buffer...
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java
index b8dd60f..beaaeca 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2018, 2020 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
@@ -23,12 +23,16 @@
 import java.security.GeneralSecurityException;
 import java.security.KeyPair;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
 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.client.ClientAuthenticationManager;
 import org.apache.sshd.client.SshClient;
 import org.apache.sshd.client.config.hosts.HostConfigEntry;
 import org.apache.sshd.client.future.ConnectFuture;
@@ -45,6 +49,9 @@
 import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.common.session.helpers.AbstractSession;
 import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.net.SshdSocketAddress;
+import org.eclipse.jgit.internal.transport.sshd.JGitClientSession.ChainingAttributes;
+import org.eclipse.jgit.internal.transport.sshd.JGitClientSession.SessionAttributes;
 import org.eclipse.jgit.internal.transport.sshd.proxy.HttpClientConnector;
 import org.eclipse.jgit.internal.transport.sshd.proxy.Socks5ClientConnector;
 import org.eclipse.jgit.transport.CredentialsProvider;
@@ -52,6 +59,7 @@
 import org.eclipse.jgit.transport.sshd.KeyCache;
 import org.eclipse.jgit.transport.sshd.ProxyData;
 import org.eclipse.jgit.transport.sshd.ProxyDataFactory;
+import org.eclipse.jgit.util.StringUtils;
 
 /**
  * Customized {@link SshClient} for JGit. It creates specialized
@@ -75,6 +83,16 @@
 	 */
 	public static final AttributeKey<String> PREFERRED_AUTHENTICATIONS = new AttributeKey<>();
 
+	/**
+	 * An attribute key for storing an alternate local address to connect to if
+	 * a local forward from a ProxyJump ssh config is present. If set,
+	 * {@link #connect(HostConfigEntry, AttributeRepository, SocketAddress)}
+	 * will not connect to the address obtained from the {@link HostConfigEntry}
+	 * but to the address stored in this key (which is assumed to forward the
+	 * {@code HostConfigEntry} address).
+	 */
+	public static final AttributeKey<SshdSocketAddress> LOCAL_FORWARD_ADDRESS = new AttributeKey<>();
+
 	private KeyCache keyCache;
 
 	private CredentialsProvider credentialsProvider;
@@ -95,40 +113,72 @@
 			throw new IllegalStateException("SshClient not started."); //$NON-NLS-1$
 		}
 		Objects.requireNonNull(hostConfig, "No host configuration"); //$NON-NLS-1$
-		String host = ValidateUtils.checkNotNullAndNotEmpty(
+		String originalHost = ValidateUtils.checkNotNullAndNotEmpty(
 				hostConfig.getHostName(), "No target host"); //$NON-NLS-1$
-		int port = hostConfig.getPort();
-		ValidateUtils.checkTrue(port > 0, "Invalid port: %d", port); //$NON-NLS-1$
+		int originalPort = hostConfig.getPort();
+		ValidateUtils.checkTrue(originalPort > 0, "Invalid port: %d", //$NON-NLS-1$
+				originalPort);
+		InetSocketAddress originalAddress = new InetSocketAddress(originalHost,
+				originalPort);
+		InetSocketAddress targetAddress = originalAddress;
 		String userName = hostConfig.getUsername();
-		InetSocketAddress address = new InetSocketAddress(host, port);
-		ConnectFuture connectFuture = new DefaultConnectFuture(
-				userName + '@' + address, null);
-		SshFutureListener<IoConnectFuture> listener = createConnectCompletionListener(
-				connectFuture, userName, address, hostConfig);
-		// sshd needs some entries from the host config already in the
-		// constructor of the session. Put those as properties on this client,
-		// where it will find them. We can set the host config only once the
-		// session object has been created.
-		copyProperty(
-				hostConfig.getProperty(SshConstants.PREFERRED_AUTHENTICATIONS,
-						getAttribute(PREFERRED_AUTHENTICATIONS)),
-				PREFERRED_AUTHS);
-		setAttribute(HOST_CONFIG_ENTRY, hostConfig);
-		setAttribute(ORIGINAL_REMOTE_ADDRESS, address);
-		// Proxy support
-		ProxyData proxy = getProxyData(address);
-		if (proxy != null) {
-			address = configureProxy(proxy, address);
-			proxy.clearPassword();
+		String id = userName + '@' + originalAddress;
+		AttributeRepository attributes = chain(context, this);
+		SshdSocketAddress localForward = attributes
+				.resolveAttribute(LOCAL_FORWARD_ADDRESS);
+		if (localForward != null) {
+			targetAddress = new InetSocketAddress(localForward.getHostName(),
+					localForward.getPort());
+			id += '/' + targetAddress.toString();
 		}
-		connector.connect(address, this, localAddress).addListener(listener);
+		ConnectFuture connectFuture = new DefaultConnectFuture(id, null);
+		SshFutureListener<IoConnectFuture> listener = createConnectCompletionListener(
+				connectFuture, userName, originalAddress, hostConfig);
+		attributes = sessionAttributes(attributes, hostConfig, originalAddress);
+		// Proxy support
+		if (localForward == null) {
+			ProxyData proxy = getProxyData(targetAddress);
+			if (proxy != null) {
+				targetAddress = configureProxy(proxy, targetAddress);
+				proxy.clearPassword();
+			}
+		}
+		connector.connect(targetAddress, attributes, localAddress)
+				.addListener(listener);
 		return connectFuture;
 	}
 
-	private void copyProperty(String value, String key) {
-		if (value != null && !value.isEmpty()) {
-			getProperties().put(key, value);
+	private AttributeRepository chain(AttributeRepository self,
+			AttributeRepository parent) {
+		if (self == null) {
+			return Objects.requireNonNull(parent);
 		}
+		if (parent == null || parent == self) {
+			return self;
+		}
+		return new ChainingAttributes(self, parent);
+	}
+
+	private AttributeRepository sessionAttributes(AttributeRepository parent,
+			HostConfigEntry hostConfig, InetSocketAddress originalAddress) {
+		// sshd needs some entries from the host config already in the
+		// constructor of the session. Put those into a dedicated
+		// AttributeRepository for the new session where it will find them.
+		// We can set the host config only once the session object has been
+		// created.
+		Map<AttributeKey<?>, Object> data = new HashMap<>();
+		data.put(HOST_CONFIG_ENTRY, hostConfig);
+		data.put(ORIGINAL_REMOTE_ADDRESS, originalAddress);
+		String preferredAuths = hostConfig.getProperty(
+				SshConstants.PREFERRED_AUTHENTICATIONS,
+				resolveAttribute(PREFERRED_AUTHENTICATIONS));
+		if (!StringUtils.isEmptyOrNull(preferredAuths)) {
+			data.put(SessionAttributes.PROPERTIES,
+					Collections.singletonMap(PREFERRED_AUTHS, preferredAuths));
+		}
+		return new SessionAttributes(
+				AttributeRepository.ofAttributesMap(data),
+				parent, this);
 	}
 
 	private ProxyData getProxyData(InetSocketAddress remoteAddress) {
@@ -219,11 +269,6 @@
 		int numberOfPasswordPrompts = getNumberOfPasswordPrompts(hostConfig);
 		session.getProperties().put(PASSWORD_PROMPTS,
 				Integer.valueOf(numberOfPasswordPrompts));
-		FilePasswordProvider passwordProvider = getFilePasswordProvider();
-		if (passwordProvider instanceof RepeatingFilePasswordProvider) {
-			((RepeatingFilePasswordProvider) passwordProvider)
-					.setAttempts(numberOfPasswordPrompts);
-		}
 		List<Path> identities = hostConfig.getIdentities().stream()
 				.map(s -> {
 					try {
@@ -237,6 +282,7 @@
 				.collect(Collectors.toList());
 		CachingKeyPairProvider ourConfiguredKeysProvider = new CachingKeyPairProvider(
 				identities, keyCache);
+		FilePasswordProvider passwordProvider = getFilePasswordProvider();
 		ourConfiguredKeysProvider.setPasswordFinder(passwordProvider);
 		if (hostConfig.isIdentitiesOnly()) {
 			session.setKeyIdentityProvider(ourConfiguredKeysProvider);
@@ -265,9 +311,7 @@
 			log.warn(format(SshdText.get().configInvalidPositive,
 					SshConstants.NUMBER_OF_PASSWORD_PROMPTS, prompts));
 		}
-		// Default for NumberOfPasswordPrompts according to
-		// https://man.openbsd.org/ssh_config
-		return 3;
+		return ClientAuthenticationManager.DEFAULT_PASSWORD_PROMPTS;
 	}
 
 	/**
@@ -408,6 +452,5 @@
 
 			};
 		}
-
 	}
 }
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/PasswordProviderWrapper.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/PasswordProviderWrapper.java
index 5b48a8c..078e411 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/PasswordProviderWrapper.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/PasswordProviderWrapper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2018, 2020 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
@@ -16,8 +16,12 @@
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
 
+import org.apache.sshd.client.ClientAuthenticationManager;
+import org.apache.sshd.common.AttributeRepository.AttributeKey;
 import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
 import org.apache.sshd.common.session.SessionContext;
 import org.eclipse.jgit.annotations.NonNull;
 import org.eclipse.jgit.transport.CredentialsProvider;
@@ -25,39 +29,61 @@
 import org.eclipse.jgit.transport.sshd.KeyPasswordProvider;
 
 /**
- * A bridge from sshd's {@link RepeatingFilePasswordProvider} to our
+ * A bridge from sshd's {@link FilePasswordProvider} to our per-session
  * {@link KeyPasswordProvider} API.
  */
-public class PasswordProviderWrapper implements RepeatingFilePasswordProvider {
+public class PasswordProviderWrapper implements FilePasswordProvider {
 
-	private final KeyPasswordProvider delegate;
+	private static final AttributeKey<PerSessionState> STATE = new AttributeKey<>();
 
-	private Map<String, AtomicInteger> counts = new ConcurrentHashMap<>();
+	private static class PerSessionState {
+
+		Map<String, AtomicInteger> counts = new ConcurrentHashMap<>();
+
+		KeyPasswordProvider delegate;
+
+	}
+
+	private final Supplier<KeyPasswordProvider> factory;
 
 	/**
-	 * @param delegate
+	 * Creates a new {@link PasswordProviderWrapper}.
+	 *
+	 * @param factory
+	 *            to use to create per-session {@link KeyPasswordProvider}s
 	 */
-	public PasswordProviderWrapper(@NonNull KeyPasswordProvider delegate) {
-		this.delegate = delegate;
+	public PasswordProviderWrapper(
+			@NonNull Supplier<KeyPasswordProvider> factory) {
+		this.factory = factory;
 	}
 
-	@Override
-	public void setAttempts(int numberOfPasswordPrompts) {
-		delegate.setAttempts(numberOfPasswordPrompts);
-	}
-
-	@Override
-	public int getAttempts() {
-		return delegate.getAttempts();
+	private PerSessionState getState(SessionContext context) {
+		PerSessionState state = context.getAttribute(STATE);
+		if (state == null) {
+			state = new PerSessionState();
+			state.delegate = factory.get();
+			Integer maxNumberOfAttempts = context
+					.getInteger(ClientAuthenticationManager.PASSWORD_PROMPTS);
+			if (maxNumberOfAttempts != null
+					&& maxNumberOfAttempts.intValue() > 0) {
+				state.delegate.setAttempts(maxNumberOfAttempts.intValue());
+			} else {
+				state.delegate.setAttempts(
+						ClientAuthenticationManager.DEFAULT_PASSWORD_PROMPTS);
+			}
+			context.setAttribute(STATE, state);
+		}
+		return state;
 	}
 
 	@Override
 	public String getPassword(SessionContext session, NamedResource resource,
 			int attemptIndex) throws IOException {
 		String key = resource.getName();
-		int attempt = counts
+		PerSessionState state = getState(session);
+		int attempt = state.counts
 				.computeIfAbsent(key, k -> new AtomicInteger()).get();
-		char[] passphrase = delegate.getPassphrase(toUri(key), attempt);
+		char[] passphrase = state.delegate.getPassphrase(toUri(key), attempt);
 		if (passphrase == null) {
 			return null;
 		}
@@ -74,18 +100,19 @@
 			String password, Exception err)
 			throws IOException, GeneralSecurityException {
 		String key = resource.getName();
-		AtomicInteger count = counts.get(key);
+		PerSessionState state = getState(session);
+		AtomicInteger count = state.counts.get(key);
 		int numberOfAttempts = count == null ? 0 : count.incrementAndGet();
 		ResourceDecodeResult result = null;
 		try {
-			if (delegate.keyLoaded(toUri(key), numberOfAttempts, err)) {
+			if (state.delegate.keyLoaded(toUri(key), numberOfAttempts, err)) {
 				result = ResourceDecodeResult.RETRY;
 			} else {
 				result = ResourceDecodeResult.TERMINATE;
 			}
 		} finally {
 			if (result != ResourceDecodeResult.RETRY) {
-				counts.remove(key);
+				state.counts.remove(key);
 			}
 		}
 		return result;
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/RepeatingFilePasswordProvider.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/RepeatingFilePasswordProvider.java
deleted file mode 100644
index 86f0fe7..0000000
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/RepeatingFilePasswordProvider.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2018, 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;
-
-import org.apache.sshd.common.config.keys.FilePasswordProvider;
-
-/**
- * A {@link FilePasswordProvider} augmented to support repeatedly asking for
- * passwords.
- *
- */
-public interface RepeatingFilePasswordProvider extends FilePasswordProvider {
-
-	/**
-	 * Define the maximum number of attempts to get a password that should be
-	 * attempted for one identity resource through this provider.
-	 *
-	 * @param numberOfPasswordPrompts
-	 *            number of times to ask for a password;
-	 *            {@link IllegalArgumentException} may be thrown if <= 0
-	 */
-	void setAttempts(int numberOfPasswordPrompts);
-
-	/**
-	 * Gets the maximum number of attempts to get a password that should be
-	 * attempted for one identity resource through this provider.
-	 *
-	 * @return the maximum number of attempts to try, always >= 1.
-	 */
-	default int getAttempts() {
-		return 1;
-	}
-
-}
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 f67170e..13bb3eb 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
@@ -19,12 +19,16 @@
 
 	// @formatter:off
 	/***/ public String authenticationCanceled;
+	/***/ public String authenticationOnClosedSession;
 	/***/ public String closeListenerFailed;
 	/***/ public String configInvalidPath;
 	/***/ public String configInvalidPattern;
 	/***/ public String configInvalidPositive;
+	/***/ public String configInvalidProxyJump;
 	/***/ public String configNoKnownHostKeyAlgorithms;
 	/***/ public String configNoRemainingHostKeyAlgorithms;
+	/***/ public String configProxyJumpNotSsh;
+	/***/ public String configProxyJumpWithPath;
 	/***/ public String ftpCloseFailed;
 	/***/ public String gssapiFailure;
 	/***/ public String gssapiInitFailure;
@@ -57,12 +61,14 @@
 	/***/ public String knownHostsUnknownKeyType;
 	/***/ public String knownHostsUserAskCreationMsg;
 	/***/ public String knownHostsUserAskCreationPrompt;
+	/***/ public String loginDenied;
 	/***/ public String passwordPrompt;
 	/***/ public String proxyCannotAuthenticate;
 	/***/ public String proxyHttpFailure;
 	/***/ public String proxyHttpInvalidUserName;
 	/***/ public String proxyHttpUnexpectedReply;
 	/***/ public String proxyHttpUnspecifiedFailureReason;
+	/***/ public String proxyJumpAbort;
 	/***/ public String proxyPasswordPrompt;
 	/***/ public String proxySocksAuthenticationFailed;
 	/***/ public String proxySocksFailureForbidden;
@@ -87,9 +93,11 @@
 	/***/ public String serverIdTooLong;
 	/***/ public String serverIdWithNul;
 	/***/ public String sessionCloseFailed;
+	/***/ public String sessionWithoutUsername;
 	/***/ public String sshClosingDown;
 	/***/ public String sshCommandTimeout;
 	/***/ public String sshProcessStillRunning;
+	/***/ public String sshProxySessionCloseFailed;
 	/***/ public String unknownProxyProtocol;
 
 }
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/HttpParser.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/HttpParser.java
index d5b8037..0500a63 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/HttpParser.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/HttpParser.java
@@ -13,6 +13,8 @@
 import java.util.Iterator;
 import java.util.List;
 
+import org.eclipse.jgit.util.HttpSupport;
+
 /**
  * A basic parser for HTTP response headers. Handles status lines and
  * authentication headers (WWW-Authenticate, Proxy-Authenticate).
@@ -135,7 +137,7 @@
 		int length = header.length();
 		for (int i = 0; i < length;) {
 			int start = skipWhiteSpace(header, i);
-			int end = scanToken(header, start);
+			int end = HttpSupport.scanToken(header, start);
 			if (end <= start) {
 				break;
 			}
@@ -156,7 +158,7 @@
 			// optional legacy whitespace around the equals sign), where the
 			// value can be either a token or a quoted string.
 			start = skipWhiteSpace(header, start);
-			int end = scanToken(header, start);
+			int end = HttpSupport.scanToken(header, start);
 			if (end == start) {
 				// Nothing found. Either at end or on a comma.
 				if (start < header.length() && header.charAt(start) == ',') {
@@ -222,7 +224,7 @@
 					challenge.addArgument(header.substring(start, end), value);
 					start = nextEnd[0];
 				} else {
-					int nextEnd = scanToken(header, nextStart);
+					int nextEnd = HttpSupport.scanToken(header, nextStart);
 					challenge.addArgument(header.substring(start, end),
 							header.substring(nextStart, nextEnd));
 					start = nextEnd;
@@ -244,49 +246,6 @@
 		return i;
 	}
 
-	private static int scanToken(String header, int from) {
-		int length = header.length();
-		int i = from;
-		while (i < length) {
-			char c = header.charAt(i);
-			switch (c) {
-			case '!':
-			case '#':
-			case '$':
-			case '%':
-			case '&':
-			case '\'':
-			case '*':
-			case '+':
-			case '-':
-			case '.':
-			case '^':
-			case '_':
-			case '`':
-			case '|':
-			case '0':
-			case '1':
-			case '2':
-			case '3':
-			case '4':
-			case '5':
-			case '6':
-			case '7':
-			case '8':
-			case '9':
-				i++;
-				break;
-			default:
-				if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z') {
-					i++;
-					break;
-				}
-				return i;
-			}
-		}
-		return i;
-	}
-
 	private static String scanQuotedString(String header, int from, int[] to) {
 		StringBuilder result = new StringBuilder();
 		int length = header.length();
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/IdentityPasswordProvider.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/IdentityPasswordProvider.java
index 004d3f8..dd6894b 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/IdentityPasswordProvider.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/IdentityPasswordProvider.java
@@ -19,13 +19,14 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.CancellationException;
 
 import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.internal.transport.sshd.AuthenticationCanceledException;
 import org.eclipse.jgit.internal.transport.sshd.SshdText;
 import org.eclipse.jgit.transport.CredentialItem;
 import org.eclipse.jgit.transport.CredentialsProvider;
 import org.eclipse.jgit.transport.URIish;
+import org.eclipse.jgit.util.StringUtils;
 
 /**
  * A {@link KeyPasswordProvider} based on a {@link CredentialsProvider}.
@@ -155,35 +156,84 @@
 		state.incCount();
 		String message = state.count == 1 ? SshdText.get().keyEncryptedMsg
 				: SshdText.get().keyEncryptedRetry;
-		char[] pass = getPassword(uri, message);
+		char[] pass = getPassword(uri, format(message, uri));
 		state.setPassword(pass);
 		return pass;
 	}
 
-	private char[] getPassword(URIish uri, String message) {
+	/**
+	 * Retrieves the JGit {@link CredentialsProvider} to use for user
+	 * interaction.
+	 *
+	 * @return the {@link CredentialsProvider} or {@code null} if none
+	 *         configured
+	 * @since 5.10
+	 */
+	protected CredentialsProvider getCredentialsProvider() {
+		return provider;
+	}
+
+	/**
+	 * Obtains the passphrase/password for an encrypted private key via the
+	 * {@link #getCredentialsProvider() configured CredentialsProvider}.
+	 *
+	 * @param uri
+	 *            identifying the resource to obtain a password for
+	 * @param message
+	 *            optional message text to display; may be {@code null} or empty
+	 *            if none
+	 * @return the password entered, or {@code null} if no
+	 *         {@link CredentialsProvider} is configured or none was entered
+	 * @throws java.util.concurrent.CancellationException
+	 *             if the user canceled the operation
+	 * @since 5.10
+	 */
+	protected char[] getPassword(URIish uri, String message) {
 		if (provider == null) {
 			return null;
 		}
-		List<CredentialItem> items = new ArrayList<>(2);
-		items.add(new CredentialItem.InformationalMessage(
-				format(message, uri)));
+		boolean haveMessage = !StringUtils.isEmptyOrNull(message);
+		List<CredentialItem> items = new ArrayList<>(haveMessage ? 2 : 1);
+		if (haveMessage) {
+			items.add(new CredentialItem.InformationalMessage(message));
+		}
 		CredentialItem.Password password = new CredentialItem.Password(
 				SshdText.get().keyEncryptedPrompt);
 		items.add(password);
 		try {
-			provider.get(uri, items);
+			boolean completed = provider.get(uri, items);
 			char[] pass = password.getValue();
-			if (pass == null) {
-				throw new CancellationException(
-						SshdText.get().authenticationCanceled);
+			if (!completed) {
+				cancelAuthentication();
+				return null;
 			}
-			return pass.clone();
+			return pass == null ? null : pass.clone();
 		} finally {
 			password.clear();
 		}
 	}
 
 	/**
+	 * Cancels the authentication process. Called by
+	 * {@link #getPassword(URIish, String)} when the user interaction has been
+	 * canceled. If this throws a
+	 * {@link java.util.concurrent.CancellationException}, the authentication
+	 * process is aborted; otherwise it may continue with the next configured
+	 * authentication mechanism, if any.
+	 * <p>
+	 * This default implementation always throws a
+	 * {@link java.util.concurrent.CancellationException}.
+	 * </p>
+	 *
+	 * @throws java.util.concurrent.CancellationException
+	 *             always
+	 * @since 5.10
+	 */
+	protected void cancelAuthentication() {
+		throw new AuthenticationCanceledException();
+	}
+
+	/**
 	 * Invoked to inform the password provider about the decoding result.
 	 *
 	 * @param uri
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 0c1533c..0fb0610 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
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2018, 2020 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
@@ -10,37 +10,53 @@
 package org.eclipse.jgit.transport.sshd;
 
 import static java.text.MessageFormat.format;
+import static org.apache.sshd.common.SshConstants.SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE;
 
+import java.io.Closeable;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.InterruptedIOException;
 import java.io.OutputStream;
+import java.net.URISyntaxException;
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.EnumSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Supplier;
+import java.util.regex.Pattern;
 
 import org.apache.sshd.client.SshClient;
 import org.apache.sshd.client.channel.ChannelExec;
 import org.apache.sshd.client.channel.ClientChannelEvent;
+import org.apache.sshd.client.config.hosts.HostConfigEntry;
+import org.apache.sshd.client.future.ConnectFuture;
 import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.session.forward.PortForwardingTracker;
 import org.apache.sshd.client.subsystem.sftp.SftpClient;
 import org.apache.sshd.client.subsystem.sftp.SftpClient.CloseableHandle;
 import org.apache.sshd.client.subsystem.sftp.SftpClient.CopyMode;
 import org.apache.sshd.client.subsystem.sftp.SftpClientFactory;
-import org.apache.sshd.common.session.Session;
-import org.apache.sshd.common.session.SessionListener;
+import org.apache.sshd.common.AttributeRepository;
+import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.future.CloseFuture;
+import org.apache.sshd.common.future.SshFutureListener;
 import org.apache.sshd.common.subsystem.sftp.SftpException;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.common.util.net.SshdSocketAddress;
 import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.internal.transport.sshd.JGitSshClient;
 import org.eclipse.jgit.internal.transport.sshd.SshdText;
 import org.eclipse.jgit.transport.FtpChannel;
 import org.eclipse.jgit.transport.RemoteSession;
+import org.eclipse.jgit.transport.SshConstants;
 import org.eclipse.jgit.transport.URIish;
+import org.eclipse.jgit.util.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -54,6 +70,11 @@
 	private static final Logger LOG = LoggerFactory
 			.getLogger(SshdSession.class);
 
+	private static final Pattern SHORT_SSH_FORMAT = Pattern
+			.compile("[-\\w.]+(?:@[-\\w.]+)?(?::\\d+)?"); //$NON-NLS-1$
+
+	private static final int MAX_DEPTH = 10;
+
 	private final CopyOnWriteArrayList<SessionCloseListener> listeners = new CopyOnWriteArrayList<>();
 
 	private final URIish uri;
@@ -72,32 +93,169 @@
 			client.start();
 		}
 		try {
-			String username = uri.getUser();
-			String host = uri.getHost();
-			int port = uri.getPort();
-			long t = timeout.toMillis();
-			if (t <= 0) {
-				session = client.connect(username, host, port).verify()
-						.getSession();
-			} else {
-				session = client.connect(username, host, port)
-						.verify(timeout.toMillis()).getSession();
-			}
-			session.addSessionListener(new SessionListener() {
-
-				@Override
-				public void sessionClosed(Session s) {
-					notifyCloseListeners();
-				}
-			});
-			// Authentication timeout is by default 2 minutes.
-			session.auth().verify(session.getAuthTimeout());
+			session = connect(uri, Collections.emptyList(),
+					future -> notifyCloseListeners(), timeout, MAX_DEPTH);
 		} catch (IOException e) {
 			disconnect(e);
 			throw e;
 		}
 	}
 
+	private ClientSession connect(URIish target, List<URIish> jumps,
+			SshFutureListener<CloseFuture> listener, Duration timeout,
+			int depth) throws IOException {
+		if (--depth < 0) {
+			throw new IOException(
+					format(SshdText.get().proxyJumpAbort, target));
+		}
+		HostConfigEntry hostConfig = getHostConfig(target.getUser(),
+				target.getHost(), target.getPort());
+		String host = hostConfig.getHostName();
+		int port = hostConfig.getPort();
+		List<URIish> hops = determineHops(jumps, hostConfig, target.getHost());
+		ClientSession resultSession = null;
+		ClientSession proxySession = null;
+		PortForwardingTracker portForward = null;
+		try {
+			if (!hops.isEmpty()) {
+				URIish hop = hops.remove(0);
+				if (LOG.isDebugEnabled()) {
+					LOG.debug("Connecting to jump host {}", hop); //$NON-NLS-1$
+				}
+				proxySession = connect(hop, hops, null, timeout, depth);
+			}
+			AttributeRepository context = null;
+			if (proxySession != null) {
+				SshdSocketAddress remoteAddress = new SshdSocketAddress(host,
+						port);
+				portForward = proxySession.createLocalPortForwardingTracker(
+						SshdSocketAddress.LOCALHOST_ADDRESS, remoteAddress);
+				// We must connect to the locally bound address, not the one
+				// from the host config.
+				context = AttributeRepository.ofKeyValuePair(
+						JGitSshClient.LOCAL_FORWARD_ADDRESS,
+						portForward.getBoundAddress());
+			}
+			resultSession = connect(hostConfig, context, timeout);
+			if (proxySession != null) {
+				final PortForwardingTracker tracker = portForward;
+				final ClientSession pSession = proxySession;
+				resultSession.addCloseFutureListener(future -> {
+					IoUtils.closeQuietly(tracker);
+					String sessionName = pSession.toString();
+					try {
+						pSession.close();
+					} catch (IOException e) {
+						LOG.error(format(
+								SshdText.get().sshProxySessionCloseFailed,
+								sessionName), e);
+					}
+				});
+				portForward = null;
+				proxySession = null;
+			}
+			if (listener != null) {
+				resultSession.addCloseFutureListener(listener);
+			}
+			// Authentication timeout is by default 2 minutes.
+			resultSession.auth().verify(resultSession.getAuthTimeout());
+			return resultSession;
+		} catch (IOException e) {
+			close(portForward, e);
+			close(proxySession, e);
+			close(resultSession, e);
+			if (e instanceof SshException && ((SshException) e)
+					.getDisconnectCode() == SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE) {
+				// Ensure the user gets to know on which URI the authentication
+				// was denied.
+				throw new TransportException(target,
+						format(SshdText.get().loginDenied, host,
+								Integer.toString(port)),
+						e);
+			}
+			throw e;
+		}
+	}
+
+	private ClientSession connect(HostConfigEntry config,
+			AttributeRepository context, Duration timeout)
+			throws IOException {
+		ConnectFuture connected = client.connect(config, context, null);
+		long timeoutMillis = timeout.toMillis();
+		if (timeoutMillis <= 0) {
+			connected = connected.verify();
+		} else {
+			connected = connected.verify(timeoutMillis);
+		}
+		return connected.getSession();
+	}
+
+	private void close(Closeable toClose, Throwable error) {
+		if (toClose != null) {
+			try {
+				toClose.close();
+			} catch (IOException e) {
+				error.addSuppressed(e);
+			}
+		}
+	}
+
+	private HostConfigEntry getHostConfig(String username, String host,
+			int port) throws IOException {
+		HostConfigEntry entry = client.getHostConfigEntryResolver()
+				.resolveEffectiveHost(host, port, null, username, null);
+		if (entry == null) {
+			if (SshdSocketAddress.isIPv6Address(host)) {
+				return new HostConfigEntry("", host, port, username); //$NON-NLS-1$
+			}
+			return new HostConfigEntry(host, host, port, username);
+		}
+		return entry;
+	}
+
+	private List<URIish> determineHops(List<URIish> currentHops,
+			HostConfigEntry hostConfig, String host) throws IOException {
+		if (currentHops.isEmpty()) {
+			String jumpHosts = hostConfig.getProperty(SshConstants.PROXY_JUMP);
+			if (!StringUtils.isEmptyOrNull(jumpHosts)) {
+				try {
+					return parseProxyJump(jumpHosts);
+				} catch (URISyntaxException e) {
+					throw new IOException(
+							format(SshdText.get().configInvalidProxyJump, host,
+									jumpHosts),
+							e);
+				}
+			}
+		}
+		return currentHops;
+	}
+
+	private List<URIish> parseProxyJump(String proxyJump)
+			throws URISyntaxException {
+		String[] hops = proxyJump.split(","); //$NON-NLS-1$
+		List<URIish> result = new LinkedList<>();
+		for (String hop : hops) {
+			// There shouldn't be any whitespace, but let's be lenient
+			hop = hop.trim();
+			if (SHORT_SSH_FORMAT.matcher(hop).matches()) {
+				// URIish doesn't understand the short SSH format
+				// user@host:port, only user@host:path
+				hop = SshConstants.SSH_SCHEME + "://" + hop; //$NON-NLS-1$
+			}
+			URIish to = new URIish(hop);
+			if (!SshConstants.SSH_SCHEME.equalsIgnoreCase(to.getScheme())) {
+				throw new URISyntaxException(hop,
+						SshdText.get().configProxyJumpNotSsh);
+			} else if (!StringUtils.isEmptyOrNull(to.getPath())) {
+				throw new URISyntaxException(hop,
+						SshdText.get().configProxyJumpWithPath);
+			}
+			result.add(to);
+		}
+		return result;
+	}
+
 	/**
 	 * Adds a {@link SessionCloseListener} to this session. Has no effect if the
 	 * given {@code listener} is already registered with this session.
@@ -134,28 +292,23 @@
 	public Process exec(String commandName, int timeout) throws IOException {
 		@SuppressWarnings("resource")
 		ChannelExec exec = session.createExecChannel(commandName);
-		long timeoutMillis = TimeUnit.SECONDS.toMillis(timeout);
-		try {
-			if (timeout <= 0) {
+		if (timeout <= 0) {
+			try {
 				exec.open().verify();
-			} else {
-				long start = System.nanoTime();
-				exec.open().verify(timeoutMillis);
-				timeoutMillis -= TimeUnit.NANOSECONDS
-						.toMillis(System.nanoTime() - start);
+			} catch (IOException | RuntimeException e) {
+				exec.close(true);
+				throw e;
 			}
-		} catch (IOException | RuntimeException e) {
-			exec.close(true);
-			throw e;
+		} else {
+			try {
+				exec.open().verify(TimeUnit.SECONDS.toMillis(timeout));
+			} catch (IOException | RuntimeException e) {
+				exec.close(true);
+				throw new IOException(format(SshdText.get().sshCommandTimeout,
+						commandName, Integer.valueOf(timeout)), e);
+			}
 		}
-		if (timeout > 0 && timeoutMillis <= 0) {
-			// We have used up the whole timeout for opening the channel
-			exec.close(true);
-			throw new InterruptedIOException(
-					format(SshdText.get().sshCommandTimeout, commandName,
-							Integer.valueOf(timeout)));
-		}
-		return new SshdExecProcess(exec, commandName, timeoutMillis);
+		return new SshdExecProcess(exec, commandName);
 	}
 
 	/**
@@ -195,14 +348,10 @@
 
 		private final ChannelExec channel;
 
-		private final long timeoutMillis;
-
 		private final String commandName;
 
-		public SshdExecProcess(ChannelExec channel, String commandName,
-				long timeoutMillis) {
+		public SshdExecProcess(ChannelExec channel, String commandName) {
 			this.channel = channel;
-			this.timeoutMillis = timeoutMillis > 0 ? timeoutMillis : -1L;
 			this.commandName = commandName;
 		}
 
@@ -223,7 +372,7 @@
 
 		@Override
 		public int waitFor() throws InterruptedException {
-			if (waitFor(timeoutMillis, TimeUnit.MILLISECONDS)) {
+			if (waitFor(-1L, TimeUnit.MILLISECONDS)) {
 				return exitValue();
 			}
 			return -1;
@@ -252,7 +401,7 @@
 		@Override
 		public void destroy() {
 			if (channel.isOpen()) {
-				channel.close(true);
+				channel.close(false);
 			}
 		}
 	}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java
index bb4e49b..df0e1d2 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018, 2019 Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2018, 2020 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
@@ -25,6 +25,7 @@
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
 import org.apache.sshd.client.ClientBuilder;
@@ -33,6 +34,7 @@
 import org.apache.sshd.client.auth.keyboard.UserAuthKeyboardInteractiveFactory;
 import org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyFactory;
 import org.apache.sshd.client.config.hosts.HostConfigEntryResolver;
+import org.apache.sshd.common.SshException;
 import org.apache.sshd.common.compression.BuiltinCompressions;
 import org.apache.sshd.common.config.keys.FilePasswordProvider;
 import org.apache.sshd.common.config.keys.loader.openssh.kdf.BCryptKdfOptions;
@@ -40,6 +42,7 @@
 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.AuthenticationCanceledException;
 import org.eclipse.jgit.internal.transport.sshd.CachingKeyPairProvider;
 import org.eclipse.jgit.internal.transport.sshd.GssApiWithMicAuthFactory;
 import org.eclipse.jgit.internal.transport.sshd.JGitPasswordAuthFactory;
@@ -194,12 +197,11 @@
 						home, sshDir);
 				KeyIdentityProvider defaultKeysProvider = toKeyIdentityProvider(
 						getDefaultKeys(sshDir));
-				KeyPasswordProvider passphrases = createKeyPasswordProvider(
-						credentialsProvider);
 				SshClient client = ClientBuilder.builder()
 						.factory(JGitSshClient::new)
-						.filePasswordProvider(
-								createFilePasswordProvider(passphrases))
+						.filePasswordProvider(createFilePasswordProvider(
+								() -> createKeyPasswordProvider(
+										credentialsProvider)))
 						.hostConfigEntryResolver(configFile)
 						.serverKeyVerifier(new JGitServerKeyVerifier(
 								getServerKeyDatabase(home, sshDir)))
@@ -230,7 +232,16 @@
 			return session;
 		} catch (Exception e) {
 			unregister(session);
-			throw new TransportException(uri, e.getMessage(), e);
+			if (e instanceof TransportException) {
+				throw (TransportException) e;
+			}
+			Throwable cause = e;
+			if (e instanceof SshException && e
+					.getCause() instanceof AuthenticationCanceledException) {
+				// Results in a nicer error message
+				cause = e.getCause();
+			}
+			throw new TransportException(uri, cause.getMessage(), cause);
 		}
 	}
 
@@ -536,14 +547,14 @@
 	/**
 	 * Creates a {@link FilePasswordProvider} for a new session.
 	 *
-	 * @param provider
-	 *            the {@link KeyPasswordProvider} to delegate to
+	 * @param providerFactory
+	 *            providing the {@link KeyPasswordProvider} to delegate to
 	 * @return a new {@link FilePasswordProvider}
 	 */
 	@NonNull
 	private FilePasswordProvider createFilePasswordProvider(
-			KeyPasswordProvider provider) {
-		return new PasswordProviderWrapper(provider);
+			Supplier<KeyPasswordProvider> providerFactory) {
+		return new PasswordProviderWrapper(providerFactory);
 	}
 
 	/**
diff --git a/org.eclipse.jgit.ssh.jsch/pom.xml b/org.eclipse.jgit.ssh.jsch/pom.xml
index 2b0f1ee..95a20b5 100644
--- a/org.eclipse.jgit.ssh.jsch/pom.xml
+++ b/org.eclipse.jgit.ssh.jsch/pom.xml
@@ -123,7 +123,6 @@
         </configuration>
       </plugin>
 
-      <!-- No previous version to compare to
       <plugin>
           <groupId>com.github.siom79.japicmp</groupId>
           <artifactId>japicmp-maven-plugin</artifactId>
@@ -165,13 +164,11 @@
           </execution>
         </executions>
       </plugin>
-      -->
     </plugins>
   </build>
 
   <reporting>
     <plugins>
-      <!-- No previous version to compare to
       <plugin>
           <groupId>com.github.siom79.japicmp</groupId>
           <artifactId>japicmp-maven-plugin</artifactId>
@@ -212,7 +209,6 @@
               <skip>false</skip>
           </configuration>
       </plugin>
-      -->
     </plugins>
   </reporting>
 </project>
diff --git a/org.eclipse.jgit.ssh.jsch/src/org/eclipse/jgit/transport/JschSession.java b/org.eclipse.jgit.ssh.jsch/src/org/eclipse/jgit/transport/JschSession.java
index 300e03d..858bdf3 100644
--- a/org.eclipse.jgit.ssh.jsch/src/org/eclipse/jgit/transport/JschSession.java
+++ b/org.eclipse.jgit.ssh.jsch/src/org/eclipse/jgit/transport/JschSession.java
@@ -198,7 +198,7 @@
 		@Override
 		public int exitValue() {
 			if (isRunning())
-				throw new IllegalStateException();
+				throw new IllegalThreadStateException();
 			return channel.getExitStatus();
 		}
 
diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
index c4c03b7..d4cc7b0 100644
--- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
@@ -48,6 +48,7 @@
  org.eclipse.jgit.lfs;version="[6.0.0,6.1.0)",
  org.eclipse.jgit.lib;version="[6.0.0,6.1.0)",
  org.eclipse.jgit.lib.internal;version="[6.0.0,6.1.0)",
+ org.eclipse.jgit.logging;version="[6.0.0,6.1.0)",
  org.eclipse.jgit.merge;version="[6.0.0,6.1.0)",
  org.eclipse.jgit.nls;version="[6.0.0,6.1.0)",
  org.eclipse.jgit.notes;version="[6.0.0,6.1.0)",
diff --git a/org.eclipse.jgit.test/build.properties b/org.eclipse.jgit.test/build.properties
index 7dc26c0..b527a74 100644
--- a/org.eclipse.jgit.test/build.properties
+++ b/org.eclipse.jgit.test/build.properties
@@ -8,4 +8,4 @@
                bin-tst/,\
                bin/
 additional.bundles = org.apache.log4j,\
-                     org.slf4j.impl.log4j12
+                     org.slf4j.binding.log4j12
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/gitgit.index.v4 b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/gitgit.index.v4
new file mode 100644
index 0000000..de49e56
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/gitgit.index.v4
Binary files differ
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-bad-fanout-table.idx b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-bad-fanout-table.idx
new file mode 100644
index 0000000..2029915
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-bad-fanout-table.idx
Binary files differ
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-bad-fanout-table.idxV2 b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-bad-fanout-table.idxV2
new file mode 100644
index 0000000..28bd4a7
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-bad-fanout-table.idxV2
Binary files differ
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java
index b737bbe..de25870 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java
@@ -92,7 +92,6 @@
 		command.setURI(fileUri());
 		Git git2 = command.call();
 		addRepoToClose(git2.getRepository());
-		assertNotNull(git2);
 		ObjectId id = git2.getRepository().resolve("tag-for-blob");
 		assertNotNull(id);
 		assertEquals(git2.getRepository().getFullBranch(), "refs/heads/test");
@@ -277,8 +276,7 @@
 		Git git2 = command.call();
 		addRepoToClose(git2.getRepository());
 
-		assertNotNull(git2);
-		assertEquals(git2.getRepository().getFullBranch(), "refs/heads/master");
+		assertEquals("refs/heads/master", git2.getRepository().getFullBranch());
 		assertEquals(
 				"refs/heads/master, refs/remotes/origin/master, refs/remotes/origin/test",
 				allRefNames(git2.branchList().setListMode(ListMode.ALL).call()));
@@ -293,7 +291,6 @@
 		git2 = command.call();
 		addRepoToClose(git2.getRepository());
 
-		assertNotNull(git2);
 		assertEquals(git2.getRepository().getFullBranch(), "refs/heads/master");
 		assertEquals("refs/remotes/origin/master, refs/remotes/origin/test",
 				allRefNames(git2.branchList().setListMode(ListMode.ALL).call()));
@@ -308,8 +305,7 @@
 		git2 = command.call();
 		addRepoToClose(git2.getRepository());
 
-		assertNotNull(git2);
-		assertEquals(git2.getRepository().getFullBranch(), "refs/heads/master");
+		assertEquals("refs/heads/master", git2.getRepository().getFullBranch());
 		assertEquals("refs/heads/master, refs/heads/test", allRefNames(git2
 				.branchList().setListMode(ListMode.ALL).call()));
 	}
@@ -324,7 +320,6 @@
 		Git git2 = command.call();
 		addRepoToClose(git2.getRepository());
 
-		assertNotNull(git2);
 		assertEquals("refs/heads/test", git2.getRepository().getFullBranch());
 	}
 
@@ -338,7 +333,6 @@
 		Git git2 = command.call();
 		addRepoToClose(git2.getRepository());
 
-		assertNotNull(git2);
 		ObjectId taggedCommit = db.resolve("tag-initial^{commit}");
 		assertEquals(taggedCommit.name(), git2
 				.getRepository().getFullBranch());
@@ -355,10 +349,9 @@
 		command.setURI(fileUri());
 		Git git2 = command.call();
 		addRepoToClose(git2.getRepository());
-		assertNotNull(git2);
 		assertNull(git2.getRepository().resolve("tag-for-blob"));
 		assertNotNull(git2.getRepository().resolve("tag-initial"));
-		assertEquals(git2.getRepository().getFullBranch(), "refs/heads/master");
+		assertEquals("refs/heads/master", git2.getRepository().getFullBranch());
 		assertEquals("refs/remotes/origin/master", allRefNames(git2
 				.branchList().setListMode(ListMode.REMOTE).call()));
 		RemoteConfig cfg = new RemoteConfig(git2.getRepository().getConfig(),
@@ -383,10 +376,9 @@
 		command.setBare(true);
 		Git git2 = command.call();
 		addRepoToClose(git2.getRepository());
-		assertNotNull(git2);
 		assertNull(git2.getRepository().resolve("tag-for-blob"));
 		assertNotNull(git2.getRepository().resolve("tag-initial"));
-		assertEquals(git2.getRepository().getFullBranch(), "refs/heads/master");
+		assertEquals("refs/heads/master", git2.getRepository().getFullBranch());
 		assertEquals("refs/heads/master", allRefNames(git2.branchList()
 				.setListMode(ListMode.ALL).call()));
 		RemoteConfig cfg = new RemoteConfig(git2.getRepository().getConfig(),
@@ -409,11 +401,10 @@
 		command.setURI(fileUri());
 		Git git2 = command.call();
 		addRepoToClose(git2.getRepository());
-		assertNotNull(git2);
 		assertTrue(git2.getRepository().isBare());
 		assertNotNull(git2.getRepository().resolve("tag-for-blob"));
 		assertNotNull(git2.getRepository().resolve("tag-initial"));
-		assertEquals(git2.getRepository().getFullBranch(), "refs/heads/master");
+		assertEquals("refs/heads/master", git2.getRepository().getFullBranch());
 		assertEquals("refs/heads/master, refs/heads/test", allRefNames(
 				git2.branchList().setListMode(ListMode.ALL).call()));
 		assertNotNull(git2.getRepository().exactRef("refs/meta/foo/bar"));
@@ -436,7 +427,6 @@
 		command.setURI(fileUri());
 		Git git2 = command.call();
 		addRepoToClose(git2.getRepository());
-		assertNotNull(git2);
 		assertNull(git2.getRepository().resolve("tag-for-blob"));
 		assertNull(git2.getRepository().resolve("refs/heads/master"));
 		assertNotNull(git2.getRepository().resolve("tag-initial"));
@@ -464,8 +454,7 @@
 		command.setURI(fileUri());
 		Git git2 = command.call();
 		addRepoToClose(git2.getRepository());
-		assertNotNull(git2);
-		assertEquals(git2.getRepository().getFullBranch(), "refs/heads/test");
+		assertEquals("refs/heads/test", git2.getRepository().getFullBranch());
 		// Expect both remote branches to exist; setCloneAllBranches(true)
 		// should override any setBranchesToClone().
 		assertNotNull(
@@ -492,8 +481,7 @@
 		command.setURI(fileUri());
 		Git git2 = command.call();
 		addRepoToClose(git2.getRepository());
-		assertNotNull(git2);
-		assertEquals(git2.getRepository().getFullBranch(), "refs/heads/test");
+		assertEquals("refs/heads/test", git2.getRepository().getFullBranch());
 		// Expect only the test branch; allBranches was re-set to false
 		assertNull(git2.getRepository().resolve("refs/remotes/origin/master"));
 		assertNotNull(git2.getRepository().resolve("refs/remotes/origin/test"));
@@ -525,7 +513,6 @@
 		command.setURI(fileUri());
 		Git git2 = command.call();
 		addRepoToClose(git2.getRepository());
-		assertNotNull(git2);
 		// clone again
 		command = Git.cloneRepository();
 		command.setDirectory(directory);
@@ -551,7 +538,6 @@
 		clone.setURI(fileUri());
 		Git git2 = clone.call();
 		addRepoToClose(git2.getRepository());
-		assertNotNull(git2);
 
 		assertEquals(Constants.MASTER, git2.getRepository().getBranch());
 	}
@@ -595,7 +581,6 @@
 		clone.setURI(fileUri());
 		Git git2 = clone.call();
 		addRepoToClose(git2.getRepository());
-		assertNotNull(git2);
 
 		assertEquals(Constants.MASTER, git2.getRepository().getBranch());
 		assertTrue(new File(git2.getRepository().getWorkTree(), path
@@ -683,7 +668,6 @@
 		clone.setURI(git.getRepository().getDirectory().toURI().toString());
 		Git git2 = clone.call();
 		addRepoToClose(git2.getRepository());
-		assertNotNull(git2);
 
 		assertEquals(Constants.MASTER, git2.getRepository().getBranch());
 		assertTrue(new File(git2.getRepository().getWorkTree(), path
@@ -813,7 +797,6 @@
 		command.setNoTags();
 		Git git2 = command.call();
 		addRepoToClose(git2.getRepository());
-		assertNotNull(git2);
 		assertNotNull(git2.getRepository().resolve("refs/heads/test"));
 		assertNull(git2.getRepository().resolve("tag-initial"));
 		assertNull(git2.getRepository().resolve("tag-for-blob"));
@@ -833,13 +816,41 @@
 		command.setTagOption(TagOpt.FETCH_TAGS);
 		Git git2 = command.call();
 		addRepoToClose(git2.getRepository());
-		assertNotNull(git2);
 		assertNull(git2.getRepository().resolve("refs/heads/test"));
 		assertNotNull(git2.getRepository().resolve("tag-initial"));
 		assertNotNull(git2.getRepository().resolve("tag-for-blob"));
 		assertTagOption(git2.getRepository(), TagOpt.FETCH_TAGS);
 	}
 
+	@Test
+	public void testCloneWithHeadSymRefIsMasterCopy() throws IOException, GitAPIException {
+		// create a branch with the same head as master and switch to it
+		git.checkout().setStartPoint("master").setCreateBranch(true).setName("master-copy").call();
+
+		// when we clone the HEAD symref->master-copy means we start on master-copy and not master
+		File directory = createTempDirectory("testCloneRepositorySymRef_master-copy");
+		CloneCommand command = Git.cloneRepository();
+		command.setDirectory(directory);
+		command.setURI(fileUri());
+		Git git2 = command.call();
+		addRepoToClose(git2.getRepository());
+		assertEquals("refs/heads/master-copy", git2.getRepository().getFullBranch());
+	}
+
+	@Test
+	public void testCloneWithHeadSymRefIsNonMasterCopy() throws IOException, GitAPIException {
+		// create a branch with the same head as test and switch to it
+		git.checkout().setStartPoint("test").setCreateBranch(true).setName("test-copy").call();
+
+		File directory = createTempDirectory("testCloneRepositorySymRef_test-copy");
+		CloneCommand command = Git.cloneRepository();
+		command.setDirectory(directory);
+		command.setURI(fileUri());
+		Git git2 = command.call();
+		addRepoToClose(git2.getRepository());
+		assertEquals("refs/heads/test-copy", git2.getRepository().getFullBranch());
+	}
+
 	private void assertTagOption(Repository repo, TagOpt expectedTagOption)
 			throws URISyntaxException {
 		RemoteConfig remoteConfig = new RemoteConfig(
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 7ba7c8d..b460e3f 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
@@ -310,7 +310,7 @@
 			assertEquals(
 					"2 commits for describe commit increment expected since lightweight tag: c4 and c3",
 					"t2-2-g119892b", describe(c4)); // 2 commits: c4 and c3
-		} else if (!useAnnotatedTags && !describeUseAllTags) {
+		} else if (!useAnnotatedTags) {
 			assertEquals("no matching commits expected", null, describe(c4));
 		} else {
 			assertEquals(
@@ -405,6 +405,46 @@
 		}
 	}
 
+	@Test
+	public void testDescribeUseAllRefsMaster() throws Exception {
+		final ObjectId c1 = modify("aaa");
+		tag("t1");
+
+		if (useAnnotatedTags || describeUseAllTags) {
+			assertEquals("t1", describe(c1));
+		} else {
+			assertEquals(null, describe(c1));
+		}
+		assertEquals("heads/master", describeAll(c1));
+	}
+
+	/**
+	 * Branch off from master and then tag
+	 *
+	 * <pre>
+	 * c1 -+ -> c2
+	 *     |
+	 *     +-> t1
+	 * </pre>
+	 * @throws Exception
+	 * */
+	@Test
+	public void testDescribeUseAllRefsBranch() throws Exception {
+		final ObjectId c1 = modify("aaa");
+		modify("bbb");
+
+		branch("b", c1);
+		final ObjectId c3 = modify("ccc");
+		tag("t1");
+
+		if (!useAnnotatedTags && !describeUseAllTags) {
+			assertEquals(null, describe(c3));
+		} else {
+			assertEquals("t1", describe(c3));
+		}
+		assertEquals("heads/b", describeAll(c3));
+	}
+
 	private ObjectId merge(ObjectId c2) throws GitAPIException {
 		return git.merge().include(c2).call().getNewHead();
 	}
@@ -444,6 +484,11 @@
 		return describe(c1, false, false);
 	}
 
+	private String describeAll(ObjectId c1) throws GitAPIException, IOException {
+		return git.describe().setTarget(c1).setTags(describeUseAllTags)
+				.setLong(false).setAlways(false).setAll(true).call();
+	}
+
 	private String describe(ObjectId c1, String... patterns) throws Exception {
 		return git.describe().setTarget(c1).setTags(describeUseAllTags)
 				.setMatch(patterns).call();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolStreamTypeUtilTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolStreamTypeUtilTest.java
index 1e3a39a..2a553ce 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolStreamTypeUtilTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolStreamTypeUtilTest.java
@@ -1,43 +1,11 @@
 /*
- * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com>
+ * Copyright (C) 2015, 2020 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;
@@ -90,16 +58,20 @@
 		testCheckout(TEXT_LF, AUTO_LF, "\r", "\r");
 		testCheckout(TEXT_LF, AUTO_LF, "\n", "\n");
 
-		testCheckout(TEXT_LF, AUTO_LF, "\r\n", "\n");
+		testCheckout(TEXT_LF, null, "\r\n", "\n");
+		testCheckout(null, AUTO_LF, "\r\n", "\r\n");
 		testCheckout(TEXT_LF, AUTO_LF, "\n\r", "\n\r");
 
-		testCheckout(TEXT_LF, AUTO_LF, "\n\r\n", "\n\n");
-		testCheckout(TEXT_LF, AUTO_LF, "\r\n\r", "\n\r");
+		testCheckout(TEXT_LF, null, "\n\r\n", "\n\n");
+		testCheckout(null, AUTO_LF, "\n\r\n", "\n\r\n");
+		testCheckout(TEXT_LF, null, "\r\n\r", "\n\r");
+		testCheckout(null, AUTO_LF, "\r\n\r", "\r\n\r");
 
 		testCheckout(TEXT_LF, AUTO_LF, "a\nb\n", "a\nb\n");
 		testCheckout(TEXT_LF, AUTO_LF, "a\rb\r", "a\rb\r");
 		testCheckout(TEXT_LF, AUTO_LF, "a\n\rb\n\r", "a\n\rb\n\r");
-		testCheckout(TEXT_LF, AUTO_LF, "a\r\nb\r\n", "a\nb\n");
+		testCheckout(TEXT_LF, null, "a\r\nb\r\n", "a\nb\n");
+		testCheckout(null, AUTO_LF, "a\r\nb\r\n", "a\r\nb\r\n");
 	}
 
 	@Test
@@ -153,45 +125,49 @@
 		byte[] outputBytes = output.getBytes(UTF_8);
 		byte[] expectedConversionBytes = expectedConversion.getBytes(UTF_8);
 
-		// test using output text and assuming it was declared TEXT
-		b = new ByteArrayOutputStream();
-		try (OutputStream out = EolStreamTypeUtil.wrapOutputStream(b,
-				streamTypeText)) {
-			out.write(outputBytes);
+		if (streamTypeText != null) {
+			// test using output text and assuming it was declared TEXT
+			b = new ByteArrayOutputStream();
+			try (OutputStream out = EolStreamTypeUtil.wrapOutputStream(b,
+					streamTypeText)) {
+				out.write(outputBytes);
+			}
+			assertArrayEquals(expectedConversionBytes, b.toByteArray());
 		}
-		assertArrayEquals(expectedConversionBytes, b.toByteArray());
-
-		// test using ouput text and assuming it was declared AUTO, using binary
-		// detection
-		b = new ByteArrayOutputStream();
-		try (OutputStream out = EolStreamTypeUtil.wrapOutputStream(b,
-				streamTypeWithBinaryCheck)) {
-			out.write(outputBytes);
+		if (streamTypeWithBinaryCheck != null) {
+			// test using output text and assuming it was declared AUTO, using
+			// binary detection
+			b = new ByteArrayOutputStream();
+			try (OutputStream out = EolStreamTypeUtil.wrapOutputStream(b,
+					streamTypeWithBinaryCheck)) {
+				out.write(outputBytes);
+			}
+			assertArrayEquals(expectedConversionBytes, b.toByteArray());
 		}
-		assertArrayEquals(expectedConversionBytes, b.toByteArray());
-
 		// now pollute output text with some binary bytes
 		outputBytes = extendWithBinaryData(outputBytes);
 		expectedConversionBytes = extendWithBinaryData(expectedConversionBytes);
 
-		// again, test using output text and assuming it was declared TEXT
-		b = new ByteArrayOutputStream();
-		try (OutputStream out = EolStreamTypeUtil.wrapOutputStream(b,
-				streamTypeText)) {
-			out.write(outputBytes);
+		if (streamTypeText != null) {
+			// again, test using output text and assuming it was declared TEXT
+			b = new ByteArrayOutputStream();
+			try (OutputStream out = EolStreamTypeUtil.wrapOutputStream(b,
+					streamTypeText)) {
+				out.write(outputBytes);
+			}
+			assertArrayEquals(expectedConversionBytes, b.toByteArray());
 		}
-		assertArrayEquals(expectedConversionBytes, b.toByteArray());
-
-		// again, test using ouput text and assuming it was declared AUTO, using
-		// binary
-		// detection
-		b = new ByteArrayOutputStream();
-		try (OutputStream out = EolStreamTypeUtil.wrapOutputStream(b,
-				streamTypeWithBinaryCheck)) {
-			out.write(outputBytes);
+		if (streamTypeWithBinaryCheck != null) {
+			// again, test using output text and assuming it was declared AUTO,
+			// using binary detection
+			b = new ByteArrayOutputStream();
+			try (OutputStream out = EolStreamTypeUtil.wrapOutputStream(b,
+					streamTypeWithBinaryCheck)) {
+				out.write(outputBytes);
+			}
+			// expect no conversion
+			assertArrayEquals(outputBytes, b.toByteArray());
 		}
-		// expect no conversion
-		assertArrayEquals(outputBytes, b.toByteArray());
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LsRemoteCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LsRemoteCommandTest.java
index 00f84e9..12ec2aa 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LsRemoteCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LsRemoteCommandTest.java
@@ -11,9 +11,11 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 import java.io.File;
 import java.util.Collection;
+import java.util.Optional;
 
 import org.eclipse.jgit.junit.RepositoryTestCase;
 import org.eclipse.jgit.lib.Constants;
@@ -34,7 +36,7 @@
 		git.add().addFilepattern("Test.txt").call();
 		git.commit().setMessage("Initial commit").call();
 
-		// create a master branch and switch to it
+		// create a test branch and switch to it
 		git.branchCreate().setName("test").call();
 		RefUpdate rup = db.updateRef(Constants.HEAD);
 		rup.link("refs/heads/test");
@@ -104,6 +106,28 @@
 		assertEquals(2, refs.size());
 	}
 
+	@Test
+	public void testLsRemoteWithSymRefs() throws Exception {
+		File directory = createTempDirectory("testRepository");
+		CloneCommand command = Git.cloneRepository();
+		command.setDirectory(directory);
+		command.setURI(fileUri());
+		command.setCloneAllBranches(true);
+		Git git2 = command.call();
+		addRepoToClose(git2.getRepository());
+
+
+		LsRemoteCommand lsRemoteCommand = git2.lsRemote();
+		Collection<Ref> refs = lsRemoteCommand.call();
+		assertNotNull(refs);
+		assertEquals(6, refs.size());
+
+		Optional<Ref> headRef = refs.stream().filter(ref -> ref.getName().equals(Constants.HEAD)).findFirst();
+		assertTrue("expected a HEAD Ref", headRef.isPresent());
+		assertTrue("expected HEAD Ref to be a Symbolic", headRef.get().isSymbolic());
+		assertEquals("refs/heads/test", headRef.get().getTarget().getName());
+	}
+
 	private String fileUri() {
 		return "file://" + git.getRepository().getWorkTree().getAbsolutePath();
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PathCheckoutCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PathCheckoutCommandTest.java
index e0a1c1d..f52b715 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PathCheckoutCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PathCheckoutCommandTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011, Kevin Sawicki <kevin@github.com> and others
+ * Copyright (C) 2011, 2020 Kevin Sawicki <kevin@github.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
@@ -24,6 +24,7 @@
 import org.eclipse.jgit.errors.NoWorkTreeException;
 import org.eclipse.jgit.junit.RepositoryTestCase;
 import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.RepositoryState;
 import org.eclipse.jgit.lib.StoredConfig;
@@ -310,6 +311,16 @@
 	}
 
 	@Test
+	public void testCheckoutFileWithConflict() throws Exception {
+		setupConflictingState();
+		assertEquals('[' + FILE1 + ']',
+				git.status().call().getConflicting().toString());
+		git.checkout().setStartPoint(Constants.HEAD).addPath(FILE1).call();
+		assertEquals("3", read(FILE1));
+		assertTrue(git.status().call().isClean());
+	}
+
+	@Test
 	public void testCheckoutOursWhenNoBase() throws Exception {
 		String file = "added.txt";
 
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 7e0de82..5311edb 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
@@ -160,4 +160,25 @@
 			assertTrue("Expected no differences", status.isClean());
 		}
 	}
+
+	@Test
+	public void testFolderPrefix() throws Exception {
+		// "audio" is a prefix of "audio-new" and "audio.new".
+		try (Git git = new Git(db)) {
+			// Order here is the git order, but that doesn't really matter.
+			// They are processed by StatusCommand in this order even if written
+			// in a different order. Bug 566799 would, when having processed
+			// audio/foo, remove previously recorded untracked folders that have
+			// "audio" as a prefix: audio-new and audio.new.
+			writeTrashFile("audi", "foo", "foo");
+			writeTrashFile("audio-new", "foo", "foo");
+			writeTrashFile("audio.new", "foo", "foo");
+			writeTrashFile("audio", "foo", "foo");
+			writeTrashFile("audio_new", "foo", "foo");
+			Status stat = git.status().call();
+			assertEquals(Sets.of("audi", "audio-new", "audio.new", "audio",
+					"audio_new"), stat.getUntrackedFolders());
+		}
+	}
+
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java
index 3d46a47..62824d3 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010, 2013 Google Inc. and others
+ * Copyright (C) 2010, 2020 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
@@ -11,12 +11,16 @@
 package org.eclipse.jgit.diff;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 
 import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.Status;
 import org.eclipse.jgit.diff.DiffEntry.ChangeType;
 import org.eclipse.jgit.dircache.DirCacheIterator;
 import org.eclipse.jgit.junit.RepositoryTestCase;
@@ -74,6 +78,17 @@
 	}
 
 	@Test
+	public void testDefaultRenameDetectorSettings() throws Exception {
+		RenameDetector rd = df.getRenameDetector();
+		assertNull(rd);
+		df.setDetectRenames(true);
+		rd = df.getRenameDetector();
+		assertNotNull(rd);
+		assertEquals(400, rd.getRenameLimit());
+		assertEquals(60, rd.getRenameScore());
+	}
+
+	@Test
 	public void testCreateFileHeader_Add() throws Exception {
 		ObjectId adId = blob("a\nd\n");
 		DiffEntry ent = DiffEntry.add("FOO", adId);
@@ -455,6 +470,58 @@
 	}
 
 	@Test
+	public void testTrackedFileInIgnoredFolderUnchanged()
+			throws Exception {
+		commitFile("empty/empty/foo", "", "master");
+		commitFile(".gitignore", "empty/*", "master");
+		try (Git git = new Git(db)) {
+			Status status = git.status().call();
+			assertTrue(status.isClean());
+		}
+		try (ByteArrayOutputStream os = new ByteArrayOutputStream();
+				DiffFormatter dfmt = new DiffFormatter(os)) {
+			dfmt.setRepository(db);
+			dfmt.format(new DirCacheIterator(db.readDirCache()),
+					new FileTreeIterator(db));
+			dfmt.flush();
+
+			String actual = os.toString("UTF-8");
+
+			assertEquals("", actual);
+		}
+	}
+
+	@Test
+	public void testTrackedFileInIgnoredFolderChanged()
+			throws Exception {
+		String expectedDiff = "diff --git a/empty/empty/foo b/empty/empty/foo\n"
+				+ "index e69de29..5ea2ed4 100644\n" //
+				+ "--- a/empty/empty/foo\n" //
+				+ "+++ b/empty/empty/foo\n" //
+				+ "@@ -0,0 +1 @@\n" //
+				+ "+changed\n";
+
+		commitFile("empty/empty/foo", "", "master");
+		commitFile(".gitignore", "empty/*", "master");
+		try (Git git = new Git(db)) {
+			Status status = git.status().call();
+			assertTrue(status.isClean());
+		}
+		try (ByteArrayOutputStream os = new ByteArrayOutputStream();
+				DiffFormatter dfmt = new DiffFormatter(os)) {
+			writeTrashFile("empty/empty/foo", "changed\n");
+			dfmt.setRepository(db);
+			dfmt.format(new DirCacheIterator(db.readDirCache()),
+					new FileTreeIterator(db));
+			dfmt.flush();
+
+			String actual = os.toString("UTF-8");
+
+			assertEquals(expectedDiff, actual);
+		}
+	}
+
+	@Test
 	public void testDiffAutoCrlfSmallFile() throws Exception {
 		String content = "01234\r\n01234\r\n01234\r\n";
 		String expectedDiff = "diff --git a/test.txt b/test.txt\n"
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheAfterCloneTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheAfterCloneTest.java
new file mode 100644
index 0000000..f210760
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheAfterCloneTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2020 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.dircache;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.util.EnumSet;
+import java.util.Set;
+
+import org.eclipse.jgit.api.CloneCommand;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.ResetCommand.ResetType;
+import org.eclipse.jgit.dircache.DirCache.DirCacheVersion;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.util.FileUtils;
+import org.eclipse.jgit.util.SystemReader;
+import org.junit.Test;
+
+/**
+ * Tests for initial DirCache version after a clone or after a mixed or hard
+ * reset.
+ */
+public class DirCacheAfterCloneTest extends RepositoryTestCase {
+
+	@Override
+	public void setUp() throws Exception {
+		super.setUp();
+		try (Git git = new Git(db)) {
+			writeTrashFile("Test.txt", "Hello world");
+			git.add().addFilepattern("Test.txt").call();
+			git.commit().setMessage("Initial commit").call();
+		}
+	}
+
+	private DirCacheVersion cloneAndCheck(Set<DirCacheVersion> expected)
+			throws Exception {
+		File directory = createTempDirectory("testCloneRepository");
+		CloneCommand command = Git.cloneRepository();
+		command.setDirectory(directory);
+		command.setURI("file://" + db.getWorkTree().getAbsolutePath());
+		Git git2 = command.call();
+		addRepoToClose(git2.getRepository());
+		assertNotNull(git2);
+		DirCache dc = DirCache.read(git2.getRepository());
+		DirCacheVersion version = dc.getVersion();
+		assertTrue(expected.contains(version));
+		return version;
+	}
+
+	@Test
+	public void testCloneV3OrV2() throws Exception {
+		cloneAndCheck(EnumSet.of(DirCacheVersion.DIRC_VERSION_MINIMUM,
+				DirCacheVersion.DIRC_VERSION_EXTENDED));
+	}
+
+	@Test
+	public void testCloneV4() throws Exception {
+		StoredConfig cfg = SystemReader.getInstance().getUserConfig();
+		cfg.load();
+		cfg.setInt("index", null, "version", 4);
+		cfg.save();
+		cloneAndCheck(EnumSet.of(DirCacheVersion.DIRC_VERSION_PATHCOMPRESS));
+	}
+
+	@Test
+	public void testCloneV4manyFiles() throws Exception {
+		StoredConfig cfg = SystemReader.getInstance().getUserConfig();
+		cfg.load();
+		cfg.setBoolean("feature", null, "manyFiles", true);
+		cfg.save();
+		cloneAndCheck(EnumSet.of(DirCacheVersion.DIRC_VERSION_PATHCOMPRESS));
+	}
+
+	@Test
+	public void testCloneV3CommitNoVersionChange() throws Exception {
+		DirCacheVersion initial = cloneAndCheck(
+				EnumSet.of(DirCacheVersion.DIRC_VERSION_MINIMUM,
+						DirCacheVersion.DIRC_VERSION_EXTENDED));
+		StoredConfig cfg = db.getConfig();
+		cfg.setInt("index", null, "version", 4);
+		cfg.save();
+		try (Git git = new Git(db)) {
+			writeTrashFile("Test.txt2", "Hello again");
+			git.add().addFilepattern("Test.txt2").call();
+			git.commit().setMessage("Second commit").call();
+		}
+		assertEquals("DirCache version should be unchanged", initial,
+				DirCache.read(db).getVersion());
+	}
+
+	@Test
+	public void testCloneV3ResetHardVersionChange() throws Exception {
+		cloneAndCheck(EnumSet.of(DirCacheVersion.DIRC_VERSION_MINIMUM,
+						DirCacheVersion.DIRC_VERSION_EXTENDED));
+		StoredConfig cfg = db.getConfig();
+		cfg.setInt("index", null, "version", 4);
+		cfg.save();
+		FileUtils.delete(new File(db.getDirectory(), "index"));
+		try (Git git = new Git(db)) {
+			git.reset().setMode(ResetType.HARD).call();
+		}
+		assertEquals("DirCache version should have changed",
+				DirCacheVersion.DIRC_VERSION_PATHCOMPRESS,
+				DirCache.read(db).getVersion());
+	}
+
+	@Test
+	public void testCloneV3ResetMixedVersionChange() throws Exception {
+		cloneAndCheck(EnumSet.of(DirCacheVersion.DIRC_VERSION_MINIMUM,
+				DirCacheVersion.DIRC_VERSION_EXTENDED));
+		StoredConfig cfg = db.getConfig();
+		cfg.setInt("index", null, "version", 4);
+		cfg.save();
+		FileUtils.delete(new File(db.getDirectory(), "index"));
+		try (Git git = new Git(db)) {
+			git.reset().setMode(ResetType.MIXED).call();
+		}
+		assertEquals("DirCache version should have changed",
+				DirCacheVersion.DIRC_VERSION_PATHCOMPRESS,
+				DirCache.read(db).getVersion());
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java
index c57cb26..6d4d0b4 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008-2010, Google Inc. and others
+ * Copyright (C) 2008, 2020, 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
@@ -28,6 +28,7 @@
 import java.util.LinkedHashMap;
 import java.util.Map;
 
+import org.eclipse.jgit.dircache.DirCache.DirCacheVersion;
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.junit.JGitTestUtil;
 import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
@@ -188,6 +189,28 @@
 		assertArrayEquals(expectedBytes, indexBytes);
 	}
 
+	@Test
+	public void testReadWriteV4() throws Exception {
+		final File file = pathOf("gitgit.index.v4");
+		final DirCache dc = new DirCache(file, FS.DETECTED);
+		dc.read();
+		assertEquals(DirCacheVersion.DIRC_VERSION_PATHCOMPRESS,
+				dc.getVersion());
+		assertEquals(5, dc.getEntryCount());
+		assertV4TreeEntry(0, "src/org/eclipse/jgit/atest/foo.txt", false, dc);
+		assertV4TreeEntry(1, "src/org/eclipse/jgit/atest/foobar.txt", false,
+				dc);
+		assertV4TreeEntry(2, "src/org/eclipse/jgit/other/bar.txt", true, dc);
+		assertV4TreeEntry(3, "test.txt", false, dc);
+		assertV4TreeEntry(4, "test.txt2", false, dc);
+
+		final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+		dc.writeTo(null, bos);
+		final byte[] indexBytes = bos.toByteArray();
+		final byte[] expectedBytes = IO.readFully(file);
+		assertArrayEquals(expectedBytes, indexBytes);
+	}
+
 	private static void assertV3TreeEntry(int indexPosition, String path,
 			boolean skipWorkTree, boolean intentToAdd, DirCache dc) {
 		final DirCacheEntry entry = dc.getEntry(indexPosition);
@@ -196,6 +219,13 @@
 		assertEquals(intentToAdd, entry.isIntentToAdd());
 	}
 
+	private static void assertV4TreeEntry(int indexPosition, String path,
+			boolean skipWorkTree, DirCache dc) {
+		final DirCacheEntry entry = dc.getEntry(indexPosition);
+		assertEquals(path, entry.getPathString());
+		assertEquals(skipWorkTree, entry.isSkipWorkTree());
+	}
+
 	private static File pathOf(String name) {
 		return JGitTestUtil.getTestResourceFile(name);
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java
index f21c7f8..8e84dfa 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009, Google Inc. and others
+ * Copyright (C) 2009, 2020 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
@@ -11,14 +11,25 @@
 package org.eclipse.jgit.dircache;
 
 import static java.time.Instant.EPOCH;
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.time.Instant;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jgit.dircache.DirCache.DirCacheVersion;
+import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.util.MutableInteger;
 import org.junit.Test;
 
 public class DirCacheEntryTest {
@@ -47,6 +58,95 @@
 		}
 	}
 
+	private static void checkPath(DirCacheVersion indexVersion,
+			DirCacheEntry previous, String name) throws IOException {
+		DirCacheEntry dce = new DirCacheEntry(name);
+		long now = System.currentTimeMillis();
+		long anHourAgo = now - TimeUnit.HOURS.toMillis(1);
+		dce.setLastModified(Instant.ofEpochMilli(anHourAgo));
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		dce.write(out, indexVersion, previous);
+		byte[] raw = out.toByteArray();
+		MessageDigest md0 = Constants.newMessageDigest();
+		md0.update(raw);
+		ByteArrayInputStream in = new ByteArrayInputStream(raw);
+		MutableInteger infoAt = new MutableInteger();
+		byte[] sharedInfo = new byte[raw.length];
+		MessageDigest md = Constants.newMessageDigest();
+		DirCacheEntry read = new DirCacheEntry(sharedInfo, infoAt, in, md,
+				Instant.ofEpochMilli(now), indexVersion, previous);
+		assertEquals("Paths of length " + name.length() + " should match", name,
+				read.getPathString());
+		assertEquals("Should have been fully read", -1, in.read());
+		assertArrayEquals("Digests should match", md0.digest(),
+				md.digest());
+	}
+
+	@Test
+	public void testLongPath() throws Exception {
+		StringBuilder name = new StringBuilder(4094 + 16);
+		for (int i = 0; i < 4094; i++) {
+			name.append('a');
+		}
+		for (int j = 0; j < 16; j++) {
+			checkPath(DirCacheVersion.DIRC_VERSION_EXTENDED, null,
+					name.toString());
+			name.append('b');
+		}
+	}
+
+	@Test
+	public void testLongPathV4() throws Exception {
+		StringBuilder name = new StringBuilder(4094 + 16);
+		for (int i = 0; i < 4094; i++) {
+			name.append('a');
+		}
+		DirCacheEntry previous = new DirCacheEntry(name.toString());
+		for (int j = 0; j < 16; j++) {
+			checkPath(DirCacheVersion.DIRC_VERSION_PATHCOMPRESS, previous,
+					name.toString());
+			name.append('b');
+		}
+	}
+
+	@Test
+	public void testShortPath() throws Exception {
+		StringBuilder name = new StringBuilder(1 + 16);
+		name.append('a');
+		for (int j = 0; j < 16; j++) {
+			checkPath(DirCacheVersion.DIRC_VERSION_EXTENDED, null,
+					name.toString());
+			name.append('b');
+		}
+	}
+
+	@Test
+	public void testShortPathV4() throws Exception {
+		StringBuilder name = new StringBuilder(1 + 16);
+		name.append('a');
+		DirCacheEntry previous = new DirCacheEntry(name.toString());
+		for (int j = 0; j < 16; j++) {
+			checkPath(DirCacheVersion.DIRC_VERSION_PATHCOMPRESS, previous,
+					name.toString());
+			name.append('b');
+		}
+	}
+
+	@Test
+	public void testPathV4() throws Exception {
+		StringBuilder name = new StringBuilder();
+		for (int i = 0; i < 20; i++) {
+			name.append('a');
+		}
+		DirCacheEntry previous = new DirCacheEntry(name.toString());
+		for (int j = 0; j < 20; j++) {
+			name.setLength(name.length() - 1);
+			String newName = name.toString() + "bbb";
+			checkPath(DirCacheVersion.DIRC_VERSION_PATHCOMPRESS, previous,
+					newName);
+		}
+	}
+
 	@SuppressWarnings("unused")
 	@Test
 	public void testCreate_ByStringPath() {
@@ -142,6 +242,46 @@
 	}
 
 	@Test
+	public void testSetStage() {
+		DirCacheEntry e = new DirCacheEntry("some/path", DirCacheEntry.STAGE_1);
+		e.setAssumeValid(true);
+		e.setCreationTime(2L);
+		e.setFileMode(FileMode.EXECUTABLE_FILE);
+		e.setLastModified(EPOCH.plusMillis(3L));
+		e.setLength(100L);
+		e.setObjectId(ObjectId
+				.fromString("0123456789012345678901234567890123456789"));
+		e.setUpdateNeeded(true);
+		e.setStage(DirCacheEntry.STAGE_2);
+
+		assertTrue(e.isAssumeValid());
+		assertEquals(2L, e.getCreationTime());
+		assertEquals(
+				ObjectId.fromString("0123456789012345678901234567890123456789"),
+				e.getObjectId());
+		assertEquals(FileMode.EXECUTABLE_FILE, e.getFileMode());
+		assertEquals(EPOCH.plusMillis(3L), e.getLastModifiedInstant());
+		assertEquals(100L, e.getLength());
+		assertEquals(DirCacheEntry.STAGE_2, e.getStage());
+		assertTrue(e.isUpdateNeeded());
+		assertEquals("some/path", e.getPathString());
+
+		e.setStage(DirCacheEntry.STAGE_0);
+
+		assertTrue(e.isAssumeValid());
+		assertEquals(2L, e.getCreationTime());
+		assertEquals(
+				ObjectId.fromString("0123456789012345678901234567890123456789"),
+				e.getObjectId());
+		assertEquals(FileMode.EXECUTABLE_FILE, e.getFileMode());
+		assertEquals(EPOCH.plusMillis(3L), e.getLastModifiedInstant());
+		assertEquals(100L, e.getLength());
+		assertEquals(DirCacheEntry.STAGE_0, e.getStage());
+		assertTrue(e.isUpdateNeeded());
+		assertEquals("some/path", e.getPathString());
+	}
+
+	@Test
 	public void testCopyMetaDataWithStage() {
 		copyMetaDataHelper(false);
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCachePathEditTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCachePathEditTest.java
index 39a1f01..5778d28 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCachePathEditTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCachePathEditTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011, Robin Rosenberg and others
+ * Copyright (C) 2011, 2020 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
@@ -124,6 +124,32 @@
 	}
 
 	@Test
+	public void testPathEditWithStagesAndReset() throws Exception {
+		DirCache dc = DirCache.newInCore();
+		DirCacheBuilder builder = new DirCacheBuilder(dc, 3);
+		builder.add(createEntry("a", DirCacheEntry.STAGE_1));
+		builder.add(createEntry("a", DirCacheEntry.STAGE_2));
+		builder.add(createEntry("a", DirCacheEntry.STAGE_3));
+		builder.finish();
+
+		DirCacheEditor editor = dc.editor();
+		PathEdit edit = new PathEdit("a") {
+
+			@Override
+			public void apply(DirCacheEntry ent) {
+				ent.setStage(DirCacheEntry.STAGE_0);
+			}
+		};
+		editor.add(edit);
+		editor.finish();
+
+		assertEquals(1, dc.getEntryCount());
+		DirCacheEntry entry = dc.getEntry(0);
+		assertEquals("a", entry.getPathString());
+		assertEquals(DirCacheEntry.STAGE_0, entry.getStage());
+	}
+
+	@Test
 	public void testFileReplacesTree() throws Exception {
 		DirCache dc = DirCache.newInCore();
 		DirCacheEditor editor = dc.editor();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBundleWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBundleWriterTest.java
new file mode 100644
index 0000000..4238ee6
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBundleWriterTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2020, Google LLC  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
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.storage.dfs;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Set;
+
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.transport.FetchResult;
+import org.eclipse.jgit.transport.RefSpec;
+import org.eclipse.jgit.transport.TransportBundleStream;
+import org.eclipse.jgit.transport.URIish;
+import org.junit.Before;
+import org.junit.Test;
+
+public class DfsBundleWriterTest {
+	private TestRepository<InMemoryRepository> git;
+
+	private InMemoryRepository repo;
+
+	@Before
+	public void setUp() throws IOException {
+		DfsRepositoryDescription desc = new DfsRepositoryDescription("test");
+		git = new TestRepository<>(new InMemoryRepository(desc));
+		repo = git.getRepository();
+	}
+
+	@Test
+	public void testRepo() throws Exception {
+		RevCommit commit0 = git.commit().message("0").create();
+		RevCommit commit1 = git.commit().message("1").parent(commit0).create();
+		git.update("master", commit1);
+
+		RevCommit commit2 = git.commit().message("0").create();
+
+		byte[] bundle = makeBundle();
+		try (Repository newRepo = new InMemoryRepository(
+				new DfsRepositoryDescription("copy"))) {
+			fetchFromBundle(newRepo, bundle);
+			Ref ref = newRepo.exactRef("refs/heads/master");
+			assertNotNull(ref);
+			assertEquals(commit1.toObjectId(), ref.getObjectId());
+
+			// Unreferenced objects are included as well.
+			assertTrue(newRepo.getObjectDatabase().has(commit2));
+		}
+	}
+
+	private byte[] makeBundle() throws IOException {
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		DfsBundleWriter.writeEntireRepositoryAsBundle(
+				NullProgressMonitor.INSTANCE, out, repo);
+		return out.toByteArray();
+	}
+
+	private static FetchResult fetchFromBundle(Repository newRepo,
+			byte[] bundle) throws Exception {
+		URIish uri = new URIish("in-memory://");
+		ByteArrayInputStream in = new ByteArrayInputStream(bundle);
+		RefSpec rs = new RefSpec("refs/heads/*:refs/heads/*");
+		Set<RefSpec> refs = Collections.singleton(rs);
+		try (TransportBundleStream transport = new TransportBundleStream(
+				newRepo, uri, in)) {
+			return transport.fetch(NullProgressMonitor.INSTANCE, refs);
+		}
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java
index e8d1cd3..d007dd4 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java
@@ -80,6 +80,29 @@
 	}
 
 	@Theory
+	public void testPack2Commits_noPackFolder(boolean aggressive) throws Exception {
+		File packDir = repo.getObjectDatabase().getPackDirectory();
+		assertTrue(packDir.delete());
+
+		BranchBuilder bb = tr.branch("refs/heads/master");
+		bb.commit().add("A", "A").add("B", "B").create();
+		bb.commit().add("A", "A2").add("B", "B2").create();
+
+		stats = gc.getStatistics();
+		assertEquals(8, stats.numberOfLooseObjects);
+		assertEquals(0, stats.numberOfPackedObjects);
+		configureGc(gc, aggressive);
+		gc.gc();
+		stats = gc.getStatistics();
+		assertEquals(0, stats.numberOfLooseObjects);
+		assertEquals(8, stats.numberOfPackedObjects);
+		assertEquals(1, stats.numberOfPackFiles);
+		assertEquals(2, stats.numberOfBitmaps);
+
+		assertTrue(packDir.exists());
+	}
+
+	@Theory
 	public void testPackAllObjectsInOnePack(boolean aggressive)
 			throws Exception {
 		tr.branch("refs/heads/master").commit().add("A", "A").add("B", "B")
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcOrphanFilesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcOrphanFilesTest.java
index 84d364b..c5c316d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcOrphanFilesTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcOrphanFilesTest.java
@@ -24,10 +24,14 @@
 
 	private static final String BITMAP_File_1 = PACK + "-1.bitmap";
 
+	private static final String BITMAP_File_2 = PACK + "-2.bitmap";
+
 	private static final String IDX_File_2 = PACK + "-2.idx";
 
 	private static final String IDX_File_malformed = PACK + "-1234idx";
 
+	private static final String KEEP_File_2 = PACK + "-2.keep";
+
 	private static final String PACK_File_2 = PACK + "-2.pack";
 
 	private static final String PACK_File_3 = PACK + "-3.pack";
@@ -72,6 +76,22 @@
 		assertTrue(new File(packDir, IDX_File_malformed).exists());
 	}
 
+	@Test
+	public void keepPreventsDeletionOfIndexFilesForMissingPackFile()
+			throws Exception {
+		createFileInPackFolder(BITMAP_File_1);
+		createFileInPackFolder(IDX_File_2);
+		createFileInPackFolder(BITMAP_File_2);
+		createFileInPackFolder(KEEP_File_2);
+		createFileInPackFolder(PACK_File_3);
+		gc.gc();
+		assertFalse(new File(packDir, BITMAP_File_1).exists());
+		assertTrue(new File(packDir, BITMAP_File_2).exists());
+		assertTrue(new File(packDir, IDX_File_2).exists());
+		assertTrue(new File(packDir, KEEP_File_2).exists());
+		assertTrue(new File(packDir, PACK_File_3).exists());
+	}
+
 	private void createFileInPackFolder(String fileName) throws IOException {
 		if (!packDir.exists() || !packDir.isDirectory()) {
 			assertTrue(packDir.mkdirs());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexTestCase.java
index fe05fba..910b928 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexTestCase.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexTestCase.java
@@ -12,13 +12,17 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.fail;
 
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
 
 import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry;
 import org.eclipse.jgit.junit.RepositoryTestCase;
 import org.junit.Test;
@@ -51,6 +55,13 @@
 	public abstract File getFileForPackdf2982f28();
 
 	/**
+	 * Return file with appropriate index version for bad fanout table test.
+	 *
+	 * @return file with index
+	 */
+	public abstract File getFileForBadFanoutTable();
+
+	/**
 	 * Verify CRC32 support.
 	 *
 	 * @throws MissingObjectException
@@ -158,4 +169,15 @@
 				.name());
 	}
 
+	@Test
+	public void testBadFanoutTable() {
+		IOException ex = assertThrows(IOException.class, () -> {
+			try (FileInputStream fis = new FileInputStream(
+					getFileForBadFanoutTable())) {
+				PackIndex.read(fis);
+			}
+		});
+		assertEquals(JGitText.get().indexFileIsTooLargeForJgit,
+				ex.getMessage());
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexV1Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexV1Test.java
index e41ded7..c4f6372 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexV1Test.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexV1Test.java
@@ -35,6 +35,11 @@
                     "pack-df2982f284bbabb6bdb59ee3fcc6eb0983e20371.idx");
 	}
 
+	@Override
+	public File getFileForBadFanoutTable() {
+		return JGitTestUtil.getTestResourceFile("pack-bad-fanout-table.idx");
+	}
+
 	/**
 	 * Verify CRC32 - V1 should not index anything.
 	 *
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexV2Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexV2Test.java
index c1da547..1d179ab 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexV2Test.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexV2Test.java
@@ -35,6 +35,11 @@
 				"pack-df2982f284bbabb6bdb59ee3fcc6eb0983e20371.idxV2");
 	}
 
+	@Override
+	public File getFileForBadFanoutTable() {
+		return JGitTestUtil.getTestResourceFile("pack-bad-fanout-table.idxV2");
+	}
+
 	/**
 	 * Verify CRC32 indexing.
 	 *
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/MergedReftableTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/MergedReftableTest.java
index a246ac9..0a03fc3 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/MergedReftableTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/MergedReftableTest.java
@@ -329,22 +329,22 @@
 	public void overlappedUpdateIndices() throws IOException {
 		ByteArrayOutputStream buf = new ByteArrayOutputStream();
 		ReftableWriter writer = new ReftableWriter(buf)
-				.setMinUpdateIndex(1)
-				.setMaxUpdateIndex(3)
-				.begin();
-		writer.writeRef(ref("refs/heads/a", 1), 1);
-		writer.writeRef(ref("refs/heads/b", 2), 3);
-		writer.finish();
-		byte[] base = buf.toByteArray();
-
-		buf = new ByteArrayOutputStream();
-		writer = new ReftableWriter(buf)
 				.setMinUpdateIndex(2)
 				.setMaxUpdateIndex(4)
 				.begin();
 		writer.writeRef(ref("refs/heads/a", 10), 2);
 		writer.writeRef(ref("refs/heads/b", 20), 4);
 		writer.finish();
+		byte[] base = buf.toByteArray();
+
+		buf = new ByteArrayOutputStream();
+		writer = new ReftableWriter(buf)
+				.setMinUpdateIndex(1)
+				.setMaxUpdateIndex(3)
+				.begin();
+		writer.writeRef(ref("refs/heads/a", 1), 1);
+		writer.writeRef(ref("refs/heads/b", 2), 3);
+		writer.finish();
 		byte[] delta = buf.toByteArray();
 
 		MergedReftable mr = merge(base, delta);
@@ -368,22 +368,22 @@
 	public void enclosedUpdateIndices() throws IOException {
 		ByteArrayOutputStream buf = new ByteArrayOutputStream();
 		ReftableWriter writer = new ReftableWriter(buf)
-				.setMinUpdateIndex(1)
-				.setMaxUpdateIndex(4)
-				.begin();
-		writer.writeRef(ref("refs/heads/a", 1), 1);
-		writer.writeRef(ref("refs/heads/b", 20), 4);
-		writer.finish();
-		byte[] base = buf.toByteArray();
-
-		buf = new ByteArrayOutputStream();
-		writer = new ReftableWriter(buf)
 				.setMinUpdateIndex(2)
 				.setMaxUpdateIndex(3)
 				.begin();
 		writer.writeRef(ref("refs/heads/a", 10), 2);
 		writer.writeRef(ref("refs/heads/b", 2), 3);
 		writer.finish();
+		byte[] base = buf.toByteArray();
+
+		buf = new ByteArrayOutputStream();
+		writer = new ReftableWriter(buf)
+				.setMinUpdateIndex(1)
+				.setMaxUpdateIndex(4)
+				.begin();
+		writer.writeRef(ref("refs/heads/a", 1), 1);
+		writer.writeRef(ref("refs/heads/b", 20), 4);
+		writer.finish();
 		byte[] delta = buf.toByteArray();
 
 		MergedReftable mr = merge(base, delta);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/CommitBuilderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/CommitBuilderTest.java
index 26294c7..dee58f9 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/CommitBuilderTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/CommitBuilderTest.java
@@ -13,7 +13,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertThrows;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -24,6 +24,32 @@
 
 public class CommitBuilderTest {
 
+	// @formatter:off
+	private static final String SIGNATURE = "-----BEGIN PGP SIGNATURE-----\n" +
+			"Version: BCPG v1.60\n" +
+			"\n" +
+			"iQEcBAABCAAGBQJb9cVhAAoJEKX+6Axg/6TZeFsH/0CY0WX/z7U8+7S5giFX4wH4\n" +
+			"opvBwqyt6OX8lgNwTwBGHFNt8LdmDCCmKoq/XwkNi3ARVjLhe3gBcKXNoavvPk2Z\n" +
+			"gIg5ChevGkU4afWCOMLVEYnkCBGw2+86XhrK1P7gTHEk1Rd+Yv1ZRDJBY+fFO7yz\n" +
+			"uSBuF5RpEY2sJiIvp27Gub/rY3B5NTR/feO/z+b9oiP/fMUhpRwG5KuWUsn9NPjw\n" +
+			"3tvbgawYpU/2UnS+xnavMY4t2fjRYjsoxndPLb2MUX8X7vC7FgWLBlmI/rquLZVM\n" +
+			"IQEKkjnA+lhejjK1rv+ulq4kGZJFKGYWYYhRDwFg5PTkzhudhN2SGUq5Wxq1Eg4=\n" +
+			"=b9OI\n" +
+			"-----END PGP SIGNATURE-----";
+
+	private static final String EXPECTED = "-----BEGIN PGP SIGNATURE-----\n" +
+			" Version: BCPG v1.60\n" +
+			" \n" +
+			" iQEcBAABCAAGBQJb9cVhAAoJEKX+6Axg/6TZeFsH/0CY0WX/z7U8+7S5giFX4wH4\n" +
+			" opvBwqyt6OX8lgNwTwBGHFNt8LdmDCCmKoq/XwkNi3ARVjLhe3gBcKXNoavvPk2Z\n" +
+			" gIg5ChevGkU4afWCOMLVEYnkCBGw2+86XhrK1P7gTHEk1Rd+Yv1ZRDJBY+fFO7yz\n" +
+			" uSBuF5RpEY2sJiIvp27Gub/rY3B5NTR/feO/z+b9oiP/fMUhpRwG5KuWUsn9NPjw\n" +
+			" 3tvbgawYpU/2UnS+xnavMY4t2fjRYjsoxndPLb2MUX8X7vC7FgWLBlmI/rquLZVM\n" +
+			" IQEKkjnA+lhejjK1rv+ulq4kGZJFKGYWYYhRDwFg5PTkzhudhN2SGUq5Wxq1Eg4=\n" +
+			" =b9OI\n" +
+			" -----END PGP SIGNATURE-----";
+	// @formatter:on
+
 	private void assertGpgSignatureStringOutcome(String signature,
 			String expectedOutcome) throws IOException {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -33,47 +59,37 @@
 	}
 
 	@Test
-	public void writeGpgSignatureString_1() throws Exception {
-		// @formatter:off
-		String signature = "-----BEGIN PGP SIGNATURE-----\n" +
-				"Version: BCPG v1.60\n" +
-				"\n" +
-				"iQEcBAABCAAGBQJb9cVhAAoJEKX+6Axg/6TZeFsH/0CY0WX/z7U8+7S5giFX4wH4\n" +
-				"opvBwqyt6OX8lgNwTwBGHFNt8LdmDCCmKoq/XwkNi3ARVjLhe3gBcKXNoavvPk2Z\n" +
-				"gIg5ChevGkU4afWCOMLVEYnkCBGw2+86XhrK1P7gTHEk1Rd+Yv1ZRDJBY+fFO7yz\n" +
-				"uSBuF5RpEY2sJiIvp27Gub/rY3B5NTR/feO/z+b9oiP/fMUhpRwG5KuWUsn9NPjw\n" +
-				"3tvbgawYpU/2UnS+xnavMY4t2fjRYjsoxndPLb2MUX8X7vC7FgWLBlmI/rquLZVM\n" +
-				"IQEKkjnA+lhejjK1rv+ulq4kGZJFKGYWYYhRDwFg5PTkzhudhN2SGUq5Wxq1Eg4=\n" +
-				"=b9OI\n" +
-				"-----END PGP SIGNATURE-----";
-		String expectedOutcome = "-----BEGIN PGP SIGNATURE-----\n" +
-				" Version: BCPG v1.60\n" +
-				" \n" +
-				" iQEcBAABCAAGBQJb9cVhAAoJEKX+6Axg/6TZeFsH/0CY0WX/z7U8+7S5giFX4wH4\n" +
-				" opvBwqyt6OX8lgNwTwBGHFNt8LdmDCCmKoq/XwkNi3ARVjLhe3gBcKXNoavvPk2Z\n" +
-				" gIg5ChevGkU4afWCOMLVEYnkCBGw2+86XhrK1P7gTHEk1Rd+Yv1ZRDJBY+fFO7yz\n" +
-				" uSBuF5RpEY2sJiIvp27Gub/rY3B5NTR/feO/z+b9oiP/fMUhpRwG5KuWUsn9NPjw\n" +
-				" 3tvbgawYpU/2UnS+xnavMY4t2fjRYjsoxndPLb2MUX8X7vC7FgWLBlmI/rquLZVM\n" +
-				" IQEKkjnA+lhejjK1rv+ulq4kGZJFKGYWYYhRDwFg5PTkzhudhN2SGUq5Wxq1Eg4=\n" +
-				" =b9OI\n" +
-				" -----END PGP SIGNATURE-----";
-		// @formatter:on
-		assertGpgSignatureStringOutcome(signature, expectedOutcome);
+	public void writeGpgSignatureString() throws Exception {
+		assertGpgSignatureStringOutcome(SIGNATURE, EXPECTED);
+	}
+
+	@Test
+	public void writeGpgSignatureStringTrailingLF() throws Exception {
+		assertGpgSignatureStringOutcome(SIGNATURE + '\n', EXPECTED);
+	}
+
+	@Test
+	public void writeGpgSignatureStringCRLF() throws Exception {
+		assertGpgSignatureStringOutcome(SIGNATURE.replaceAll("\n", "\r\n"),
+				EXPECTED);
+	}
+
+	@Test
+	public void writeGpgSignatureStringTrailingCRLF() throws Exception {
+		assertGpgSignatureStringOutcome(
+				SIGNATURE.replaceAll("\n", "\r\n") + "\r\n", EXPECTED);
 	}
 
 	@Test
 	public void writeGpgSignatureString_failsForNonAscii() throws Exception {
 		String signature = "Ü Ä";
-		try {
-			CommitBuilder.writeGpgSignatureString(signature,
-					new ByteArrayOutputStream());
-			fail("Exception expected");
-		} catch (IllegalArgumentException e) {
-			// good
-			String message = MessageFormat.format(JGitText.get().notASCIIString,
-					signature);
-			assertEquals(message, e.getMessage());
-		}
+		IllegalArgumentException e = assertThrows(
+				IllegalArgumentException.class,
+				() -> CommitBuilder.writeGpgSignatureString(signature,
+						new ByteArrayOutputStream()));
+		String message = MessageFormat.format(JGitText.get().notASCIIString,
+				signature);
+		assertEquals(message, e.getMessage());
 	}
 
 	@Test
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 a272c8f..b943486 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,41 +2,13 @@
  * 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-2011, Christian Halstrick <christian.halstrick@sap.com>
- * and other copyright owners as documented in the project's IP log.
+ * Copyright (C) 2010, 2020 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 v1.0 which accompanies this
- * distribution, is reproduced below, and is available at
- * http://www.eclipse.org/org/documents/edl-v10.php
+ * 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.lib;
 
@@ -85,6 +57,7 @@
 import org.eclipse.jgit.treewalk.WorkingTreeIterator;
 import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.FileUtils;
+import org.eclipse.jgit.util.StringUtils;
 import org.junit.Assume;
 import org.junit.Test;
 
@@ -284,6 +257,86 @@
 		}
 	}
 
+	private void checkoutLineEndings(String inIndex, String expected,
+			String attributes) throws Exception {
+		try (Git git = new Git(db);
+				TestRepository<Repository> db_t = new TestRepository<>(db)) {
+			BranchBuilder master = db_t.branch("master");
+			master.commit().add("f", inIndex).message("m0").create();
+			if (!StringUtils.isEmptyOrNull(attributes)) {
+				master.commit().add(".gitattributes", attributes)
+						.message("attributes").create();
+			}
+			File f = new File(db.getWorkTree(), "f");
+			assertFalse(f.exists());
+			git.checkout().setName("master").call();
+			assertTrue(f.exists());
+			checkFile(f, expected);
+		}
+	}
+
+	@Test
+	public void testCheckoutWithCRLF() throws Exception {
+		checkoutLineEndings("first line\r\nsecond line\r\n",
+				"first line\r\nsecond line\r\n", null);
+	}
+
+	@Test
+	public void testCheckoutWithCRLFAuto() throws Exception {
+		checkoutLineEndings("first line\r\nsecond line\r\n",
+				"first line\r\nsecond line\r\n", "f text=auto");
+	}
+
+	@Test
+	public void testCheckoutWithCRLFAutoEolLf() throws Exception {
+		checkoutLineEndings("first line\r\nsecond line\r\n",
+				"first line\r\nsecond line\r\n", "f text=auto eol=lf");
+	}
+
+	@Test
+	public void testCheckoutWithCRLFAutoEolNative() throws Exception {
+		checkoutLineEndings("first line\r\nsecond line\r\n",
+				"first line\r\nsecond line\r\n", "f text=auto eol=native");
+	}
+
+	@Test
+	public void testCheckoutWithCRLFAutoEolCrLf() throws Exception {
+		checkoutLineEndings("first line\r\nsecond line\r\n",
+				"first line\r\nsecond line\r\n", "f text=auto eol=crlf");
+	}
+
+	@Test
+	public void testCheckoutWithLF() throws Exception {
+		checkoutLineEndings("first line\nsecond line\n",
+				"first line\nsecond line\n", null);
+	}
+
+	@Test
+	public void testCheckoutWithLFAuto() throws Exception {
+		checkoutLineEndings("first line\nsecond line\n",
+				"first line\nsecond line\n", "f text=auto");
+	}
+
+	@Test
+	public void testCheckoutWithLFAutoEolLf() throws Exception {
+		checkoutLineEndings("first line\nsecond line\n",
+				"first line\nsecond line\n", "f text=auto eol=lf");
+	}
+
+	@Test
+	public void testCheckoutWithLFAutoEolNative() throws Exception {
+		checkoutLineEndings(
+				"first line\nsecond line\n", "first line\nsecond line\n"
+						.replaceAll("\n", System.lineSeparator()),
+				"f text=auto eol=native");
+	}
+
+	@Test
+	public void testCheckoutWithLFAutoEolCrLf() throws Exception {
+		checkoutLineEndings("first line\nsecond line\n",
+				"first line\r\nsecond line\r\n", "f text=auto eol=crlf");
+	}
+
 	private DirCacheCheckout resetHard(RevCommit commit)
 			throws NoWorkTreeException,
 			CorruptObjectException, IOException {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/logging/PerformanceLogContextTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/logging/PerformanceLogContextTest.java
new file mode 100644
index 0000000..f3c1dde
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/logging/PerformanceLogContextTest.java
@@ -0,0 +1,95 @@
+package org.eclipse.jgit.logging;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import java.util.List;
+
+/**
+ * Tests for performance log context utilities.
+ */
+public class PerformanceLogContextTest {
+
+	@Test
+	public void testAddEvent() {
+		PerformanceLogRecord record = new PerformanceLogRecord("record", 0);
+		PerformanceLogContext.getInstance().addEvent(record);
+
+		List<PerformanceLogRecord> eventRecords = PerformanceLogContext
+				.getInstance().getEventRecords();
+		assertTrue(eventRecords.contains(record));
+		assertEquals(1, eventRecords.size());
+	}
+
+	@Test
+	public void testCleanEvents() {
+		PerformanceLogRecord record1 = new PerformanceLogRecord("record1", 0);
+		PerformanceLogContext.getInstance().addEvent(record1);
+
+		PerformanceLogRecord record2 = new PerformanceLogRecord("record2", 0);
+		PerformanceLogContext.getInstance().addEvent(record2);
+
+		PerformanceLogContext.getInstance().cleanEvents();
+		List<PerformanceLogRecord> eventRecords = PerformanceLogContext
+				.getInstance().getEventRecords();
+		assertEquals(0, eventRecords.size());
+	}
+
+	@Test
+	public void testAddEventsTwoThreads() throws InterruptedException {
+		TestRunnable runnable1 = new TestRunnable("record1", 1);
+		TestRunnable runnable2 = new TestRunnable("record2", 2);
+
+		Thread thread1 = new Thread(runnable1);
+		Thread thread2 = new Thread(runnable2);
+
+		thread1.start();
+		thread2.start();
+
+		thread1.join();
+		thread2.join();
+		assertEquals(1, runnable1.getEventRecordsCount());
+		assertEquals(1, runnable2.getEventRecordsCount());
+		assertFalse(runnable1.isThrown());
+		assertFalse(runnable2.isThrown());
+	}
+
+	private static class TestRunnable implements Runnable {
+		private String name;
+
+		private long durationMs;
+
+		private long eventRecordsCount;
+
+		private boolean thrown = false;
+
+		public TestRunnable(String name, long durationMs) {
+			this.name = name;
+			this.durationMs = durationMs;
+		}
+
+		public boolean isThrown() {
+			return thrown;
+		}
+
+		public long getEventRecordsCount() {
+			return eventRecordsCount;
+		}
+
+		@Override
+		public void run() {
+			PerformanceLogRecord record = new PerformanceLogRecord(name,
+					durationMs);
+			try {
+				PerformanceLogContext.getInstance().addEvent(record);
+				eventRecordsCount = PerformanceLogContext.getInstance()
+						.getEventRecords().size();
+				PerformanceLogContext.getInstance().cleanEvents();
+			} catch (Exception e) {
+				thrown = true;
+			}
+		}
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/GitlinkMergeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/GitlinkMergeTest.java
new file mode 100644
index 0000000..f410960
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/GitlinkMergeTest.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright (c) 2020, Google LLC  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
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.merge;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheBuilder;
+import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.lib.CommitBuilder;
+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.test.resources.SampleDataRepositoryTestCase;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.junit.Test;
+
+public class GitlinkMergeTest extends SampleDataRepositoryTestCase {
+	private static final String LINK_ID1 = "DEADBEEFDEADBEEFBABEDEADBEEFDEADBEEFBABE";
+	private static final String LINK_ID2 = "DEADDEADDEADDEADDEADDEADDEADDEADDEADDEAD";
+	private static final String LINK_ID3 = "BEEFBEEFBEEFBEEFBEEFBEEFBEEFBEEFBEEFBEEF";
+
+	private static final String SUBMODULE_PATH = "submodule.link";
+
+	@Test
+	public void testGitLinkMerging_AddNew() throws Exception {
+		assertGitLinkValue(
+				testGitLink(null, null, LINK_ID3, newResolveMerger(), true),
+				LINK_ID3);
+	}
+
+	@Test
+	public void testGitLinkMerging_Delete() throws Exception {
+		assertGitLinkDoesntExist(testGitLink(LINK_ID1, LINK_ID1, null,
+				newResolveMerger(), true));
+	}
+
+	@Test
+	public void testGitLinkMerging_UpdateDelete() throws Exception {
+		testGitLink(LINK_ID1, LINK_ID2, null, newResolveMerger(), false);
+	}
+
+	@Test
+	public void testGitLinkMerging_DeleteUpdate() throws Exception {
+		testGitLink(LINK_ID1, null, LINK_ID3, newResolveMerger(), false);
+	}
+
+	@Test
+	public void testGitLinkMerging_UpdateUpdate() throws Exception {
+		testGitLink(LINK_ID1, LINK_ID2, LINK_ID3, newResolveMerger(), false);
+	}
+
+	@Test
+	public void testGitLinkMerging_bothAddedSameLink() throws Exception {
+		assertGitLinkValue(
+				testGitLink(null, LINK_ID2, LINK_ID2, newResolveMerger(), true),
+				LINK_ID2);
+	}
+
+	@Test
+	public void testGitLinkMerging_bothAddedDifferentLink() throws Exception {
+		testGitLink(null, LINK_ID2, LINK_ID3, newResolveMerger(), false);
+	}
+
+	@Test
+	public void testGitLinkMerging_AddNew_ignoreConflicts() throws Exception {
+		assertGitLinkValue(
+				testGitLink(null, null, LINK_ID3, newIgnoreConflictMerger(),
+						true),
+				LINK_ID3);
+	}
+
+	@Test
+	public void testGitLinkMerging_Delete_ignoreConflicts() throws Exception {
+		assertGitLinkDoesntExist(testGitLink(LINK_ID1, LINK_ID1, null,
+				newIgnoreConflictMerger(), true));
+	}
+
+	@Test
+	public void testGitLinkMerging_UpdateDelete_ignoreConflicts()
+			throws Exception {
+		assertGitLinkValue(testGitLink(LINK_ID1, LINK_ID2, null,
+				newIgnoreConflictMerger(), true), LINK_ID2);
+	}
+
+	@Test
+	public void testGitLinkMerging_DeleteUpdate_ignoreConflicts()
+			throws Exception {
+		assertGitLinkDoesntExist(testGitLink(LINK_ID1, null, LINK_ID3,
+				newIgnoreConflictMerger(), true));
+	}
+
+	@Test
+	public void testGitLinkMerging_UpdateUpdate_ignoreConflicts()
+			throws Exception {
+		assertGitLinkValue(testGitLink(LINK_ID1, LINK_ID2, LINK_ID3,
+				newIgnoreConflictMerger(), true), LINK_ID2);
+	}
+
+	@Test
+	public void testGitLinkMerging_bothAddedSameLink_ignoreConflicts()
+			throws Exception {
+		assertGitLinkValue(testGitLink(null, LINK_ID2, LINK_ID2,
+				newIgnoreConflictMerger(), true), LINK_ID2);
+	}
+
+	@Test
+	public void testGitLinkMerging_bothAddedDifferentLink_ignoreConflicts()
+			throws Exception {
+		assertGitLinkValue(testGitLink(null, LINK_ID2, LINK_ID3,
+				newIgnoreConflictMerger(), true), LINK_ID2);
+	}
+
+	protected Merger testGitLink(@Nullable String baseLink,
+			@Nullable String oursLink, @Nullable String theirsLink,
+			Merger merger, boolean shouldMerge)
+			throws Exception {
+		DirCache treeB = db.readDirCache();
+		DirCache treeO = db.readDirCache();
+		DirCache treeT = db.readDirCache();
+
+		DirCacheBuilder bTreeBuilder = treeB.builder();
+		DirCacheBuilder oTreeBuilder = treeO.builder();
+		DirCacheBuilder tTreeBuilder = treeT.builder();
+
+		maybeAddLink(bTreeBuilder, baseLink);
+		maybeAddLink(oTreeBuilder, oursLink);
+		maybeAddLink(tTreeBuilder, theirsLink);
+
+		bTreeBuilder.finish();
+		oTreeBuilder.finish();
+		tTreeBuilder.finish();
+
+		ObjectInserter ow = db.newObjectInserter();
+		ObjectId b = commit(ow, treeB, new ObjectId[] {});
+		ObjectId o = commit(ow, treeO, new ObjectId[] { b });
+		ObjectId t = commit(ow, treeT, new ObjectId[] { b });
+
+		boolean merge = merger.merge(new ObjectId[] { o, t });
+		assertEquals(Boolean.valueOf(shouldMerge), Boolean.valueOf(merge));
+
+		return merger;
+	}
+
+	private Merger newResolveMerger() {
+		return MergeStrategy.RESOLVE.newMerger(db, true);
+	}
+
+	private Merger newIgnoreConflictMerger() {
+		return new ResolveMerger(db, true) {
+			@Override
+			protected boolean mergeImpl() throws IOException {
+				// emulate call with ignore conflicts.
+				return mergeTrees(mergeBase(), sourceTrees[0], sourceTrees[1],
+						true);
+			}
+		};
+	}
+
+	@Test
+	public void testGitLinkMerging_blobWithLink() throws Exception {
+		DirCache treeB = db.readDirCache();
+		DirCache treeO = db.readDirCache();
+		DirCache treeT = db.readDirCache();
+
+		DirCacheBuilder bTreeBuilder = treeB.builder();
+		DirCacheBuilder oTreeBuilder = treeO.builder();
+		DirCacheBuilder tTreeBuilder = treeT.builder();
+
+		bTreeBuilder.add(
+				createEntry(SUBMODULE_PATH, FileMode.REGULAR_FILE, "blob"));
+		oTreeBuilder.add(
+				createEntry(SUBMODULE_PATH, FileMode.REGULAR_FILE, "blob 2"));
+
+		maybeAddLink(tTreeBuilder, LINK_ID3);
+
+		bTreeBuilder.finish();
+		oTreeBuilder.finish();
+		tTreeBuilder.finish();
+
+		ObjectInserter ow = db.newObjectInserter();
+		ObjectId b = commit(ow, treeB, new ObjectId[] {});
+		ObjectId o = commit(ow, treeO, new ObjectId[] { b });
+		ObjectId t = commit(ow, treeT, new ObjectId[] { b });
+
+		Merger resolveMerger = MergeStrategy.RESOLVE.newMerger(db);
+		boolean merge = resolveMerger.merge(new ObjectId[] { o, t });
+		assertFalse(merge);
+	}
+
+	@Test
+	public void testGitLinkMerging_linkWithBlob() throws Exception {
+		DirCache treeB = db.readDirCache();
+		DirCache treeO = db.readDirCache();
+		DirCache treeT = db.readDirCache();
+
+		DirCacheBuilder bTreeBuilder = treeB.builder();
+		DirCacheBuilder oTreeBuilder = treeO.builder();
+		DirCacheBuilder tTreeBuilder = treeT.builder();
+
+		maybeAddLink(bTreeBuilder, LINK_ID1);
+		maybeAddLink(oTreeBuilder, LINK_ID2);
+		tTreeBuilder.add(
+				createEntry(SUBMODULE_PATH, FileMode.REGULAR_FILE, "blob 3"));
+
+		bTreeBuilder.finish();
+		oTreeBuilder.finish();
+		tTreeBuilder.finish();
+
+		ObjectInserter ow = db.newObjectInserter();
+		ObjectId b = commit(ow, treeB, new ObjectId[] {});
+		ObjectId o = commit(ow, treeO, new ObjectId[] { b });
+		ObjectId t = commit(ow, treeT, new ObjectId[] { b });
+
+		Merger resolveMerger = MergeStrategy.RESOLVE.newMerger(db);
+		boolean merge = resolveMerger.merge(new ObjectId[] { o, t });
+		assertFalse(merge);
+	}
+
+	@Test
+	public void testGitLinkMerging_linkWithLink() throws Exception {
+		DirCache treeB = db.readDirCache();
+		DirCache treeO = db.readDirCache();
+		DirCache treeT = db.readDirCache();
+
+		DirCacheBuilder bTreeBuilder = treeB.builder();
+		DirCacheBuilder oTreeBuilder = treeO.builder();
+		DirCacheBuilder tTreeBuilder = treeT.builder();
+
+		bTreeBuilder.add(
+				createEntry(SUBMODULE_PATH, FileMode.REGULAR_FILE, "blob"));
+		maybeAddLink(oTreeBuilder, LINK_ID2);
+		maybeAddLink(tTreeBuilder, LINK_ID3);
+
+		bTreeBuilder.finish();
+		oTreeBuilder.finish();
+		tTreeBuilder.finish();
+
+		ObjectInserter ow = db.newObjectInserter();
+		ObjectId b = commit(ow, treeB, new ObjectId[] {});
+		ObjectId o = commit(ow, treeO, new ObjectId[] { b });
+		ObjectId t = commit(ow, treeT, new ObjectId[] { b });
+
+		Merger resolveMerger = MergeStrategy.RESOLVE.newMerger(db);
+		boolean merge = resolveMerger.merge(new ObjectId[] { o, t });
+		assertFalse(merge);
+	}
+
+	@Test
+	public void testGitLinkMerging_blobWithBlobFromLink() throws Exception {
+		DirCache treeB = db.readDirCache();
+		DirCache treeO = db.readDirCache();
+		DirCache treeT = db.readDirCache();
+
+		DirCacheBuilder bTreeBuilder = treeB.builder();
+		DirCacheBuilder oTreeBuilder = treeO.builder();
+		DirCacheBuilder tTreeBuilder = treeT.builder();
+
+		maybeAddLink(bTreeBuilder, LINK_ID1);
+		oTreeBuilder.add(
+				createEntry(SUBMODULE_PATH, FileMode.REGULAR_FILE, "blob 2"));
+		tTreeBuilder.add(
+				createEntry(SUBMODULE_PATH, FileMode.REGULAR_FILE, "blob 3"));
+
+		bTreeBuilder.finish();
+		oTreeBuilder.finish();
+		tTreeBuilder.finish();
+
+		ObjectInserter ow = db.newObjectInserter();
+		ObjectId b = commit(ow, treeB, new ObjectId[] {});
+		ObjectId o = commit(ow, treeO, new ObjectId[] { b });
+		ObjectId t = commit(ow, treeT, new ObjectId[] { b });
+
+		Merger resolveMerger = MergeStrategy.RESOLVE.newMerger(db);
+		boolean merge = resolveMerger.merge(new ObjectId[] { o, t });
+		assertFalse(merge);
+	}
+
+	@Test
+	public void testGitLinkMerging_linkBlobDeleted() throws Exception {
+		// We changed a link to a blob, others has deleted this link.
+		DirCache treeB = db.readDirCache();
+		DirCache treeO = db.readDirCache();
+		DirCache treeT = db.readDirCache();
+
+		DirCacheBuilder bTreeBuilder = treeB.builder();
+		DirCacheBuilder oTreeBuilder = treeO.builder();
+		DirCacheBuilder tTreeBuilder = treeT.builder();
+
+		maybeAddLink(bTreeBuilder, LINK_ID1);
+		oTreeBuilder.add(
+				createEntry(SUBMODULE_PATH, FileMode.REGULAR_FILE, "blob 2"));
+
+		bTreeBuilder.finish();
+		oTreeBuilder.finish();
+		tTreeBuilder.finish();
+
+		ObjectInserter ow = db.newObjectInserter();
+		ObjectId b = commit(ow, treeB, new ObjectId[] {});
+		ObjectId o = commit(ow, treeO, new ObjectId[] { b });
+		ObjectId t = commit(ow, treeT, new ObjectId[] { b });
+
+		Merger resolveMerger = MergeStrategy.RESOLVE.newMerger(db);
+		boolean merge = resolveMerger.merge(new ObjectId[] { o, t });
+		assertFalse(merge);
+	}
+
+	private void maybeAddLink(DirCacheBuilder builder,
+			@Nullable String linkId) {
+		if (linkId == null) {
+			return;
+		}
+		DirCacheEntry newLink = createGitLink(SUBMODULE_PATH,
+				ObjectId.fromString(linkId));
+		builder.add(newLink);
+	}
+
+	private void assertGitLinkValue(Merger resolveMerger, String expectedValue)
+			throws Exception {
+		try (TreeWalk tw = new TreeWalk(db)) {
+			tw.setRecursive(true);
+			tw.reset(resolveMerger.getResultTreeId());
+
+			assertTrue(tw.next());
+			assertEquals(SUBMODULE_PATH, tw.getPathString());
+			assertEquals(ObjectId.fromString(expectedValue), tw.getObjectId(0));
+
+			assertFalse(tw.next());
+		}
+	}
+
+	private void assertGitLinkDoesntExist(Merger resolveMerger)
+			throws Exception {
+		try (TreeWalk tw = new TreeWalk(db)) {
+			tw.setRecursive(true);
+			tw.reset(resolveMerger.getResultTreeId());
+
+			assertFalse(tw.next());
+		}
+	}
+
+	private static ObjectId commit(ObjectInserter odi, DirCache treeB,
+			ObjectId[] parentIds) throws Exception {
+		CommitBuilder c = new CommitBuilder();
+		c.setTreeId(treeB.writeTree(odi));
+		c.setAuthor(new PersonIdent("A U Thor", "a.u.thor", 1L, 0));
+		c.setCommitter(c.getAuthor());
+		c.setParentIds(parentIds);
+		c.setMessage("Tree " + c.getTreeId().name());
+		ObjectId id = odi.insert(c);
+		odi.flush();
+		return id;
+	}
+}
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 7a244e1..e2ac89b 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
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012, Robin Stocker <robin@nibor.org> and others
+ * Copyright (C) 2012, 2020 Robin Stocker <robin@nibor.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
@@ -367,6 +367,48 @@
 	}
 
 	@Theory
+	public void mergeConflictWithCrLfTextAuto(MergeStrategy strategy)
+			throws IOException, GitAPIException {
+		Git git = Git.wrap(db);
+		writeTrashFile("crlf.txt", "a crlf file\r\n");
+		git.add().addFilepattern("crlf.txt").call();
+		git.commit().setMessage("base").call();
+		assertEquals("[crlf.txt, mode:100644, content:a crlf file\r\n]",
+				indexState(CONTENT));
+		writeTrashFile(".gitattributes", "crlf.txt text=auto");
+		git.add().addFilepattern(".gitattributes").call();
+		git.commit().setMessage("attributes").call();
+
+		git.branchCreate().setName("brancha").call();
+
+		writeTrashFile("crlf.txt", "a crlf file\r\na second line\r\n");
+		git.add().addFilepattern("crlf.txt").call();
+		git.commit().setMessage("on master").call();
+		assertEquals(
+				"[.gitattributes, mode:100644, content:crlf.txt text=auto]"
+						+ "[crlf.txt, mode:100644, content:a crlf file\r\na second line\r\n]",
+				indexState(CONTENT));
+
+		git.checkout().setName("brancha").call();
+		File testFile = writeTrashFile("crlf.txt",
+				"a crlf file\r\nanother line\r\n");
+		git.add().addFilepattern("crlf.txt").call();
+		git.commit().setMessage("on brancha").call();
+
+		MergeResult mergeResult = git.merge().setStrategy(strategy)
+				.include(db.resolve("master")).call();
+		assertEquals(MergeResult.MergeStatus.CONFLICTING,
+				mergeResult.getMergeStatus());
+		checkFile(testFile,
+				"a crlf file\r\n" //
+						+ "<<<<<<< HEAD\n" //
+						+ "another line\r\n" //
+						+ "=======\n" //
+						+ "a second line\r\n" //
+						+ ">>>>>>> 8e9e704742f1bc8a41eac88aac4aeefd338b7384\n");
+	}
+
+	@Theory
 	public void mergeWithCrlfAutoCrlfTrue(MergeStrategy strategy)
 			throws IOException, GitAPIException {
 		Git git = Git.wrap(db);
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 70175c8..ea994f0 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
@@ -455,6 +455,7 @@
 		final CanonicalTreeParser p = new CanonicalTreeParser();
 		p.reset(testDb.getRevWalk().getObjectReader(), commit.getTree());
 		try (SubmoduleWalk gen = SubmoduleWalk.forPath(db, p, "sub")) {
+			assertEquals(arbitraryName, gen.getModuleName());
 			assertEquals(path, gen.getPath());
 			assertEquals(subId, gen.getObjectId());
 			assertEquals(new File(db.getWorkTree(), path), gen.getDirectory());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BasePackConnectionTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BasePackConnectionTest.java
new file mode 100644
index 0000000..64b16f6
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BasePackConnectionTest.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2020, Lee Worrall 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.transport;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasKey;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.SymbolicRef;
+import org.junit.Test;
+
+public class BasePackConnectionTest {
+
+	@Test
+	public void testExtractSymRefsFromCapabilities() {
+		final Map<String, String> symRefs = BasePackConnection
+				.extractSymRefsFromCapabilities(
+						Arrays.asList("symref=HEAD:refs/heads/main",
+								"symref=refs/heads/sym:refs/heads/other"));
+
+		assertEquals(2, symRefs.size());
+		assertEquals("refs/heads/main", symRefs.get("HEAD"));
+		assertEquals("refs/heads/other", symRefs.get("refs/heads/sym"));
+	}
+
+	@Test
+	public void testUpdateWithSymRefsAdds() {
+		final Ref mainRef = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE,
+				"refs/heads/main", ObjectId.fromString(
+						"0000000000000000000000000000000000000001"));
+
+		final Map<String, Ref> refMap = new HashMap<>();
+		refMap.put(mainRef.getName(), mainRef);
+		refMap.put("refs/heads/other",
+				new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/other",
+						ObjectId.fromString(
+								"0000000000000000000000000000000000000002")));
+
+		final Map<String, String> symRefs = new HashMap<>();
+		symRefs.put("HEAD", "refs/heads/main");
+
+		BasePackConnection.updateWithSymRefs(refMap, symRefs);
+
+		assertThat(refMap, hasKey("HEAD"));
+		final Ref headRef = refMap.get("HEAD");
+		assertThat(headRef, instanceOf(SymbolicRef.class));
+		final SymbolicRef headSymRef = (SymbolicRef) headRef;
+		assertEquals("HEAD", headSymRef.getName());
+		assertSame(mainRef, headSymRef.getTarget());
+	}
+
+	@Test
+	public void testUpdateWithSymRefsReplaces() {
+		final Ref mainRef = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE,
+				"refs/heads/main", ObjectId.fromString(
+						"0000000000000000000000000000000000000001"));
+
+		final Map<String, Ref> refMap = new HashMap<>();
+		refMap.put(mainRef.getName(), mainRef);
+		refMap.put("HEAD", new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "HEAD",
+				mainRef.getObjectId()));
+		refMap.put("refs/heads/other",
+				new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/other",
+						ObjectId.fromString(
+								"0000000000000000000000000000000000000002")));
+
+		final Map<String, String> symRefs = new HashMap<>();
+		symRefs.put("HEAD", "refs/heads/main");
+
+		BasePackConnection.updateWithSymRefs(refMap, symRefs);
+
+		assertThat(refMap, hasKey("HEAD"));
+		final Ref headRef = refMap.get("HEAD");
+		assertThat(headRef, instanceOf(SymbolicRef.class));
+		final SymbolicRef headSymRef = (SymbolicRef) headRef;
+		assertEquals("HEAD", headSymRef.getName());
+		assertSame(mainRef, headSymRef.getTarget());
+	}
+
+	@Test
+	public void testUpdateWithSymRefsWithIndirectsAdds() {
+		final Ref mainRef = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE,
+				"refs/heads/main", ObjectId.fromString(
+						"0000000000000000000000000000000000000001"));
+
+		final Map<String, Ref> refMap = new HashMap<>();
+		refMap.put(mainRef.getName(), mainRef);
+		refMap.put("refs/heads/other",
+				new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/other",
+						ObjectId.fromString(
+								"0000000000000000000000000000000000000002")));
+
+		final Map<String, String> symRefs = new LinkedHashMap<>(); // Ordered
+		symRefs.put("refs/heads/sym3", "refs/heads/sym2"); // Forward reference
+		symRefs.put("refs/heads/sym1", "refs/heads/main");
+		symRefs.put("refs/heads/sym2", "refs/heads/sym1"); // Backward reference
+
+		BasePackConnection.updateWithSymRefs(refMap, symRefs);
+
+		assertThat(refMap, hasKey("refs/heads/sym1"));
+		final Ref sym1Ref = refMap.get("refs/heads/sym1");
+		assertThat(sym1Ref, instanceOf(SymbolicRef.class));
+		final SymbolicRef sym1SymRef = (SymbolicRef) sym1Ref;
+		assertEquals("refs/heads/sym1", sym1SymRef.getName());
+		assertSame(mainRef, sym1SymRef.getTarget());
+
+		assertThat(refMap, hasKey("refs/heads/sym2"));
+		final Ref sym2Ref = refMap.get("refs/heads/sym2");
+		assertThat(sym2Ref, instanceOf(SymbolicRef.class));
+		final SymbolicRef sym2SymRef = (SymbolicRef) sym2Ref;
+		assertEquals("refs/heads/sym2", sym2SymRef.getName());
+		assertSame(sym1SymRef, sym2SymRef.getTarget());
+
+		assertThat(refMap, hasKey("refs/heads/sym3"));
+		final Ref sym3Ref = refMap.get("refs/heads/sym3");
+		assertThat(sym3Ref, instanceOf(SymbolicRef.class));
+		final SymbolicRef sym3SymRef = (SymbolicRef) sym3Ref;
+		assertEquals("refs/heads/sym3", sym3SymRef.getName());
+		assertSame(sym2SymRef, sym3SymRef.getTarget());
+	}
+
+	@Test
+	public void testUpdateWithSymRefsWithIndirectsReplaces() {
+		final Ref mainRef = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE,
+				"refs/heads/main", ObjectId.fromString(
+						"0000000000000000000000000000000000000001"));
+
+		final Map<String, Ref> refMap = new HashMap<>();
+		refMap.put(mainRef.getName(), mainRef);
+		refMap.put("refs/heads/sym1", new ObjectIdRef.Unpeeled(
+				Ref.Storage.LOOSE, "refs/heads/sym1", mainRef.getObjectId()));
+		refMap.put("refs/heads/sym2", new ObjectIdRef.Unpeeled(
+				Ref.Storage.LOOSE, "refs/heads/sym2", mainRef.getObjectId()));
+		refMap.put("refs/heads/sym3", new ObjectIdRef.Unpeeled(
+				Ref.Storage.LOOSE, "refs/heads/sym3", mainRef.getObjectId()));
+		refMap.put("refs/heads/other",
+				new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/other",
+						ObjectId.fromString(
+								"0000000000000000000000000000000000000002")));
+
+		final Map<String, String> symRefs = new LinkedHashMap<>(); // Ordered
+		symRefs.put("refs/heads/sym3", "refs/heads/sym2"); // Forward reference
+		symRefs.put("refs/heads/sym1", "refs/heads/main");
+		symRefs.put("refs/heads/sym2", "refs/heads/sym1"); // Backward reference
+
+		BasePackConnection.updateWithSymRefs(refMap, symRefs);
+
+		assertThat(refMap, hasKey("refs/heads/sym1"));
+		final Ref sym1Ref = refMap.get("refs/heads/sym1");
+		assertThat(sym1Ref, instanceOf(SymbolicRef.class));
+		final SymbolicRef sym1SymRef = (SymbolicRef) sym1Ref;
+		assertEquals("refs/heads/sym1", sym1SymRef.getName());
+		assertSame(mainRef, sym1SymRef.getTarget());
+
+		assertThat(refMap, hasKey("refs/heads/sym2"));
+		final Ref sym2Ref = refMap.get("refs/heads/sym2");
+		assertThat(sym2Ref, instanceOf(SymbolicRef.class));
+		final SymbolicRef sym2SymRef = (SymbolicRef) sym2Ref;
+		assertEquals("refs/heads/sym2", sym2SymRef.getName());
+		assertSame(sym1SymRef, sym2SymRef.getTarget());
+
+		assertThat(refMap, hasKey("refs/heads/sym3"));
+		final Ref sym3Ref = refMap.get("refs/heads/sym3");
+		assertThat(sym3Ref, instanceOf(SymbolicRef.class));
+		final SymbolicRef sym3SymRef = (SymbolicRef) sym3Ref;
+		assertEquals("refs/heads/sym3", sym3SymRef.getName());
+		assertSame(sym2SymRef, sym3SymRef.getTarget());
+	}
+
+	@Test
+	public void testUpdateWithSymRefsIgnoresSelfReference() {
+		final Ref mainRef = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE,
+				"refs/heads/main", ObjectId.fromString(
+						"0000000000000000000000000000000000000001"));
+
+		final Map<String, Ref> refMap = new HashMap<>();
+		refMap.put(mainRef.getName(), mainRef);
+		refMap.put("refs/heads/other",
+				new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/other",
+						ObjectId.fromString(
+								"0000000000000000000000000000000000000002")));
+
+		final Map<String, String> symRefs = new LinkedHashMap<>();
+		symRefs.put("refs/heads/sym1", "refs/heads/sym1");
+
+		BasePackConnection.updateWithSymRefs(refMap, symRefs);
+
+		assertEquals(2, refMap.size());
+		assertThat(refMap, not(hasKey("refs/heads/sym1")));
+	}
+
+	@Test
+	public void testUpdateWithSymRefsIgnoreCircularReference() {
+		final Ref mainRef = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE,
+				"refs/heads/main", ObjectId.fromString(
+						"0000000000000000000000000000000000000001"));
+
+		final Map<String, Ref> refMap = new HashMap<>();
+		refMap.put(mainRef.getName(), mainRef);
+		refMap.put("refs/heads/other",
+				new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/other",
+						ObjectId.fromString(
+								"0000000000000000000000000000000000000002")));
+
+		final Map<String, String> symRefs = new LinkedHashMap<>();
+		symRefs.put("refs/heads/sym2", "refs/heads/sym1");
+		symRefs.put("refs/heads/sym1", "refs/heads/sym2");
+
+		BasePackConnection.updateWithSymRefs(refMap, symRefs);
+
+		assertEquals(2, refMap.size());
+		assertThat(refMap, not(hasKey("refs/heads/sym1")));
+		assertThat(refMap, not(hasKey("refs/heads/sym2")));
+	}
+}
\ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigTest.java
index fcbec52..5336dd7 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigTest.java
@@ -13,7 +13,9 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import org.eclipse.jgit.junit.MockSystemReader;
 import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.util.SystemReader;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -25,6 +27,7 @@
 
 	private static final String DEFAULT = "[http]\n" + "\tpostBuffer = 1\n"
 			+ "\tsslVerify= true\n" + "\tfollowRedirects = true\n"
+			+ "\textraHeader = x: y\n" + "\tuserAgent = Test/0.1\n"
 			+ "\tmaxRedirects = 5\n\n";
 
 	private Config config;
@@ -95,7 +98,8 @@
 	@Test
 	public void testMatchWithInvalidUriInConfig() throws Exception {
 		config.fromText(
-				DEFAULT + "[http \"///\"]\n" + "\tpostBuffer = 1024\n");
+				DEFAULT + "[http \"///#expectedWarning\"]\n"
+						+ "\tpostBuffer = 1024\n");
 		HttpConfig http = new HttpConfig(config,
 				new URIish("http://example.com/path/repo.git"));
 		assertEquals(1, http.getPostBuffer());
@@ -103,7 +107,8 @@
 
 	@Test
 	public void testMatchWithInvalidAndValidUriInConfig() throws Exception {
-		config.fromText(DEFAULT + "[http \"///\"]\n" + "\tpostBuffer = 1024\n"
+		config.fromText(DEFAULT + "[http \"///#expectedWarning\"]\n"
+				+ "\tpostBuffer = 1024\n"
 				+ "[http \"http://example.com\"]\n" + "\tpostBuffer = 2048\n");
 		HttpConfig http = new HttpConfig(config,
 				new URIish("http://example.com/path/repo.git"));
@@ -174,4 +179,93 @@
 				new URIish("http://user@example.com/path"));
 		assertEquals(1024, http.getPostBuffer());
 	}
+
+	@Test
+	public void testExtraHeaders() throws Exception {
+		config.fromText(DEFAULT + "[http \"http://example.com\"]\n"
+				+ "\textraHeader=foo: bar\n");
+		HttpConfig http = new HttpConfig(config,
+				new URIish("http://example.com/"));
+		assertEquals(1, http.getExtraHeaders().size());
+		assertEquals("foo: bar", http.getExtraHeaders().get(0));
+	}
+
+	@Test
+	public void testExtraHeadersMultiple() throws Exception {
+		config.fromText(DEFAULT + "[http \"http://example.com\"]\n"
+				+ "\textraHeader=foo: bar\n" //
+				+ "\textraHeader=bar: foo\n");
+		HttpConfig http = new HttpConfig(config,
+				new URIish("http://example.com/"));
+		assertEquals(2, http.getExtraHeaders().size());
+		assertEquals("foo: bar", http.getExtraHeaders().get(0));
+		assertEquals("bar: foo", http.getExtraHeaders().get(1));
+	}
+
+	@Test
+	public void testExtraHeadersReset() throws Exception {
+		config.fromText(DEFAULT + "[http \"http://example.com\"]\n"
+				+ "\textraHeader=foo: bar\n" //
+				+ "\textraHeader=bar: foo\n" //
+				+ "\textraHeader=\n");
+		HttpConfig http = new HttpConfig(config,
+				new URIish("http://example.com/"));
+		assertTrue(http.getExtraHeaders().isEmpty());
+	}
+
+	@Test
+	public void testExtraHeadersResetAndMore() throws Exception {
+		config.fromText(DEFAULT + "[http \"http://example.com\"]\n"
+				+ "\textraHeader=foo: bar\n" //
+				+ "\textraHeader=bar: foo\n" //
+				+ "\textraHeader=\n" //
+				+ "\textraHeader=baz: something\n");
+		HttpConfig http = new HttpConfig(config,
+				new URIish("http://example.com/"));
+		assertEquals(1, http.getExtraHeaders().size());
+		assertEquals("baz: something", http.getExtraHeaders().get(0));
+	}
+
+	@Test
+	public void testUserAgent() throws Exception {
+		config.fromText(DEFAULT + "[http \"http://example.com\"]\n"
+				+ "\tuserAgent=DummyAgent/4.0\n");
+		HttpConfig http = new HttpConfig(config,
+				new URIish("http://example.com/"));
+		assertEquals("DummyAgent/4.0", http.getUserAgent());
+	}
+
+	@Test
+	public void testUserAgentEnvOverride() throws Exception {
+		String mockAgent = "jgit-test/5.10.0";
+		SystemReader originalReader = SystemReader.getInstance();
+		SystemReader.setInstance(new MockSystemReader() {
+
+			@Override
+			public String getenv(String variable) {
+				if ("GIT_HTTP_USER_AGENT".equals(variable)) {
+					return mockAgent;
+				}
+				return super.getenv(variable);
+			}
+		});
+		try {
+			config.fromText(DEFAULT + "[http \"http://example.com\"]\n"
+					+ "\tuserAgent=DummyAgent/4.0\n");
+			HttpConfig http = new HttpConfig(config,
+					new URIish("http://example.com/"));
+			assertEquals(mockAgent, http.getUserAgent());
+		} finally {
+			SystemReader.setInstance(originalReader);
+		}
+	}
+
+	@Test
+	public void testUserAgentNonAscii() throws Exception {
+		config.fromText(DEFAULT + "[http \"http://example.com\"]\n"
+				+ "\tuserAgent= d ümmy Agent -5.10\n");
+		HttpConfig http = new HttpConfig(config,
+				new URIish("http://example.com/"));
+		assertEquals("d.mmy.Agent.-5.10", http.getUserAgent());
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV0ParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV0ParserTest.java
index 965a2fa..c0db83a 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV0ParserTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV0ParserTest.java
@@ -9,6 +9,10 @@
  */
 package org.eclipse.jgit.transport;
 
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
+import static org.eclipse.jgit.lib.Constants.OBJ_TAG;
+import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
 import static org.eclipse.jgit.transport.ObjectIdMatcher.hasOnlyObjectIds;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
@@ -160,6 +164,10 @@
 		assertThat(request.getWantIds(),
 				hasOnlyObjectIds("4624442d68ee402a94364191085b77137618633e",
 						"f900c8326a43303685c46b279b9f70411bff1a4b"));
+		assertTrue(request.getFilterSpec().allowsType(OBJ_BLOB));
+		assertTrue(request.getFilterSpec().allowsType(OBJ_TREE));
+		assertTrue(request.getFilterSpec().allowsType(OBJ_COMMIT));
+		assertTrue(request.getFilterSpec().allowsType(OBJ_TAG));
 		assertEquals(13000, request.getFilterSpec().getBlobLimit());
 		assertEquals(-1, request.getFilterSpec().getTreeDepthLimit());
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV2ParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV2ParserTest.java
index 038ce71..837bdce 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV2ParserTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV2ParserTest.java
@@ -9,6 +9,10 @@
  */
 package org.eclipse.jgit.transport;
 
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
+import static org.eclipse.jgit.lib.Constants.OBJ_TAG;
+import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
 import static org.eclipse.jgit.transport.ObjectIdMatcher.hasOnlyObjectIds;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.hasItems;
@@ -195,7 +199,11 @@
 		ProtocolV2Parser parser = new ProtocolV2Parser(
 				ConfigBuilder.start().allowFilter().done());
 		FetchV2Request request = parser.parseFetchRequest(pckIn);
-		assertEquals(0, request.getFilterSpec().getBlobLimit());
+		assertFalse(request.getFilterSpec().allowsType(OBJ_BLOB));
+		assertTrue(request.getFilterSpec().allowsType(OBJ_TREE));
+		assertTrue(request.getFilterSpec().allowsType(OBJ_COMMIT));
+		assertTrue(request.getFilterSpec().allowsType(OBJ_TAG));
+		assertEquals(-1, request.getFilterSpec().getBlobLimit());
 		assertEquals(-1, request.getFilterSpec().getTreeDepthLimit());
 	}
 
@@ -207,6 +215,10 @@
 		ProtocolV2Parser parser = new ProtocolV2Parser(
 				ConfigBuilder.start().allowFilter().done());
 		FetchV2Request request = parser.parseFetchRequest(pckIn);
+		assertTrue(request.getFilterSpec().allowsType(OBJ_BLOB));
+		assertTrue(request.getFilterSpec().allowsType(OBJ_TREE));
+		assertTrue(request.getFilterSpec().allowsType(OBJ_COMMIT));
+		assertTrue(request.getFilterSpec().allowsType(OBJ_TAG));
 		assertEquals(15, request.getFilterSpec().getBlobLimit());
 		assertEquals(-1, request.getFilterSpec().getTreeDepthLimit());
 	}
@@ -219,6 +231,10 @@
 		ProtocolV2Parser parser = new ProtocolV2Parser(
 				ConfigBuilder.start().allowFilter().done());
 		FetchV2Request request = parser.parseFetchRequest(pckIn);
+		assertTrue(request.getFilterSpec().allowsType(OBJ_BLOB));
+		assertTrue(request.getFilterSpec().allowsType(OBJ_TREE));
+		assertTrue(request.getFilterSpec().allowsType(OBJ_COMMIT));
+		assertTrue(request.getFilterSpec().allowsType(OBJ_TAG));
 		assertEquals(-1, request.getFilterSpec().getBlobLimit());
 		assertEquals(3, request.getFilterSpec().getTreeDepthLimit());
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConnectionTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConnectionTest.java
index 0179d8c..21fde3e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConnectionTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConnectionTest.java
@@ -162,7 +162,9 @@
 				fail("server did not abort");
 			} catch (TransportException e) {
 				String msg = e.getMessage();
-				assertEquals("remote: Too many commands", msg);
+				assertEquals(
+						"remote: Commands size exceeds limit defined in receive.maxCommandBytes",
+						msg);
 			}
 		}
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportHttpTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportHttpTest.java
index b84b6b2..029b45e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportHttpTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportHttpTest.java
@@ -18,7 +18,9 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Date;
+import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
+import java.util.Map;
 import java.util.Set;
 
 import org.eclipse.jgit.internal.transport.http.NetscapeCookieFile;
@@ -155,4 +157,50 @@
 					cookieFile.exists());
 		}
 	}
+
+	private void assertHeaders(String expected, String... headersToAdd) {
+		HttpConnection fake = Mockito.mock(HttpConnection.class);
+		Map<String, String> headers = new LinkedHashMap<>();
+		Mockito.doAnswer(invocation -> {
+			Object[] args = invocation.getArguments();
+			headers.put(args[0].toString(), args[1].toString());
+			return null;
+		}).when(fake).setRequestProperty(ArgumentMatchers.anyString(),
+				ArgumentMatchers.anyString());
+		TransportHttp.addHeaders(fake, Arrays.asList(headersToAdd));
+		Assert.assertEquals(expected, headers.toString());
+	}
+
+	@Test
+	public void testAddHeaders() {
+		assertHeaders("{a=b, c=d}", "a: b", "c :d");
+	}
+
+	@Test
+	public void testAddHeaderEmptyValue() {
+		assertHeaders("{a-x=b, c=, d=e}", "a-x: b", "c:", "d:e");
+	}
+
+	@Test
+	public void testSkipHeaderWithEmptyKey() {
+		assertHeaders("{a=b, c=d}", "a: b", " : x", "c :d");
+		assertHeaders("{a=b, c=d}", "a: b", ": x", "c :d");
+	}
+
+	@Test
+	public void testSkipHeaderWithoutKey() {
+		assertHeaders("{a=b, c=d}", "a: b", "x", "c :d");
+	}
+
+	@Test
+	public void testSkipHeaderWithInvalidKey() {
+		assertHeaders("{a=b, c=d}", "a: b", "q/p: x", "c :d");
+		assertHeaders("{a=b, c=d}", "a: b", "ä: x", "c :d");
+	}
+
+	@Test
+	public void testSkipHeaderWithNonAsciiValue() {
+		assertHeaders("{a=b, c=d}", "a: b", "q/p: x", "c :d");
+		assertHeaders("{a=b, c=d}", "a: b", "x: ä", "c :d");
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java
index d58e576..ce546e3 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java
@@ -127,7 +127,7 @@
 	}
 
 	@Test
-	public void testFetchWithBlobNoneFilter() throws Exception {
+	public void testFetchWithBlobZeroFilter() throws Exception {
 		InMemoryRepository server2 = newRepo("server2");
 		try (TestRepository<InMemoryRepository> remote2 = new TestRepository<>(
 				server2)) {
@@ -298,6 +298,38 @@
 	}
 
 	@Test
+	public void testFetchWithTreeZeroFilter() throws Exception {
+		InMemoryRepository server2 = newRepo("server2");
+		try (TestRepository<InMemoryRepository> remote2 = new TestRepository<>(
+				server2)) {
+			RevBlob blob1 = remote2.blob("foobar");
+			RevBlob blob2 = remote2.blob("fooba");
+			RevTree tree = remote2.tree(remote2.file("1", blob1),
+					remote2.file("2", blob2));
+			RevCommit commit = remote2.commit(tree);
+			remote2.update("master", commit);
+
+			server2.getConfig().setBoolean("uploadpack", null, "allowfilter",
+					true);
+
+			testProtocol = new TestProtocol<>((Object req, Repository db) -> {
+				UploadPack up = new UploadPack(db);
+				return up;
+			}, null);
+			uri = testProtocol.register(ctx, server2);
+
+			try (Transport tn = testProtocol.open(uri, client, "server2")) {
+				tn.setFilterSpec(FilterSpec.withTreeDepthLimit(0));
+				tn.fetch(NullProgressMonitor.INSTANCE,
+						Collections.singletonList(new RefSpec(commit.name())));
+				assertFalse(client.getObjectDatabase().has(tree.toObjectId()));
+				assertFalse(client.getObjectDatabase().has(blob1.toObjectId()));
+				assertFalse(client.getObjectDatabase().has(blob2.toObjectId()));
+			}
+		}
+	}
+
+	@Test
 	public void testFetchWithNonSupportingServer() throws Exception {
 		InMemoryRepository server2 = newRepo("server2");
 		try (TestRepository<InMemoryRepository> remote2 = new TestRepository<>(
@@ -329,21 +361,21 @@
 	}
 
 	/*
-	 * Invokes UploadPack with protocol v2 and sends it the given lines,
+	 * Invokes UploadPack with specified protocol version and sends it the given lines,
 	 * and returns UploadPack's output stream.
 	 */
-	private ByteArrayInputStream uploadPackV2Setup(
+	private ByteArrayInputStream uploadPackSetup(String version,
 			Consumer<UploadPack> postConstructionSetup, String... inputLines)
 			throws Exception {
 
 		ByteArrayInputStream send = linesAsInputStream(inputLines);
 
-		server.getConfig().setString("protocol", null, "version", "2");
+		server.getConfig().setString("protocol", null, "version", version);
 		UploadPack up = new UploadPack(server);
 		if (postConstructionSetup != null) {
 			postConstructionSetup.accept(up);
 		}
-		up.setExtraParameters(Sets.of("version=2"));
+		up.setExtraParameters(Sets.of("version=".concat(version)));
 
 		ByteArrayOutputStream recv = new ByteArrayOutputStream();
 		up.upload(send, recv, null);
@@ -371,6 +403,30 @@
 	}
 
 	/*
+	 * Invokes UploadPack with protocol v1 and sends it the given lines.
+	 * Returns UploadPack's output stream, not including the capability
+	 * advertisement by the server.
+	 */
+	private ByteArrayInputStream uploadPackV1(
+			Consumer<UploadPack> postConstructionSetup,
+			String... inputLines)
+			throws Exception {
+		ByteArrayInputStream recvStream =
+				uploadPackSetup("1", postConstructionSetup, inputLines);
+		PacketLineIn pckIn = new PacketLineIn(recvStream);
+
+		// drain capabilities
+		while (!PacketLineIn.isEnd(pckIn.readString())) {
+			// do nothing
+		}
+		return recvStream;
+	}
+
+	private ByteArrayInputStream uploadPackV1(String... inputLines) throws Exception {
+		return uploadPackV1(null, inputLines);
+	}
+
+	/*
 	 * Invokes UploadPack with protocol v2 and sends it the given lines.
 	 * Returns UploadPack's output stream, not including the capability
 	 * advertisement by the server.
@@ -380,7 +436,7 @@
 			String... inputLines)
 			throws Exception {
 		ByteArrayInputStream recvStream =
-				uploadPackV2Setup(postConstructionSetup, inputLines);
+				uploadPackSetup("2", postConstructionSetup, inputLines);
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 
 		// drain capabilities
@@ -420,7 +476,7 @@
 	@Test
 	public void testV2Capabilities() throws Exception {
 		TestV2Hook hook = new TestV2Hook();
-		ByteArrayInputStream recvStream = uploadPackV2Setup(
+		ByteArrayInputStream recvStream = uploadPackSetup( "2",
 				(UploadPack up) -> {up.setProtocolV2Hook(hook);},
 				PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
@@ -443,7 +499,7 @@
 			String fetchCapability) throws Exception {
 		server.getConfig().setBoolean(configSection, null, configName, true);
 		ByteArrayInputStream recvStream =
-				uploadPackV2Setup(null, PacketLineIn.end());
+				uploadPackSetup("2", null, PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 
 		assertThat(pckIn.readString(), is("version 2"));
@@ -467,7 +523,7 @@
 			String configName, String fetchCapability) throws Exception {
 		server.getConfig().setBoolean(configSection, null, configName, false);
 		ByteArrayInputStream recvStream =
-				uploadPackV2Setup(null, PacketLineIn.end());
+				uploadPackSetup("2", null, PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 
 		assertThat(pckIn.readString(), is("version 2"));
@@ -519,7 +575,7 @@
 		server.getConfig().setBoolean("uploadpack", null, "allowrefinwant", true);
 		server.getConfig().setBoolean("uploadpack", null, "advertiserefinwant", false);
 		ByteArrayInputStream recvStream =
-				uploadPackV2Setup(null, PacketLineIn.end());
+				uploadPackSetup("2", null, PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 
 		assertThat(pckIn.readString(), is("version 2"));
@@ -683,7 +739,7 @@
 				PacketLineIn.end() };
 
 		TestV2Hook testHook = new TestV2Hook();
-		uploadPackV2Setup((UploadPack up) -> {up.setProtocolV2Hook(testHook);}, lines);
+		uploadPackSetup("2", (UploadPack up) -> {up.setProtocolV2Hook(testHook);}, lines);
 
 		LsRefsV2Request req = testHook.lsRefsRequest;
 		assertEquals(2, req.getServerOptions().size());
@@ -1028,6 +1084,70 @@
 	}
 
 	@Test
+	public void testUploadNewBytes() throws Exception {
+		String commonInBlob = "abcdefghijklmnopqrstuvwx";
+
+		RevBlob parentBlob = remote.blob(commonInBlob + "a");
+		RevCommit parent = remote.commit(remote.tree(remote.file("foo", parentBlob)));
+		RevBlob childBlob = remote.blob(commonInBlob + "b");
+		RevCommit child = remote.commit(remote.tree(remote.file("foo", childBlob)), parent);
+		remote.update("branch1", child);
+
+		ByteArrayInputStream recvStream = uploadPackV2(
+			"command=fetch\n",
+			PacketLineIn.delimiter(),
+			"want " + child.toObjectId().getName() + "\n",
+			"ofs-delta\n",
+			"done\n",
+				PacketLineIn.end());
+		PacketLineIn pckIn = new PacketLineIn(recvStream);
+		assertThat(pckIn.readString(), is("packfile"));
+		ReceivedPackStatistics receivedStats = parsePack(recvStream);
+		assertTrue(receivedStats.getNumBytesDuplicated() == 0);
+		assertTrue(receivedStats.getNumObjectsDuplicated() == 0);
+	}
+
+	@Test
+	public void testUploadRedundantBytes() throws Exception {
+		String commonInBlob = "abcdefghijklmnopqrstuvwxyz";
+
+		RevBlob parentBlob = remote.blob(commonInBlob + "a");
+		RevCommit parent = remote.commit(remote.tree(remote.file("foo", parentBlob)));
+		RevBlob childBlob = remote.blob(commonInBlob + "b");
+		RevCommit child = remote.commit(remote.tree(remote.file("foo", childBlob)), parent);
+		remote.update("branch1", child);
+
+		try (TestRepository<InMemoryRepository> local = new TestRepository<>(
+				client)) {
+			RevBlob localParentBlob = local.blob(commonInBlob + "a");
+			RevCommit localParent = local
+					.commit(local.tree(local.file("foo", localParentBlob)));
+			RevBlob localChildBlob = local.blob(commonInBlob + "b");
+			RevCommit localChild = local.commit(
+					local.tree(local.file("foo", localChildBlob)), localParent);
+			local.update("branch1", localChild);
+		}
+
+		ByteArrayInputStream recvStream = uploadPackV2(
+				"command=fetch\n",
+				PacketLineIn.delimiter(),
+				"want " + child.toObjectId().getName() + "\n",
+				"ofs-delta\n",
+				"done\n",
+					PacketLineIn.end());
+		PacketLineIn pckIn = new PacketLineIn(recvStream);
+		assertThat(pckIn.readString(), is("packfile"));
+		ReceivedPackStatistics receivedStats = parsePack(recvStream);
+
+		long sizeOfHeader = 12;
+		long sizeOfTrailer = 20;
+		long expectedSize = receivedStats.getNumBytesRead() - sizeOfHeader
+				- sizeOfTrailer;
+		assertTrue(receivedStats.getNumBytesDuplicated() == expectedSize);
+		assertTrue(receivedStats.getNumObjectsDuplicated() == 6);
+	}
+
+	@Test
 	public void testV2FetchOfsDelta() throws Exception {
 		String commonInBlob = "abcdefghijklmnopqrstuvwxyz";
 
@@ -1439,7 +1559,7 @@
 				PacketLineIn.end() };
 
 		TestV2Hook testHook = new TestV2Hook();
-		uploadPackV2Setup((UploadPack up) -> {up.setProtocolV2Hook(testHook);}, lines);
+		uploadPackSetup("2", (UploadPack up) -> {up.setProtocolV2Hook(testHook);}, lines);
 
 		FetchV2Request req = testHook.fetchRequest;
 		assertNotNull(req);
@@ -2292,6 +2412,93 @@
 		assertEquals(1, ((RefCallsCountingRepository)server).numRefCalls());
 	}
 
+	@Test
+	public void testNotAdvertisedWantsV1Fetch() throws Exception {
+		String commonInBlob = "abcdefghijklmnopqrstuvwxyz";
+
+		RevBlob parentBlob = remote.blob(commonInBlob + "a");
+		RevCommit parent = remote
+				.commit(remote.tree(remote.file("foo", parentBlob)));
+		RevBlob childBlob = remote.blob(commonInBlob + "b");
+		RevCommit child = remote
+				.commit(remote.tree(remote.file("foo", childBlob)), parent);
+		remote.update("branch1", child);
+
+		uploadPackV1("want " + child.toObjectId().getName() + "\n",
+				PacketLineIn.end(),
+				"have " + parent.toObjectId().getName() + "\n",
+				"done\n", PacketLineIn.end());
+
+		assertEquals(0, stats.getNotAdvertisedWants());
+	}
+
+	@Test
+	public void testNotAdvertisedWantsV1FetchRequestPolicyReachableCommit() throws Exception {
+		String commonInBlob = "abcdefghijklmnopqrstuvwxyz";
+
+		RevBlob parentBlob = remote.blob(commonInBlob + "a");
+		RevCommit parent = remote
+				.commit(remote.tree(remote.file("foo", parentBlob)));
+		RevBlob childBlob = remote.blob(commonInBlob + "b");
+		RevCommit child = remote
+				.commit(remote.tree(remote.file("foo", childBlob)), parent);
+
+		remote.update("branch1", child);
+
+		uploadPackV1((UploadPack up) -> {up.setRequestPolicy(RequestPolicy.REACHABLE_COMMIT);},
+				"want " + parent.toObjectId().getName() + "\n",
+				PacketLineIn.end(),
+				"done\n", PacketLineIn.end());
+
+		assertEquals(1, stats.getNotAdvertisedWants());
+	}
+
+	@Test
+	public void testNotAdvertisedWantsV2FetchThinPack() throws Exception {
+		String commonInBlob = "abcdefghijklmnopqrstuvwxyz";
+
+		RevBlob parentBlob = remote.blob(commonInBlob + "a");
+		RevCommit parent = remote
+				.commit(remote.tree(remote.file("foo", parentBlob)));
+		RevBlob childBlob = remote.blob(commonInBlob + "b");
+		RevCommit child = remote
+				.commit(remote.tree(remote.file("foo", childBlob)), parent);
+		remote.update("branch1", child);
+
+		ByteArrayInputStream recvStream = uploadPackV2("command=fetch\n",
+				PacketLineIn.delimiter(),
+				"want " + child.toObjectId().getName() + "\n",
+				"have " + parent.toObjectId().getName() + "\n", "thin-pack\n",
+				"done\n", PacketLineIn.end());
+		PacketLineIn pckIn = new PacketLineIn(recvStream);
+
+		assertThat(pckIn.readString(), is("packfile"));
+
+		assertEquals(0, stats.getNotAdvertisedWants());
+	}
+
+	@Test
+	public void testNotAdvertisedWantsV2FetchRequestPolicyReachableCommit() throws Exception {
+		String commonInBlob = "abcdefghijklmnopqrstuvwxyz";
+
+		RevBlob parentBlob = remote.blob(commonInBlob + "a");
+		RevCommit parent = remote
+				.commit(remote.tree(remote.file("foo", parentBlob)));
+		RevBlob childBlob = remote.blob(commonInBlob + "b");
+		RevCommit child = remote
+				.commit(remote.tree(remote.file("foo", childBlob)), parent);
+
+		remote.update("branch1", child);
+
+		uploadPackV2((UploadPack up) -> {up.setRequestPolicy(RequestPolicy.REACHABLE_COMMIT);},
+				"command=fetch\n",
+				PacketLineIn.delimiter(),
+				"want " + parent.toObjectId().getName() + "\n", "thin-pack\n",
+				"done\n", PacketLineIn.end());
+
+		assertEquals(1, stats.getNotAdvertisedWants());
+	}
+
 	private class RefCallsCountingRepository extends InMemoryRepository {
 		private final InMemoryRepository.MemRefDatabase refdb;
 		private int numRefCalls;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoLFInputStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoLFInputStreamTest.java
index fb90461..c391694 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoLFInputStreamTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoLFInputStreamTest.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2010, Marc Strapetz <marc.strapetz@syntevo.com>
- * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com> and others
+ * Copyright (C) 2015, 2020 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 v. 1.0 which is available at
@@ -17,7 +17,9 @@
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.function.Function;
 
+import org.eclipse.jgit.util.io.AutoLFInputStream.StreamFlag;
 import org.junit.Test;
 
 public class AutoLFInputStreamTest {
@@ -25,47 +27,65 @@
 	@Test
 	public void testLF() throws IOException {
 		final byte[] bytes = asBytes("1\n2\n3");
-		test(bytes, bytes, false);
+		test(bytes, bytes);
 	}
 
 	@Test
 	public void testCR() throws IOException {
 		final byte[] bytes = asBytes("1\r2\r3");
-		test(bytes, bytes, false);
+		test(bytes, bytes);
 	}
 
 	@Test
 	public void testCRLF() throws IOException {
-		test(asBytes("1\r\n2\r\n3"), asBytes("1\n2\n3"), false);
+		test(asBytes("1\r\n2\r\n3"), asBytes("1\n2\n3"));
 	}
 
 	@Test
 	public void testLFCR() throws IOException {
 		final byte[] bytes = asBytes("1\n\r2\n\r3");
-		test(bytes, bytes, false);
+		test(bytes, bytes);
 	}
 
 	@Test
 	public void testEmpty() throws IOException {
 		final byte[] bytes = asBytes("");
-		test(bytes, bytes, false);
+		test(bytes, bytes);
 	}
 
 	@Test
 	public void testBinaryDetect() throws IOException {
 		final byte[] bytes = asBytes("1\r\n2\r\n3\0");
-		test(bytes, bytes, true);
+		test(bytes, bytes, StreamFlag.DETECT_BINARY);
 	}
 
 	@Test
 	public void testBinaryDontDetect() throws IOException {
-		test(asBytes("1\r\n2\r\n3\0"), asBytes("1\n2\n3\0"), false);
+		test(asBytes("1\r\n2\r\n3\0"), asBytes("1\n2\n3\0"));
+	}
+
+	@Test
+	public void testCrLf() throws IOException {
+		byte[] bytes = asBytes("1\r\n2\n3\r\n\r");
+		test(bytes, bytes, in -> AutoLFInputStream.create(in,
+				StreamFlag.DETECT_BINARY, StreamFlag.FOR_CHECKOUT));
+	}
+
+	@Test
+	public void testCrLfDontDetect() throws IOException {
+		test(asBytes("1\r\n2\r\n"), asBytes("1\n2\n"),
+				in -> AutoLFInputStream.create(in, StreamFlag.DETECT_BINARY));
+	}
+
+	private static void test(byte[] input, byte[] expected, StreamFlag... flags)
+			throws IOException {
+		test(input, expected, in -> AutoLFInputStream.create(in, flags));
 	}
 
 	private static void test(byte[] input, byte[] expected,
-			boolean detectBinary) throws IOException {
+			Function<InputStream, InputStream> factory) throws IOException {
 		try (InputStream bis1 = new ByteArrayInputStream(input);
-				InputStream cis1 = new AutoLFInputStream(bis1, detectBinary)) {
+				InputStream cis1 = factory.apply(bis1)) {
 			int index1 = 0;
 			for (int b = cis1.read(); b != -1; b = cis1.read()) {
 				assertEquals(expected[index1], (byte) b);
@@ -77,8 +97,7 @@
 			for (int bufferSize = 1; bufferSize < 10; bufferSize++) {
 				final byte[] buffer = new byte[bufferSize];
 				try (InputStream bis2 = new ByteArrayInputStream(input);
-						InputStream cis2 = new AutoLFInputStream(bis2,
-								detectBinary)) {
+						InputStream cis2 = factory.apply(bis2)) {
 
 					int read = 0;
 					for (int readNow = cis2.read(buffer, 0,
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoLFOutputStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoLFOutputStreamTest.java
new file mode 100644
index 0000000..1b7e55e
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoLFOutputStreamTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2020, 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.util.io;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertArrayEquals;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.junit.Test;
+
+public class AutoLFOutputStreamTest {
+
+	@Test
+	public void testLF() throws IOException {
+		final byte[] bytes = asBytes("1\n2\n3");
+		test(bytes, bytes, false);
+	}
+
+	@Test
+	public void testCR() throws IOException {
+		final byte[] bytes = asBytes("1\r2\r3");
+		test(bytes, bytes, false);
+	}
+
+	@Test
+	public void testCRLFNoDetect() throws IOException {
+		test(asBytes("1\r\n2\r\n3"), asBytes("1\n2\n3"), false);
+	}
+
+	@Test
+	public void testLFCR() throws IOException {
+		final byte[] bytes = asBytes("1\n\r2\n\r3");
+		test(bytes, bytes, false);
+	}
+
+	@Test
+	public void testEmpty() throws IOException {
+		final byte[] bytes = asBytes("");
+		test(bytes, bytes, false);
+	}
+
+	@Test
+	public void testBinaryDetect() throws IOException {
+		final byte[] bytes = asBytes("1\r\n2\r\n3\0");
+		test(bytes, bytes, true);
+	}
+
+	@Test
+	public void testBinaryDontDetect() throws IOException {
+		test(asBytes("1\r\n2\r\n3\0"), asBytes("1\n2\n3\0"), false);
+	}
+
+	@Test
+	public void testCrLfDetect() throws IOException {
+		byte[] bytes = asBytes("1\r\n2\n3\r\n\r");
+		test(bytes, bytes, true);
+	}
+
+	private static void test(byte[] input, byte[] expected,
+			boolean detectBinary) throws IOException {
+		try (ByteArrayOutputStream result = new ByteArrayOutputStream();
+				OutputStream out = new AutoLFOutputStream(result,
+						detectBinary)) {
+			out.write(input);
+			out.close();
+			assertArrayEquals(expected, result.toByteArray());
+		}
+	}
+
+	private static byte[] asBytes(String in) {
+		return in.getBytes(UTF_8);
+	}
+}
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
new file mode 100644
index 0000000..538c6f7
--- /dev/null
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<component id="org.eclipse.jgit" version="2">
+    <resource path="src/org/eclipse/jgit/lib/TypedConfigGetter.java" type="org.eclipse.jgit.lib.TypedConfigGetter">
+        <filter id="404000815">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.lib.TypedConfigGetter"/>
+                <message_argument value="getPath(Config, String, String, String, FS, File, Path)"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/storage/pack/PackStatistics.java" type="org.eclipse.jgit.storage.pack.PackStatistics$Accumulator">
+        <filter id="336658481">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.storage.pack.PackStatistics.Accumulator"/>
+                <message_argument value="notAdvertisedWants"/>
+            </message_arguments>
+        </filter>
+        <filter id="336658481">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.storage.pack.PackStatistics.Accumulator"/>
+                <message_argument value="reachabilityCheckDuration"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/transport/HttpConfig.java" type="org.eclipse.jgit.transport.HttpConfig">
+        <filter id="336658481">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.transport.HttpConfig"/>
+                <message_argument value="EXTRA_HEADER"/>
+            </message_arguments>
+        </filter>
+        <filter id="336658481">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.transport.HttpConfig"/>
+                <message_argument value="USER_AGENT"/>
+            </message_arguments>
+        </filter>
+    </resource>
+</component>
diff --git a/org.eclipse.jgit/BUILD b/org.eclipse.jgit/BUILD
index f797097..2083372 100644
--- a/org.eclipse.jgit/BUILD
+++ b/org.eclipse.jgit/BUILD
@@ -14,7 +14,7 @@
 RESOURCES = glob(["resources/**"])
 
 java_library(
-    name = "jgit",
+    name = "jgit_non_stamped",
     srcs = SRCS,
     resource_strip_prefix = "org.eclipse.jgit/resources",
     resources = RESOURCES,
@@ -25,6 +25,27 @@
     ],
 )
 
+genrule(
+    name = "jgit",
+    srcs = [":jgit_non_stamped"],
+    outs = ["jgit.jar"],
+    cmd = " && ".join([
+        "ROOT=$$PWD",
+        "TMP=$$(mktemp -d || mktemp -d -t bazel-tmp)",
+        "TZ=UTC",
+        "export TZ",
+        "GEN_VERSION=$$(cat bazel-out/stable-status.txt | grep -w STABLE_BUILD_JGIT_LABEL | cut -d ' ' -f 2)",
+        "cd $$TMP",
+        "unzip -q $$ROOT/$<",
+        "echo \"Implementation-Version: $$GEN_VERSION\n$$(cat META-INF/MANIFEST.MF)\" > META-INF/MANIFEST.MF",
+        "find . -exec touch '{}' ';'",
+        "zip -Xqr $$ROOT/$@ .",
+        "rm -rf $$TMP",
+    ]),
+    stamp = 1,
+    visibility = ["//visibility:public"],
+)
+
 java_library(
     name = "insecure_cipher_factory",
     srcs = INSECURE_CIPHER_FACTORY,
diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF
index 52aa8a3..403b8ee 100644
--- a/org.eclipse.jgit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit/META-INF/MANIFEST.MF
@@ -135,6 +135,7 @@
    org.eclipse.jgit.util.time",
  org.eclipse.jgit.lib.internal;version="6.0.0";
   x-friends:="org.eclipse.jgit.test",
+ org.eclipse.jgit.logging;version="6.0.0",
  org.eclipse.jgit.merge;version="6.0.0";
   uses:="org.eclipse.jgit.dircache,
    org.eclipse.jgit.lib,
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 e6da551..12902b9 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -202,10 +202,8 @@
 corruptPack=Pack file {0} is corrupt, removing it from pack list
 createBranchFailedUnknownReason=Create branch failed for unknown reason
 createBranchUnexpectedResult=Create branch returned unexpected result {0}
-createJGitConfigFailed=Creating JGit config directory {} failed
 createNewFileFailed=Could not create new file {0}
 createRequiresZeroOldId=Create requires old ID to be zero
-createXDGConfigHomeFailed=Creating XDG_CONFIG_HOME directory {} failed
 credentialPassword=Password
 credentialPassphrase=Passphrase
 credentialUsername=Username
@@ -215,6 +213,7 @@
 deepenSinceWithDeepen=Cannot combine deepen with deepen-since
 deleteBranchUnexpectedResult=Delete branch returned unexpected result {0}
 deleteFileFailed=Could not delete file {0}
+deletedOrphanInPackDir=Deleted orphaned file {}
 deleteRequiresZeroNewId=Delete requires new ID to be zero
 deleteTagUnexpectedResult=Delete tag returned unexpected result {0}
 deletingNotSupported=Deleting {0} not supported.
@@ -224,6 +223,8 @@
 dirCacheFileIsNotLocked=DirCache {0} not locked
 dirCacheIsNotLocked=DirCache is not locked
 DIRCChecksumMismatch=DIRC checksum mismatch
+DIRCCorruptLength=DIRC variable int {0} invalid after entry for {1}
+DIRCCorruptLengthFirst=DIRC variable int {0} invalid in first entry
 DIRCExtensionIsTooLargeAt=DIRC extension {0} is too large at {1} bytes.
 DIRCExtensionNotSupportedByThisVersion=DIRC extension {0} not supported by this version.
 DIRCHasTooManyEntries=DIRC has too many entries.
@@ -347,6 +348,9 @@
 invalidGitdirRef = Invalid .git reference in file ''{0}''
 invalidGitModules=Invalid .gitmodules file
 invalidGitType=invalid git type: {0}
+invalidHeaderFormat=Invalid header from git config http.extraHeader ignored: no colon or empty key in header ''{0}''
+invalidHeaderKey=Invalid header from git config http.extraHeader ignored: key contains illegal characters; see RFC 7230: ''{0}''
+invalidHeaderValue=Invalid header from git config http.extraHeader ignored: value should be 7bit-ASCII characters only: ''{0}''
 invalidHexString=Invalid hex string: {0}
 invalidHomeDirectory=Invalid home directory: {0}
 invalidHooksPath=Invalid git config core.hooksPath = {0}
@@ -408,6 +412,10 @@
 lockFailedRetry=locking {0} failed after {1} retries
 lockOnNotClosed=Lock on {0} not closed.
 lockOnNotHeld=Lock on {0} not held.
+logInconsistentFiletimeDiff={}: inconsistent duration from file timestamps on {}, {}: {} > {}, but diff = {}. Aborting measurement at resolution {}.
+logLargerFiletimeDiff={}: inconsistent duration from file timestamps on {}, {}: diff = {} > {} (last good value). Aborting measurement.
+logSmallerFiletime={}: got smaller file timestamp on {}, {}: {} < {}. Aborting measurement at resolution {}.
+logXDGConfigHomeInvalid=Environment variable XDG_CONFIG_HOME contains an invalid path {}
 maxCountMustBeNonNegative=max count must be >= 0
 mergeConflictOnNonNoteEntries=Merge conflict on non-note entries: base = {0}, ours = {1}, theirs = {2}
 mergeConflictOnNotes=Merge conflict on note {0}. base = {1}, ours = {2}, theirs = {2}
@@ -621,6 +629,7 @@
 sourceRefNotSpecifiedForRefspec=Source ref not specified for refspec: {0}
 squashCommitNotUpdatingHEAD=Squash commit -- not updating HEAD
 sshCommandFailed=Execution of ssh command ''{0}'' failed with error ''{1}''
+sshCommandTimeout=Execution of ssh command ''{0}'' timed out after {1} seconds
 sslFailureExceptionMessage=Secure connection to {0} could not be established because of SSL problems
 sslFailureInfo=A secure connection to {0} could not be established because the server''s certificate could not be validated.
 sslFailureCause=SSL reported: {0}
@@ -661,7 +670,7 @@
 timeIsUncertain=Time is uncertain
 timerAlreadyTerminated=Timer already terminated
 timeoutMeasureFsTimestampResolution=measuring filesystem timestamp resolution for ''{0}'' timed out, fall back to resolution of 2 seconds
-tooManyCommands=Too many commands
+tooManyCommands=Commands size exceeds limit defined in receive.maxCommandBytes
 tooManyFilters=Too many "filter" lines in request
 tooManyIncludeRecursions=Too many recursions; circular includes in config file(s)?
 topologicalSortRequired=Topological sort required.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
index 0dc5d5e..847ab0a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2010, Chris Aniszczyk <caniszczyk@gmail.com>
- * Copyright (C) 2011, Matthias Sohn <matthias.sohn@sap.com> and others
+ * Copyright (C) 2011, 2020 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
@@ -503,6 +503,11 @@
 			editor.add(new PathEdit(path) {
 				@Override
 				public void apply(DirCacheEntry ent) {
+					if (ent.getStage() != DirCacheEntry.STAGE_0) {
+						// A checkout on a conflicting file stages the checked
+						// out file and resolves the conflict.
+						ent.setStage(DirCacheEntry.STAGE_0);
+					}
 					ent.setObjectId(blobId);
 					ent.setFileMode(mode);
 					checkoutPath(ent, r,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
index 30d7f9a..aba86fc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
@@ -413,6 +413,10 @@
 			return null;
 		}
 
+		if (idHEAD != null && idHEAD.isSymbolic()) {
+			return idHEAD.getTarget();
+		}
+
 		Ref master = result.getAdvertisedRef(Constants.R_HEADS
 				+ Constants.MASTER);
 		ObjectId objectId = master != null ? master.getObjectId() : null;
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 8d42738..6a9fbd4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
@@ -9,6 +9,7 @@
  */
 package org.eclipse.jgit.api;
 
+import static org.eclipse.jgit.lib.Constants.R_REFS;
 import static org.eclipse.jgit.lib.Constants.R_TAGS;
 
 import java.io.IOException;
@@ -73,6 +74,11 @@
 	private List<FileNameMatcher> matchers = new ArrayList<>();
 
 	/**
+	 * Whether to use all refs in the refs/ namespace
+	 */
+	private boolean useAll;
+
+	/**
 	 * Whether to use all tags (incl. lightweight) or not.
 	 */
 	private boolean useTags;
@@ -153,6 +159,22 @@
 	}
 
 	/**
+	 * Instead of using only the annotated tags, use any ref found in refs/
+	 * namespace. This option enables matching any known branch,
+	 * remote-tracking branch, or lightweight tag.
+	 *
+	 * @param all
+	 *            <code>true</code> enables matching any ref found in refs/
+	 *            like setting option --all in c git
+	 * @return {@code this}
+	 * @since 5.10
+	 */
+	public DescribeCommand setAll(boolean all) {
+		this.useAll = all;
+		return this;
+	}
+
+	/**
 	 * Instead of using only the annotated tags, use any tag found in refs/tags
 	 * namespace. This option enables matching lightweight (non-annotated) tags
 	 * or not.
@@ -186,7 +208,7 @@
 	private String longDescription(Ref tag, int depth, ObjectId tip)
 			throws IOException {
 		return String.format(
-				"%s-%d-g%s", tag.getName().substring(R_TAGS.length()), //$NON-NLS-1$
+				"%s-%d-g%s", formatRefName(tag.getName()), //$NON-NLS-1$
 				Integer.valueOf(depth), w.getObjectReader().abbreviate(tip)
 						.name());
 	}
@@ -244,8 +266,7 @@
 			for (FileNameMatcher matcher : matchers) {
 				Stream<Ref> m = tags.stream().filter(
 						tag -> {
-							matcher.append(
-									tag.getName().substring(R_TAGS.length()));
+							matcher.append(formatRefName(tag.getName()));
 							boolean result = matcher.isMatch();
 							matcher.reset();
 							return result;
@@ -283,7 +304,7 @@
 			}
 
 			Collection<Ref> tagList = repo.getRefDatabase()
-					.getRefsByPrefix(R_TAGS);
+					.getRefsByPrefix(useAll ? R_REFS : R_TAGS);
 			Map<ObjectId, List<Ref>> tags = tagList.stream()
 					.filter(this::filterLightweightTags)
 					.collect(Collectors.groupingBy(this::getObjectIdFromRef));
@@ -336,7 +357,7 @@
 			Optional<Ref> bestMatch = getBestMatch(tags.get(target));
 			if (bestMatch.isPresent()) {
 				return longDesc ? longDescription(bestMatch.get(), 0, target) :
-						bestMatch.get().getName().substring(R_TAGS.length());
+						formatRefName(bestMatch.get().getName());
 			}
 
 			w.markStart(target);
@@ -408,6 +429,16 @@
 	}
 
 	/**
+	 * Removes the refs/ or refs/tags prefix from tag names
+	 * @param name the name of the tag
+	 * @return the tag name with its prefix removed
+	 */
+	private String formatRefName(String name) {
+		return name.startsWith(R_TAGS) ? name.substring(R_TAGS.length()) :
+				name.substring(R_REFS.length());
+	}
+
+	/**
 	 * Whether we use lightweight tags or not for describe Candidates
 	 *
 	 * @param ref
@@ -419,7 +450,7 @@
 	private boolean filterLightweightTags(Ref ref) {
 		ObjectId id = ref.getObjectId();
 		try {
-			return this.useTags || (id != null && (w.parseTag(id) != null));
+			return this.useAll || this.useTags || (id != null && (w.parseTag(id) != null));
 		} catch (IOException e) {
 			return false;
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitCommand.java
index ac17d3a..f4b8ac2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitCommand.java
@@ -20,8 +20,10 @@
 import java.util.List;
 
 import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.InvalidConfigurationException;
 import org.eclipse.jgit.api.errors.JGitInternalException;
 import org.eclipse.jgit.api.errors.NoHeadException;
+import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.ObjectId;
@@ -119,6 +121,8 @@
 				}
 			}
 			return results;
+		} catch (ConfigInvalidException e) {
+			throw new InvalidConfigurationException(e.getMessage(), e);
 		} catch (IOException e) {
 			throw new JGitInternalException(e.getMessage(), e);
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java
index 7ae005a..1a41df3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010, Google Inc. and others
+ * Copyright (C) 2010, 2020 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
@@ -123,7 +123,8 @@
 		WorkingTreeIterator ptr;
 
 		WorkingTreeSource(WorkingTreeIterator iterator) {
-			this.tw = new TreeWalk((ObjectReader) null);
+			this.tw = new TreeWalk(iterator.getRepository(),
+					(ObjectReader) null);
 			this.tw.setRecursive(true);
 			this.iterator = iterator;
 		}
@@ -173,6 +174,15 @@
 		private void seek(String path) throws IOException {
 			if (!path.equals(current)) {
 				iterator.reset();
+				// Possibly this iterator had an associated DirCacheIterator,
+				// but we have no access to it and thus don't know about it.
+				// We have to reset this iterator here to work without
+				// DirCacheIterator and to descend always into ignored
+				// directories. Otherwise we might not find tracked files below
+				// ignored folders. Since we're looking only for a single
+				// specific path this is not a performance problem.
+				iterator.setWalkIgnoredDirectories(true);
+				iterator.setDirCacheIterator(null, -1);
 				tw.reset();
 				tw.addTree(iterator);
 				tw.setFilter(PathFilter.create(path));
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 81367ea..ec21954 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2009, Google Inc.
- * Copyright (C) 2008-2009, Johannes E. Schindelin <johannes.schindelin@gmx.de> and others
+ * Copyright (C) 2008-2020, Johannes E. Schindelin <johannes.schindelin@gmx.de> 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
@@ -502,9 +502,18 @@
 			throws IOException {
 		assertHaveReader();
 
-		TreeWalk walk = new TreeWalk(reader);
-		walk.addTree(a);
-		walk.addTree(b);
+		TreeWalk walk = new TreeWalk(repository, reader);
+		int aIndex = walk.addTree(a);
+		int bIndex = walk.addTree(b);
+		if (repository != null) {
+			if (a instanceof WorkingTreeIterator
+					&& b instanceof DirCacheIterator) {
+				((WorkingTreeIterator) a).setDirCacheIterator(walk, bIndex);
+			} else if (b instanceof WorkingTreeIterator
+					&& a instanceof DirCacheIterator) {
+				((WorkingTreeIterator) b).setDirCacheIterator(walk, aIndex);
+			}
+		}
 		walk.setRecursive(true);
 
 		TreeFilter filter = getDiffTreeFilterFor(a, b);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
index b2764d7..03da615 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
@@ -1,7 +1,7 @@
 /*
- * Copyright (C) 2008-2010, Google Inc.
+ * Copyright (C) 2008, 2010, Google Inc.
  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
- * Copyright (C) 2011, Matthias Sohn <matthias.sohn@sap.com> and others
+ * Copyright (C) 2011, 2020, 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
@@ -41,6 +41,9 @@
 import org.eclipse.jgit.internal.storage.file.FileSnapshot;
 import org.eclipse.jgit.internal.storage.file.LockFile;
 import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Config.ConfigEnum;
+import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectInserter;
@@ -321,6 +324,9 @@
 	/** Repository containing this index */
 	private Repository repository;
 
+	/** If we read this index from disk, the original format. */
+	private DirCacheVersion version;
+
 	/**
 	 * Create a new in-core index representation.
 	 * <p>
@@ -364,6 +370,10 @@
 		return new DirCacheEditor(this, entryCnt + 16);
 	}
 
+	DirCacheVersion getVersion() {
+		return version;
+	}
+
 	void replace(DirCacheEntry[] e, int cnt) {
 		sortedEntries = e;
 		entryCnt = cnt;
@@ -445,13 +455,26 @@
 		md.update(hdr, 0, 12);
 		if (!is_DIRC(hdr))
 			throw new CorruptObjectException(JGitText.get().notADIRCFile);
-		final int ver = NB.decodeInt32(hdr, 4);
+		int versionCode = NB.decodeInt32(hdr, 4);
+		DirCacheVersion ver = DirCacheVersion.fromInt(versionCode);
+		if (ver == null) {
+			throw new CorruptObjectException(
+					MessageFormat.format(JGitText.get().unknownDIRCVersion,
+							Integer.valueOf(versionCode)));
+		}
 		boolean extended = false;
-		if (ver == 3)
+		switch (ver) {
+		case DIRC_VERSION_MINIMUM:
+			break;
+		case DIRC_VERSION_EXTENDED:
+		case DIRC_VERSION_PATHCOMPRESS:
 			extended = true;
-		else if (ver != 2)
-			throw new CorruptObjectException(MessageFormat.format(
-					JGitText.get().unknownDIRCVersion, Integer.valueOf(ver)));
+			break;
+		default:
+			throw new CorruptObjectException(MessageFormat
+					.format(JGitText.get().unknownDIRCVersion, ver));
+		}
+		version = ver;
 		entryCnt = NB.decodeInt32(hdr, 8);
 		if (entryCnt < 0)
 			throw new CorruptObjectException(JGitText.get().DIRCHasTooManyEntries);
@@ -467,7 +490,8 @@
 
 		final MutableInteger infoAt = new MutableInteger();
 		for (int i = 0; i < entryCnt; i++) {
-			sortedEntries[i] = new DirCacheEntry(infos, infoAt, in, md, smudge);
+			sortedEntries[i] = new DirCacheEntry(infos, infoAt, in, md, smudge,
+					version, i == 0 ? null : sortedEntries[i - 1]);
 		}
 
 		// After the file entries are index extensions, and then a footer.
@@ -606,11 +630,20 @@
 		final MessageDigest foot = Constants.newMessageDigest();
 		final DigestOutputStream dos = new DigestOutputStream(os, foot);
 
-		boolean extended = false;
-		for (int i = 0; i < entryCnt; i++) {
-			if (sortedEntries[i].isExtended()) {
-				extended = true;
-				break;
+		if (version == null && this.repository != null) {
+			// A new DirCache is being written.
+			DirCacheConfig config = repository.getConfig()
+					.get(DirCacheConfig::new);
+			version = config.getIndexVersion();
+		}
+		if (version == null
+				|| version == DirCacheVersion.DIRC_VERSION_MINIMUM) {
+			version = DirCacheVersion.DIRC_VERSION_MINIMUM;
+			for (int i = 0; i < entryCnt; i++) {
+				if (sortedEntries[i].isExtended()) {
+					version = DirCacheVersion.DIRC_VERSION_EXTENDED;
+					break;
+				}
 			}
 		}
 
@@ -618,7 +651,7 @@
 		//
 		final byte[] tmp = new byte[128];
 		System.arraycopy(SIG_DIRC, 0, tmp, 0, SIG_DIRC.length);
-		NB.encodeInt32(tmp, 4, extended ? 3 : 2);
+		NB.encodeInt32(tmp, 4, version.getVersionCode());
 		NB.encodeInt32(tmp, 8, entryCnt);
 		dos.write(tmp, 0, 12);
 
@@ -650,7 +683,7 @@
 			if (e.mightBeRacilyClean(smudge)) {
 				e.smudgeRacilyClean();
 			}
-			e.write(dos);
+			e.write(dos, version, i == 0 ? null : sortedEntries[i - 1]);
 		}
 
 		if (writeTree) {
@@ -982,4 +1015,76 @@
 			}
 		}
 	}
+
+	enum DirCacheVersion implements ConfigEnum {
+
+		/** Minimum index version on-disk format that we support. */
+		DIRC_VERSION_MINIMUM(2),
+		/** Version 3 supports extended flags. */
+		DIRC_VERSION_EXTENDED(3),
+		/**
+		 * Version 4 adds very simple "path compression": it strips out the
+		 * common prefix between the last entry written and the current entry.
+		 * Instead of writing two entries with paths "foo/bar/baz/a.txt" and
+		 * "foo/bar/baz/b.txt" it only writes "b.txt" for the second entry.
+		 * <p>
+		 * It is also <em>not</em> padded.
+		 * </p>
+		 */
+		DIRC_VERSION_PATHCOMPRESS(4);
+
+		private final int version;
+
+		private DirCacheVersion(int versionCode) {
+			this.version = versionCode;
+		}
+
+		public int getVersionCode() {
+			return version;
+		}
+
+		@Override
+		public String toConfigValue() {
+			return Integer.toString(version);
+		}
+
+		@Override
+		public boolean matchConfigValue(String in) {
+			try {
+				return version == Integer.parseInt(in);
+			} catch (NumberFormatException e) {
+				return false;
+			}
+		}
+
+		public static DirCacheVersion fromInt(int val) {
+			for (DirCacheVersion v : DirCacheVersion.values()) {
+				if (val == v.getVersionCode()) {
+					return v;
+				}
+			}
+			return null;
+		}
+	}
+
+	private static class DirCacheConfig {
+
+		private final DirCacheVersion indexVersion;
+
+		public DirCacheConfig(Config cfg) {
+			boolean manyFiles = cfg.getBoolean(
+					ConfigConstants.CONFIG_FEATURE_SECTION,
+					ConfigConstants.CONFIG_KEY_MANYFILES, false);
+			indexVersion = cfg.getEnum(DirCacheVersion.values(),
+					ConfigConstants.CONFIG_INDEX_SECTION, null,
+					ConfigConstants.CONFIG_KEY_VERSION,
+					manyFiles ? DirCacheVersion.DIRC_VERSION_PATHCOMPRESS
+							: DirCacheVersion.DIRC_VERSION_EXTENDED);
+		}
+
+		public DirCacheVersion getIndexVersion() {
+			return indexVersion;
+		}
+
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java
index 73d2807..8c342e2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.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, 2020 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,10 +139,28 @@
 					: eIdx;
 				fastAdd(ent);
 			} else {
-				// Apply to all entries of the current path (different stages)
 				lastIdx = cache.nextEntry(eIdx);
-				for (int i = eIdx; i < lastIdx; i++) {
-					final DirCacheEntry ent = cache.getEntry(i);
+				if (lastIdx > eIdx + 1) {
+					// Apply to all entries of the current path (different
+					// stages). If any apply() resets the stage to STAGE_0, take
+					// only that entry and omit all others.
+					DirCacheEntry[] tmp = new DirCacheEntry[lastIdx - eIdx];
+					int n = 0;
+					for (int i = eIdx; i < lastIdx; i++) {
+						DirCacheEntry ent = cache.getEntry(i);
+						e.apply(ent);
+						if (ent.getStage() == DirCacheEntry.STAGE_0) {
+							fastAdd(ent);
+							n = 0;
+							break;
+						}
+						tmp[n++] = ent;
+					}
+					for (int i = 0; i < n; i++) {
+						fastAdd(tmp[i]);
+					}
+				} else {
+					DirCacheEntry ent = cache.getEntry(eIdx);
 					e.apply(ent);
 					fastAdd(ent);
 				}
@@ -257,7 +275,9 @@
 	 * {@link #apply(DirCacheEntry)} method. The editor will invoke apply once
 	 * for each record in the index which matches the path name. If there are
 	 * multiple records (for example in stages 1, 2 and 3), the edit instance
-	 * will be called multiple times, once for each stage.
+	 * will be called multiple times, once for each stage. If any of these calls
+	 * resets the stage to 0, only this entry will be taken and entries for
+	 * other stages are discarded.
 	 */
 	public abstract static class PathEdit {
 		final byte[] path;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
index ced379f..67edf50 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
@@ -1,8 +1,8 @@
 /*
- * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008, 2009, Google Inc.
  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
  * Copyright (C) 2010, Matthias Sohn <matthias.sohn@sap.com>
- * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com> and others
+ * Copyright (C) 2010, 2020, 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
@@ -26,6 +26,7 @@
 import java.time.Instant;
 import java.util.Arrays;
 
+import org.eclipse.jgit.dircache.DirCache.DirCacheVersion;
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.AnyObjectId;
@@ -112,15 +113,16 @@
 	/** Flags which are never stored to disk. */
 	private byte inCoreFlags;
 
-	DirCacheEntry(final byte[] sharedInfo, final MutableInteger infoAt,
-			final InputStream in, final MessageDigest md, final Instant smudge)
+	DirCacheEntry(byte[] sharedInfo, MutableInteger infoAt, InputStream in,
+			MessageDigest md, Instant smudge, DirCacheVersion version,
+			DirCacheEntry previous)
 			throws IOException {
 		info = sharedInfo;
 		infoOffset = infoAt.value;
 
 		IO.readFully(in, info, infoOffset, INFO_LEN);
 
-		final int len;
+		int len;
 		if (isExtended()) {
 			len = INFO_LEN_EXTENDED;
 			IO.readFully(in, info, infoOffset + INFO_LEN, INFO_LEN_EXTENDED - INFO_LEN);
@@ -134,31 +136,66 @@
 		infoAt.value += len;
 		md.update(info, infoOffset, len);
 
+		int toRemove = 0;
+		if (version == DirCacheVersion.DIRC_VERSION_PATHCOMPRESS) {
+			// Read variable int and update digest
+			int b = in.read();
+			md.update((byte) b);
+			toRemove = b & 0x7F;
+			while ((b & 0x80) != 0) {
+				toRemove++;
+				b = in.read();
+				md.update((byte) b);
+				toRemove = (toRemove << 7) | (b & 0x7F);
+			}
+			if (toRemove < 0
+					|| (previous != null && toRemove > previous.path.length)) {
+				if (previous == null) {
+					throw new IOException(MessageFormat.format(
+							JGitText.get().DIRCCorruptLengthFirst,
+							Integer.valueOf(toRemove)));
+				}
+				throw new IOException(MessageFormat.format(
+						JGitText.get().DIRCCorruptLength,
+						Integer.valueOf(toRemove), previous.getPathString()));
+			}
+		}
 		int pathLen = NB.decodeUInt16(info, infoOffset + P_FLAGS) & NAME_MASK;
 		int skipped = 0;
 		if (pathLen < NAME_MASK) {
 			path = new byte[pathLen];
-			IO.readFully(in, path, 0, pathLen);
-			md.update(path, 0, pathLen);
-		} else {
-			final ByteArrayOutputStream tmp = new ByteArrayOutputStream();
-			{
-				final byte[] buf = new byte[NAME_MASK];
-				IO.readFully(in, buf, 0, NAME_MASK);
-				tmp.write(buf);
+			if (version == DirCacheVersion.DIRC_VERSION_PATHCOMPRESS
+					&& previous != null) {
+				System.arraycopy(previous.path, 0, path, 0,
+						previous.path.length - toRemove);
+				IO.readFully(in, path, previous.path.length - toRemove,
+						pathLen - (previous.path.length - toRemove));
+				md.update(path, previous.path.length - toRemove,
+						pathLen - (previous.path.length - toRemove));
+				pathLen = pathLen - (previous.path.length - toRemove);
+			} else {
+				IO.readFully(in, path, 0, pathLen);
+				md.update(path, 0, pathLen);
 			}
-			for (;;) {
-				final int c = in.read();
-				if (c < 0)
-					throw new EOFException(JGitText.get().shortReadOfBlock);
-				if (c == 0)
-					break;
-				tmp.write(c);
-			}
+		} else if (version != DirCacheVersion.DIRC_VERSION_PATHCOMPRESS
+				|| previous == null || toRemove == previous.path.length) {
+			ByteArrayOutputStream tmp = new ByteArrayOutputStream();
+			byte[] buf = new byte[NAME_MASK];
+			IO.readFully(in, buf, 0, NAME_MASK);
+			tmp.write(buf);
+			readNulTerminatedString(in, tmp);
 			path = tmp.toByteArray();
 			pathLen = path.length;
-			skipped = 1; // we already skipped 1 '\0' above to break the loop.
 			md.update(path, 0, pathLen);
+			skipped = 1; // we already skipped 1 '\0' in readNulTerminatedString
+			md.update((byte) 0);
+		} else {
+			ByteArrayOutputStream tmp = new ByteArrayOutputStream();
+			tmp.write(previous.path, 0, previous.path.length - toRemove);
+			pathLen = readNulTerminatedString(in, tmp);
+			path = tmp.toByteArray();
+			md.update(path, previous.path.length - toRemove, pathLen);
+			skipped = 1; // we already skipped 1 '\0' in readNulTerminatedString
 			md.update((byte) 0);
 		}
 
@@ -172,17 +209,26 @@
 			throw p;
 		}
 
-		// Index records are padded out to the next 8 byte alignment
-		// for historical reasons related to how C Git read the files.
-		//
-		final int actLen = len + pathLen;
-		final int expLen = (actLen + 8) & ~7;
-		final int padLen = expLen - actLen - skipped;
-		if (padLen > 0) {
-			IO.skipFully(in, padLen);
-			md.update(nullpad, 0, padLen);
+		if (version == DirCacheVersion.DIRC_VERSION_PATHCOMPRESS) {
+			if (skipped == 0) {
+				int b = in.read();
+				if (b < 0) {
+					throw new EOFException(JGitText.get().shortReadOfBlock);
+				}
+				md.update((byte) b);
+			}
+		} else {
+			// Index records are padded out to the next 8 byte alignment
+			// for historical reasons related to how C Git read the files.
+			//
+			final int actLen = len + pathLen;
+			final int expLen = (actLen + 8) & ~7;
+			final int padLen = expLen - actLen - skipped;
+			if (padLen > 0) {
+				IO.skipFully(in, padLen);
+				md.update(nullpad, 0, padLen);
+			}
 		}
-
 		if (mightBeRacilyClean(smudge)) {
 			smudgeRacilyClean();
 		}
@@ -283,19 +329,61 @@
 		System.arraycopy(src.info, src.infoOffset, info, 0, INFO_LEN);
 	}
 
-	void write(OutputStream os) throws IOException {
-		final int len = isExtended() ? INFO_LEN_EXTENDED : INFO_LEN;
-		final int pathLen = path.length;
-		os.write(info, infoOffset, len);
-		os.write(path, 0, pathLen);
+	private int readNulTerminatedString(InputStream in, OutputStream out)
+			throws IOException {
+		int n = 0;
+		for (;;) {
+			int c = in.read();
+			if (c < 0) {
+				throw new EOFException(JGitText.get().shortReadOfBlock);
+			}
+			if (c == 0) {
+				break;
+			}
+			out.write(c);
+			n++;
+		}
+		return n;
+	}
 
-		// Index records are padded out to the next 8 byte alignment
-		// for historical reasons related to how C Git read the files.
-		//
-		final int actLen = len + pathLen;
-		final int expLen = (actLen + 8) & ~7;
-		if (actLen != expLen)
-			os.write(nullpad, 0, expLen - actLen);
+	void write(OutputStream os, DirCacheVersion version, DirCacheEntry previous)
+			throws IOException {
+		final int len = isExtended() ? INFO_LEN_EXTENDED : INFO_LEN;
+		if (version != DirCacheVersion.DIRC_VERSION_PATHCOMPRESS) {
+			os.write(info, infoOffset, len);
+			os.write(path, 0, path.length);
+			// Index records are padded out to the next 8 byte alignment
+			// for historical reasons related to how C Git read the files.
+			//
+			int entryLen = len + path.length;
+			int expLen = (entryLen + 8) & ~7;
+			if (entryLen != expLen)
+				os.write(nullpad, 0, expLen - entryLen);
+		} else {
+			int pathCommon = 0;
+			int toRemove;
+			if (previous != null) {
+				// Figure out common prefix
+				int pathLen = Math.min(path.length, previous.path.length);
+				while (pathCommon < pathLen
+						&& path[pathCommon] == previous.path[pathCommon]) {
+					pathCommon++;
+				}
+				toRemove = previous.path.length - pathCommon;
+			} else {
+				toRemove = 0;
+			}
+			byte[] tmp = new byte[16];
+			int n = tmp.length;
+			tmp[--n] = (byte) (toRemove & 0x7F);
+			while ((toRemove >>>= 7) != 0) {
+				tmp[--n] = (byte) (0x80 | (--toRemove & 0x7F));
+			}
+			os.write(info, infoOffset, len);
+			os.write(tmp, n, tmp.length - n);
+			os.write(path, pathCommon, path.length - pathCommon);
+			os.write(0);
+		}
 	}
 
 	/**
@@ -454,6 +542,24 @@
 	}
 
 	/**
+	 * Sets the stage of an entry.
+	 *
+	 * @param stage
+	 *            to set, in the range [0..3]
+	 * @throws IllegalArgumentException
+	 *             if the stage is outside the range [0..3]
+	 * @since 5.10
+	 */
+	public void setStage(int stage) {
+		if ((stage & ~0x3) != 0) {
+			throw new IllegalArgumentException(
+					"Invalid stage, must be in range [0..3]"); //$NON-NLS-1$
+		}
+		byte flags = info[infoOffset + P_FLAGS];
+		info[infoOffset + P_FLAGS] = (byte) ((flags & 0xCF) | (stage << 4));
+	}
+
+	/**
 	 * Returns whether this entry should be skipped from the working tree.
 	 *
 	 * @return true if this entry should be skipepd.
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 782a3f8..892657d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -230,10 +230,8 @@
 	/***/ public String countingObjects;
 	/***/ public String createBranchFailedUnknownReason;
 	/***/ public String createBranchUnexpectedResult;
-	/***/ public String createJGitConfigFailed;
 	/***/ public String createNewFileFailed;
 	/***/ public String createRequiresZeroOldId;
-	/***/ public String createXDGConfigHomeFailed;
 	/***/ public String credentialPassword;
 	/***/ public String credentialPassphrase;
 	/***/ public String credentialUsername;
@@ -243,6 +241,7 @@
 	/***/ public String deepenSinceWithDeepen;
 	/***/ public String deleteBranchUnexpectedResult;
 	/***/ public String deleteFileFailed;
+	/***/ public String deletedOrphanInPackDir;
 	/***/ public String deleteRequiresZeroNewId;
 	/***/ public String deleteTagUnexpectedResult;
 	/***/ public String deletingNotSupported;
@@ -252,6 +251,8 @@
 	/***/ public String dirCacheFileIsNotLocked;
 	/***/ public String dirCacheIsNotLocked;
 	/***/ public String DIRCChecksumMismatch;
+	/***/ public String DIRCCorruptLength;
+	/***/ public String DIRCCorruptLengthFirst;
 	/***/ public String DIRCExtensionIsTooLargeAt;
 	/***/ public String DIRCExtensionNotSupportedByThisVersion;
 	/***/ public String DIRCHasTooManyEntries;
@@ -375,6 +376,9 @@
 	/***/ public String invalidGitdirRef;
 	/***/ public String invalidGitModules;
 	/***/ public String invalidGitType;
+	/***/ public String invalidHeaderFormat;
+	/***/ public String invalidHeaderKey;
+	/***/ public String invalidHeaderValue;
 	/***/ public String invalidHexString;
 	/***/ public String invalidHomeDirectory;
 	/***/ public String invalidHooksPath;
@@ -436,6 +440,10 @@
 	/***/ public String lockFailedRetry;
 	/***/ public String lockOnNotClosed;
 	/***/ public String lockOnNotHeld;
+	/***/ public String logInconsistentFiletimeDiff;
+	/***/ public String logLargerFiletimeDiff;
+	/***/ public String logSmallerFiletime;
+	/***/ public String logXDGConfigHomeInvalid;
 	/***/ public String maxCountMustBeNonNegative;
 	/***/ public String mergeConflictOnNonNoteEntries;
 	/***/ public String mergeConflictOnNotes;
@@ -649,6 +657,7 @@
 	/***/ public String sourceRefNotSpecifiedForRefspec;
 	/***/ public String squashCommitNotUpdatingHEAD;
 	/***/ public String sshCommandFailed;
+	/***/ public String sshCommandTimeout;
 	/***/ public String sslFailureExceptionMessage;
 	/***/ public String sslFailureInfo;
 	/***/ public String sslFailureCause;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBundleWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBundleWriter.java
new file mode 100644
index 0000000..736f381
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBundleWriter.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2020, Google LLC  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
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.storage.dfs;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jgit.internal.storage.pack.CachedPack;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.transport.BundleWriter;
+
+/** Writes {@link DfsRepository} to a Git bundle. */
+public class DfsBundleWriter {
+	/**
+	 * Writes the entire {@link DfsRepository} to a Git bundle.
+	 * <p>
+	 * This method try to avoid traversing the pack files as much as possible
+	 * and dumps all objects as-is to a Git bundle.
+	 *
+	 * @param pm
+	 *            progress monitor
+	 * @param os
+	 *            Git bundle output
+	 * @param db
+	 *            repository
+	 * @throws IOException
+	 *             thrown if the output stream throws one.
+	 */
+	public static void writeEntireRepositoryAsBundle(ProgressMonitor pm,
+			OutputStream os, DfsRepository db) throws IOException {
+		BundleWriter bw = new BundleWriter(db);
+		db.getRefDatabase().getRefs().forEach(bw::include);
+		List<CachedPack> packs = new ArrayList<>();
+		for (DfsPackFile p : db.getObjectDatabase().getPacks()) {
+			packs.add(new DfsCachedPack(p));
+		}
+		bw.addObjectsAsIs(packs);
+		bw.writeBundle(pm, os);
+	}
+
+	private DfsBundleWriter() {
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackBitmapIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackBitmapIndex.java
index c9bb167..ec53818 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackBitmapIndex.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackBitmapIndex.java
@@ -59,12 +59,26 @@
 		 * @return the full bitmap
 		 */
 		EWAHCompressedBitmap getBitmap() {
+			EWAHCompressedBitmap bitmap = getBitmapWithoutCaching();
+			// Cache the result.
+			bitmapContainer = bitmap;
+			return bitmap;
+		}
+
+		/**
+		 * Compute and return the full bitmap, do NOT cache the expanded bitmap,
+		 * which saves memory and should only be used during bitmap creation in
+		 * garbage collection.
+		 *
+		 * @return the full bitmap
+		 */
+		EWAHCompressedBitmap getBitmapWithoutCaching() {
 			// Fast path to immediately return the expanded result.
 			Object r = bitmapContainer;
 			if (r instanceof EWAHCompressedBitmap)
 				return (EWAHCompressedBitmap) r;
 
-			// Expand the bitmap and cache the result.
+			// Expand the bitmap but not cache the result.
 			XorCompressedBitmap xb = (XorCompressedBitmap) r;
 			EWAHCompressedBitmap out = xb.bitmap;
 			for (;;) {
@@ -72,7 +86,6 @@
 				if (r instanceof EWAHCompressedBitmap) {
 					out = out.xor((EWAHCompressedBitmap) r);
 					out.trim();
-					bitmapContainer = out;
 					return out;
 				}
 				xb = (XorCompressedBitmap) r;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
index 0899578..1f2fe10 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
@@ -115,6 +115,8 @@
 
 	private static final String INDEX_EXT = "." + PackExt.INDEX.getExtension(); //$NON-NLS-1$
 
+	private static final String KEEP_EXT = "." + PackExt.KEEP.getExtension(); //$NON-NLS-1$
+
 	private static final int DEFAULT_AUTOPACKLIMIT = 50;
 
 	private static final int DEFAULT_AUTOLIMIT = 6700;
@@ -961,11 +963,15 @@
 			fileNames = files.map(path -> path.getFileName().toString())
 					.filter(name -> (name.endsWith(PACK_EXT)
 							|| name.endsWith(BITMAP_EXT)
-							|| name.endsWith(INDEX_EXT)))
+							|| name.endsWith(INDEX_EXT)
+							|| name.endsWith(KEEP_EXT)))
+					// sort files with same base name in the order:
+					// .pack, .keep, .index, .bitmap to avoid look ahead
 					.sorted(Collections.reverseOrder())
 					.collect(Collectors.toList());
-		} catch (IOException e1) {
-			// ignore
+		} catch (IOException e) {
+			LOG.error(e.getMessage(), e);
+			return;
 		}
 		if (fileNames == null) {
 			return;
@@ -973,12 +979,15 @@
 
 		String base = null;
 		for (String n : fileNames) {
-			if (n.endsWith(PACK_EXT)) {
+			if (n.endsWith(PACK_EXT) || n.endsWith(KEEP_EXT)) {
 				base = n.substring(0, n.lastIndexOf('.'));
 			} else {
 				if (base == null || !n.startsWith(base)) {
 					try {
-						Files.delete(packDir.resolve(n));
+						Path delete = packDir.resolve(n);
+						FileUtils.delete(delete.toFile(),
+								FileUtils.RETRY | FileUtils.SKIP_MISSING);
+						LOG.warn(JGitText.get().deletedOrphanInPackDir, delete);
 					} catch (IOException e) {
 						LOG.error(e.getMessage(), e);
 					}
@@ -1165,6 +1174,7 @@
 			// create temporary files
 			String id = pw.computeName().getName();
 			File packdir = repo.getObjectDatabase().getPackDirectory();
+			packdir.mkdirs();
 			tmpPack = File.createTempFile("gc_", ".pack_tmp", packdir); //$NON-NLS-1$ //$NON-NLS-2$
 			final String tmpBase = tmpPack.getName()
 					.substring(0, tmpPack.getName().lastIndexOf('.'));
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 265b71d..d321828 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
@@ -580,7 +580,7 @@
 
 	@Override
 	void selectObjectRepresentation(PackWriter packer, ObjectToPack otp,
-																	WindowCursor curs) throws IOException {
+			WindowCursor curs) throws IOException {
 		selectObjectRepresentation(packer, otp, curs, null);
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java
index 4b25284..dd5d03c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java
@@ -156,7 +156,8 @@
 			return null;
 
 		inflated.clear();
-		for (IntIterator i = oldBitmap.getBitmap().intIterator(); i.hasNext();)
+		for (IntIterator i = oldBitmap.getBitmapWithoutCaching()
+				.intIterator(); i.hasNext();)
 			inflated.set(prevToNewMapping[i.next()]);
 		bitmap = inflated.toEWAHCompressedBitmap();
 		bitmap.trim();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java
index 9cf95d0..eb0ac6a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java
@@ -49,11 +49,11 @@
 			idxHeader[k] = NB.decodeUInt32(fanoutTable, k * 4);
 		idxdata = new byte[idxHeader.length][];
 		for (int k = 0; k < idxHeader.length; k++) {
-			int n;
+			long n;
 			if (k == 0) {
-				n = (int) (idxHeader[k]);
+				n = idxHeader[k];
 			} else {
-				n = (int) (idxHeader[k] - idxHeader[k - 1]);
+				n = idxHeader[k] - idxHeader[k - 1];
 			}
 			if (n > 0) {
 				final long len = n * (Constants.OBJECT_ID_LENGTH + 4);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java
index 80c8e10..3e8cb3a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.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, 2020 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
@@ -19,6 +19,7 @@
 import java.util.Random;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.AtomicReferenceArray;
 import java.util.concurrent.atomic.LongAdder;
@@ -376,14 +377,14 @@
 	 * @return the cached instance.
 	 */
 	public static WindowCache getInstance() {
-		return cache;
+		return cache.publishMBeanIfNeeded();
 	}
 
 	static final ByteWindow get(PackFile pack, long offset)
 			throws IOException {
 		final WindowCache c = cache;
 		final ByteWindow r = c.getOrLoad(pack, c.toStart(offset));
-		if (c != cache) {
+		if (c != cache.publishMBeanIfNeeded()) {
 			// The cache was reconfigured while we were using the old one
 			// to load this window. The window is still valid, but our
 			// cache may think its still live. Ensure the window is removed
@@ -433,6 +434,8 @@
 
 	private final StatsRecorderImpl mbean;
 
+	private final AtomicBoolean publishMBean = new AtomicBoolean();
+
 	private boolean useStrongRefs;
 
 	private WindowCache(WindowCacheConfig cfg) {
@@ -470,9 +473,7 @@
 
 		mbean = new StatsRecorderImpl();
 		statsRecorder = mbean;
-		if (cfg.getExposeStatsViaJmx()) {
-			Monitoring.registerMBean(mbean, "block_cache"); //$NON-NLS-1$
-		}
+		publishMBean.set(cfg.getExposeStatsViaJmx());
 
 		if (maxFiles < 1)
 			throw new IllegalArgumentException(JGitText.get().openFilesMustBeAtLeast1);
@@ -480,6 +481,13 @@
 			throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit);
 	}
 
+	private WindowCache publishMBeanIfNeeded() {
+		if (publishMBean.getAndSet(false)) {
+			Monitoring.registerMBean(mbean, "block_cache"); //$NON-NLS-1$
+		}
+		return this;
+	}
+
 	/**
 	 * @return cache statistics for the WindowCache
 	 */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java
index 824c62a..3e4b5df 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java
@@ -756,6 +756,19 @@
 
 	/**
 	 * Prepare the list of objects to be written to the pack stream.
+	 *
+	 * <p>
+	 * PackWriter will concat and write out the specified packs as-is.
+	 *
+	 * @param c
+	 *            cached packs to be written.
+	 */
+	public void preparePack(Collection<? extends CachedPack> c) {
+		cachedPacks.addAll(c);
+	}
+
+	/**
+	 * Prepare the list of objects to be written to the pack stream.
 	 * <p>
 	 * Basing on these 2 sets, another set of objects to put in a pack file is
 	 * created: this set consists of all objects reachable (ancestors) from
@@ -1548,6 +1561,7 @@
 		endPhase(monitor);
 	}
 
+	@SuppressWarnings("Finally")
 	private void parallelDeltaSearch(ProgressMonitor monitor,
 			ObjectToPack[] list, int cnt, int threads) throws IOException {
 		DeltaCache dc = new ThreadSafeDeltaCache(config);
@@ -1569,15 +1583,22 @@
 			// Caller didn't give us a way to run the tasks, spawn up a
 			// temporary thread pool and make sure it tears down cleanly.
 			ExecutorService pool = Executors.newFixedThreadPool(threads);
+			Throwable e1 = null;
 			try {
 				runTasks(pool, pm, taskBlock, errors);
+			} catch (Exception e) {
+				e1 = e;
 			} finally {
 				pool.shutdown();
 				for (;;) {
 					try {
-						if (pool.awaitTermination(60, TimeUnit.SECONDS))
+						if (pool.awaitTermination(60, TimeUnit.SECONDS)) {
 							break;
+						}
 					} catch (InterruptedException e) {
+						if (e1 != null) {
+							e.addSuppressed(e1);
+						}
 						throw new IOException(JGitText
 								.get().packingCancelledDuringObjectsWriting, e);
 					}
@@ -2182,10 +2203,12 @@
 
 		// Check if this object needs to be rejected, doing the cheaper
 		// checks first.
-		boolean reject = filterSpec.getBlobLimit() >= 0 &&
-			type == OBJ_BLOB &&
-			!want.contains(src) &&
-			reader.getObjectSize(src, OBJ_BLOB) > filterSpec.getBlobLimit();
+		boolean reject =
+			(!filterSpec.allowsType(type) && !want.contains(src)) ||
+			(filterSpec.getBlobLimit() >= 0 &&
+				type == OBJ_BLOB &&
+				!want.contains(src) &&
+				reader.getObjectSize(src, OBJ_BLOB) > filterSpec.getBlobLimit());
 		if (!reject) {
 			addObject(src, type, pathHashCode);
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/MergedReftable.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/MergedReftable.java
index 3633515..a78f4d2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/MergedReftable.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/MergedReftable.java
@@ -82,7 +82,7 @@
 			return 0;
 		}
 		long minUpdateIndex = tables[0].minUpdateIndex();
-		for (int i = 0; i < tables.length - 1; i++) {
+		for (int i = 1; i < tables.length; i++) {
 			if (tables[i].minUpdateIndex() < minUpdateIndex) {
 				minUpdateIndex = tables[i].minUpdateIndex();
 			}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java
index f6695bd..b11b230 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java
@@ -86,7 +86,7 @@
 
 		/**
 		 * Returns the corresponding raw compressed EWAH bitmap of the bitmap.
-		 * 
+		 *
 		 * @return the corresponding {@code EWAHCompressedBitmap}
 		 * @since 5.8
 		 */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java
index 66d7d51..4f93fda 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java
@@ -361,7 +361,9 @@
 	 * header</a>.
 	 * <p>
 	 * CRLF and CR will be sanitized to LF and signature will have a hanging
-	 * indent of one space starting with line two.
+	 * indent of one space starting with line two. A trailing line break is
+	 * <em>not</em> written; the caller is supposed to terminate the GPG
+	 * signature header by writing a single newline.
 	 * </p>
 	 *
 	 * @param in
@@ -375,22 +377,24 @@
 	 */
 	static void writeGpgSignatureString(String in, OutputStream out)
 			throws IOException, IllegalArgumentException {
-		for (int i = 0; i < in.length(); ++i) {
+		int length = in.length();
+		for (int i = 0; i < length; ++i) {
 			char ch = in.charAt(i);
 			switch (ch) {
 			case '\r':
-				if (i + 1 < in.length() && in.charAt(i + 1) == '\n') {
-					out.write('\n');
-					out.write(' ');
+				if (i + 1 < length && in.charAt(i + 1) == '\n') {
 					++i;
-				} else {
+				}
+				if (i + 1 < length) {
 					out.write('\n');
 					out.write(' ');
 				}
 				break;
 			case '\n':
-				out.write('\n');
-				out.write(' ');
+				if (i + 1 < length) {
+					out.write('\n');
+					out.write(' ');
+				}
 				break;
 			default:
 				// sanity check
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 a51593b..a369026 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
@@ -20,6 +20,9 @@
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
+import java.io.File;
+import java.nio.file.InvalidPathException;
+import java.nio.file.Path;
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -29,6 +32,7 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
+import org.eclipse.jgit.annotations.NonNull;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.events.ConfigChangedEvent;
 import org.eclipse.jgit.events.ConfigChangedListener;
@@ -36,6 +40,7 @@
 import org.eclipse.jgit.events.ListenerList;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.transport.RefSpec;
+import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.RawParseUtils;
 
 /**
@@ -475,6 +480,37 @@
 	}
 
 	/**
+	 * Parse a string value and treat it as a file path, replacing a ~/ prefix
+	 * by the user's home directory.
+	 * <p>
+	 * <b>Note:</b> this may throw {@link InvalidPathException} if the string is
+	 * not a valid path.
+	 * </p>
+	 *
+	 * @param section
+	 *            section the key is in.
+	 * @param subsection
+	 *            subsection the key is in, or null if not in a subsection.
+	 * @param name
+	 *            the key name.
+	 * @param fs
+	 *            to use to convert the string into a path.
+	 * @param resolveAgainst
+	 *            directory to resolve the path against if it is a relative
+	 *            path; {@code null} to use the Java process's current
+	 *            directory.
+	 * @param defaultValue
+	 *            to return if no value was present
+	 * @return the {@link Path}, or {@code defaultValue} if not set
+	 * @since 5.10
+	 */
+	public Path getPath(String section, String subsection, String name,
+			@NonNull FS fs, File resolveAgainst, Path defaultValue) {
+		return typedGetter.getPath(this, section, subsection, name, fs,
+				resolveAgainst, defaultValue);
+	}
+
+	/**
 	 * Parse a list of {@link org.eclipse.jgit.transport.RefSpec}s from the
 	 * configuration.
 	 *
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 eef822f..4fcf8e2 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,7 @@
 /*
  * Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com>
  * Copyright (C) 2010, Chris Aniszczyk <caniszczyk@gmail.com>
- * Copyright (C) 2012-2013, Robin Rosenberg and others
+ * Copyright (C) 2012, 2020, 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
@@ -662,4 +662,33 @@
 	 * @since 5.8
 	 */
 	public static final String CONFIG_KEY_WINDOW_MEMORY = "windowmemory";
+
+	/**
+	 * The "feature" section
+	 *
+	 * @since 5.9
+	 */
+	public static final String CONFIG_FEATURE_SECTION = "feature";
+
+	/**
+	 * The "feature.manyFiles" key
+	 *
+	 * @since 5.9
+	 */
+	public static final String CONFIG_KEY_MANYFILES = "manyFiles";
+
+	/**
+	 * The "index" section
+	 *
+	 * @since 5.9
+	 */
+	public static final String CONFIG_INDEX_SECTION = "index";
+
+	/**
+	 * The "index.version" key
+	 *
+	 * @since 5.9
+	 */
+	public static final String CONFIG_KEY_VERSION = "version";
+
 }
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 dd4be34..0f2f6cf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TypedConfigGetter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TypedConfigGetter.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017, Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2017, 2020 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
@@ -10,22 +10,26 @@
 
 package org.eclipse.jgit.lib;
 
+import java.io.File;
+import java.nio.file.InvalidPathException;
+import java.nio.file.Path;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jgit.annotations.NonNull;
 import org.eclipse.jgit.transport.RefSpec;
+import org.eclipse.jgit.util.FS;
 
 /**
- * Something that knows how to convert plain strings from a git
- * {@link org.eclipse.jgit.lib.Config} to typed values.
+ * Something that knows how to convert plain strings from a git {@link Config}
+ * to typed values.
  *
  * @since 4.9
  */
 public interface TypedConfigGetter {
 
 	/**
-	 * Get a boolean value from a git {@link org.eclipse.jgit.lib.Config}.
+	 * Get a boolean value from a git {@link Config}.
 	 *
 	 * @param config
 	 *            to get the value from
@@ -44,7 +48,7 @@
 			String name, boolean defaultValue);
 
 	/**
-	 * Parse an enumeration from a git {@link org.eclipse.jgit.lib.Config}.
+	 * Parse an enumeration from a git {@link Config}.
 	 *
 	 * @param config
 	 *            to get the value from
@@ -65,7 +69,7 @@
 			String subsection, String name, T defaultValue);
 
 	/**
-	 * Obtain an integer value from a git {@link org.eclipse.jgit.lib.Config}.
+	 * Obtain an integer value from a git {@link Config}.
 	 *
 	 * @param config
 	 *            to get the value from
@@ -83,7 +87,7 @@
 			int defaultValue);
 
 	/**
-	 * Obtain a long value from a git {@link org.eclipse.jgit.lib.Config}.
+	 * Obtain a long value from a git {@link Config}.
 	 *
 	 * @param config
 	 *            to get the value from
@@ -102,7 +106,7 @@
 
 	/**
 	 * Parse a numerical time unit, such as "1 minute", from a git
-	 * {@link org.eclipse.jgit.lib.Config}.
+	 * {@link Config}.
 	 *
 	 * @param config
 	 *            to get the value from
@@ -124,10 +128,50 @@
 	long getTimeUnit(Config config, String section, String subsection,
 			String name, long defaultValue, TimeUnit wantUnit);
 
+	/**
+	 * Parse a string value from a git {@link Config} and treat it as a file
+	 * path, replacing a ~/ prefix by the user's home directory.
+	 * <p>
+	 * <b>Note:</b> this may throw {@link InvalidPathException} if the string is
+	 * not a valid path.
+	 * </p>
+	 *
+	 * @param config
+	 *            to get the path from.
+	 * @param section
+	 *            section the key is in.
+	 * @param subsection
+	 *            subsection the key is in, or null if not in a subsection.
+	 * @param name
+	 *            the key name.
+	 * @param fs
+	 *            to use to convert the string into a path.
+	 * @param resolveAgainst
+	 *            directory to resolve the path against if it is a relative
+	 *            path.
+	 * @param defaultValue
+	 *            to return if no value was present
+	 * @return the {@link Path}, or {@code defaultValue} if not set
+	 * @since 5.10
+	 */
+	default Path getPath(Config config, String section, String subsection,
+			String name, @NonNull FS fs, File resolveAgainst,
+			Path defaultValue) {
+		String value = config.getString(section, subsection, name);
+		if (value == null) {
+			return defaultValue;
+		}
+		File file;
+		if (value.startsWith("~/")) { //$NON-NLS-1$
+			file = fs.resolve(fs.userHome(), value.substring(2));
+		} else {
+			file = fs.resolve(resolveAgainst, value);
+		}
+		return file.toPath();
+	}
 
 	/**
-	 * Parse a list of {@link org.eclipse.jgit.transport.RefSpec}s from a git
-	 * {@link org.eclipse.jgit.lib.Config}.
+	 * Parse a list of {@link RefSpec}s from a git {@link Config}.
 	 *
 	 * @param config
 	 *            to get the list from
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/logging/PerformanceLogContext.java b/org.eclipse.jgit/src/org/eclipse/jgit/logging/PerformanceLogContext.java
new file mode 100644
index 0000000..fab0dd1
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/logging/PerformanceLogContext.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2020, Google LLC  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
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.logging;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Singleton that collects performance logs.
+ *
+ * @since 5.10
+ */
+public class PerformanceLogContext {
+	/** Singleton instance that stores the statistics. */
+	private static final PerformanceLogContext INSTANCE = new PerformanceLogContext();
+
+	/** List that stores events as performance logs. */
+	private static final ThreadLocal<List<PerformanceLogRecord>> eventRecords = ThreadLocal
+			.withInitial(ArrayList::new);
+
+	private PerformanceLogContext() {
+	}
+
+	/**
+	 * Get the instance of the context.
+	 *
+	 * @return instance of performance log context.
+	 */
+	public static PerformanceLogContext getInstance() {
+		return INSTANCE;
+	}
+
+	/**
+	 * Get the unmodifiable list of events as performance records.
+	 *
+	 * @return unmodifiable list of events as performance logs.
+	 */
+	public List<PerformanceLogRecord> getEventRecords() {
+		return Collections.unmodifiableList(eventRecords.get());
+	}
+
+	/**
+	 * Adds a performance log record to the current list of events.
+	 *
+	 * @param record
+	 *            performance log record that is going to be added.
+	 */
+	public void addEvent(PerformanceLogRecord record) {
+		eventRecords.get().add(record);
+	}
+
+	/**
+	 * Removes all of the existing records from the current list of events.
+	 */
+	public void cleanEvents() {
+		eventRecords.remove();
+	}
+}
\ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/logging/PerformanceLogRecord.java b/org.eclipse.jgit/src/org/eclipse/jgit/logging/PerformanceLogRecord.java
new file mode 100644
index 0000000..3dc5f20
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/logging/PerformanceLogRecord.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2020, Google LLC  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
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.logging;
+
+/**
+ * Class to register a performance log record.
+ *
+ * @since 5.10
+ */
+public class PerformanceLogRecord {
+	/** Name of the recorded event. */
+	private String name;
+
+	/** Duration of the recorded event in milliseconds. */
+	private long durationMs;
+
+	/**
+	 * Create a new performance log record for an event.
+	 *
+	 * @param name
+	 *            name of the event.
+	 * @param durationMs
+	 *            duration in milliseconds of the event.
+	 */
+	public PerformanceLogRecord(String name, long durationMs) {
+		this.name = name;
+		this.durationMs = durationMs;
+	}
+
+	/**
+	 * Get the name of the recorded event.
+	 *
+	 * @return name of the recorded event.
+	 */
+	public String getName() {
+		return name;
+	}
+
+	/**
+	 * Get the duration in milliseconds of the recorded event.
+	 *
+	 * @return duration in milliseconds of the recorded event.
+	 */
+	public long getDurationMs() {
+		return durationMs;
+	}
+}
\ No newline at end of file
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 506d333..6c217fd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
@@ -588,7 +588,8 @@
 		final int modeO = tw.getRawMode(T_OURS);
 		final int modeT = tw.getRawMode(T_THEIRS);
 		final int modeB = tw.getRawMode(T_BASE);
-
+		boolean gitLinkMerging = isGitLink(modeO) || isGitLink(modeT)
+				|| isGitLink(modeB);
 		if (modeO == 0 && modeT == 0 && modeB == 0)
 			// File is either untracked or new, staged but uncommitted
 			return true;
@@ -737,31 +738,28 @@
 				return false;
 			}
 
-			boolean gitlinkConflict = isGitLink(modeO) || isGitLink(modeT);
-			// Don't attempt to resolve submodule link conflicts
-			if (gitlinkConflict || !attributes.canBeContentMerged()) {
+			if (gitLinkMerging && ignoreConflicts) {
+				// Always select 'ours' in case of GITLINK merge failures so
+				// a caller can use virtual commit.
+				add(tw.getRawPath(), ours, DirCacheEntry.STAGE_0, EPOCH, 0);
+				return true;
+			} else if (gitLinkMerging) {
+				add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
+				add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
+				add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
+				MergeResult<SubmoduleConflict> result = createGitLinksMergeResult(
+						base, ours, theirs);
+				result.setContainsConflicts(true);
+				mergeResults.put(tw.getPathString(), result);
+				unmergedPaths.add(tw.getPathString());
+				return true;
+			} else if (!attributes.canBeContentMerged()) {
 				add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
 				add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
 				add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
 
-				if (gitlinkConflict) {
-					MergeResult<SubmoduleConflict> result = new MergeResult<>(
-							Arrays.asList(
-									new SubmoduleConflict(base == null ? null
-											: base.getEntryObjectId()),
-									new SubmoduleConflict(ours == null ? null
-											: ours.getEntryObjectId()),
-									new SubmoduleConflict(theirs == null ? null
-											: theirs.getEntryObjectId())));
-					result.setContainsConflicts(true);
-					mergeResults.put(tw.getPathString(), result);
-					if (!ignoreConflicts) {
-						unmergedPaths.add(tw.getPathString());
-					}
-				} else {
-					// attribute merge issues are conflicts but not failures
-					unmergedPaths.add(tw.getPathString());
-				}
+				// attribute merge issues are conflicts but not failures
+				unmergedPaths.add(tw.getPathString());
 				return true;
 			}
 
@@ -786,45 +784,73 @@
 			// OURS or THEIRS has been deleted
 			if (((modeO != 0 && !tw.idEqual(T_BASE, T_OURS)) || (modeT != 0 && !tw
 					.idEqual(T_BASE, T_THEIRS)))) {
-				MergeResult<RawText> result = contentMerge(base, ours, theirs,
-						attributes);
-
-				if (ignoreConflicts) {
-					// In case a conflict is detected the working tree file is
-					// again filled with new content (containing conflict
-					// markers). But also stage 0 of the index is filled with
-					// that content.
-					result.setContainsConflicts(false);
-					updateIndex(base, ours, theirs, result, attributes);
-				} else {
+				if (gitLinkMerging && ignoreConflicts) {
+					add(tw.getRawPath(), ours, DirCacheEntry.STAGE_0, EPOCH, 0);
+				} else if (gitLinkMerging) {
 					add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
 					add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
-					DirCacheEntry e = add(tw.getRawPath(), theirs,
-							DirCacheEntry.STAGE_3, EPOCH, 0);
+					add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
+					MergeResult<SubmoduleConflict> result = createGitLinksMergeResult(
+							base, ours, theirs);
+					result.setContainsConflicts(true);
+					mergeResults.put(tw.getPathString(), result);
+					unmergedPaths.add(tw.getPathString());
+				} else {
+					MergeResult<RawText> result = contentMerge(base, ours,
+							theirs, attributes);
 
-					// OURS was deleted checkout THEIRS
-					if (modeO == 0) {
-						// Check worktree before checking out THEIRS
-						if (isWorktreeDirty(work, ourDce)) {
-							return false;
-						}
-						if (nonTree(modeT)) {
-							if (e != null) {
-								addToCheckout(tw.getPathString(), e, attributes);
+					if (ignoreConflicts) {
+						// In case a conflict is detected the working tree file
+						// is again filled with new content (containing conflict
+						// markers). But also stage 0 of the index is filled
+						// with that content.
+						result.setContainsConflicts(false);
+						updateIndex(base, ours, theirs, result, attributes);
+					} else {
+						add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH,
+								0);
+						add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH,
+								0);
+						DirCacheEntry e = add(tw.getRawPath(), theirs,
+								DirCacheEntry.STAGE_3, EPOCH, 0);
+
+						// OURS was deleted checkout THEIRS
+						if (modeO == 0) {
+							// Check worktree before checking out THEIRS
+							if (isWorktreeDirty(work, ourDce)) {
+								return false;
+							}
+							if (nonTree(modeT)) {
+								if (e != null) {
+									addToCheckout(tw.getPathString(), e,
+											attributes);
+								}
 							}
 						}
+
+						unmergedPaths.add(tw.getPathString());
+
+						// generate a MergeResult for the deleted file
+						mergeResults.put(tw.getPathString(), result);
 					}
-
-					unmergedPaths.add(tw.getPathString());
-
-					// generate a MergeResult for the deleted file
-					mergeResults.put(tw.getPathString(), result);
 				}
 			}
 		}
 		return true;
 	}
 
+	private static MergeResult<SubmoduleConflict> createGitLinksMergeResult(
+			CanonicalTreeParser base, CanonicalTreeParser ours,
+			CanonicalTreeParser theirs) {
+		return new MergeResult<>(Arrays.asList(
+				new SubmoduleConflict(
+						base == null ? null : base.getEntryObjectId()),
+				new SubmoduleConflict(
+						ours == null ? null : ours.getEntryObjectId()),
+				new SubmoduleConflict(
+						theirs == null ? null : theirs.getEntryObjectId())));
+	}
+
 	/**
 	 * Does the content merge. The three texts base, ours and theirs are
 	 * specified with {@link CanonicalTreeParser}. If any of the parsers is
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackStatistics.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackStatistics.java
index 645da0a..4d18337 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackStatistics.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackStatistics.java
@@ -188,6 +188,13 @@
 		public long haves;
 
 		/**
+		 * The count of wants that were not advertised by the server.
+		 *
+		 * @since 5.10
+		 */
+		public long notAdvertisedWants;
+
+		/**
 		 * Time in ms spent in the negotiation phase. For non-bidirectional
 		 * transports (e.g., HTTP), this is only for the final request that
 		 * sends back the pack file.
@@ -266,8 +273,16 @@
 		/** Time in ms spent writing the pack. */
 		public long timeWriting;
 
+		/** Time in ms spent checking reachability.
+		 *
+		 * @since 5.10
+		 */
+		public long reachabilityCheckDuration;
+
 		/** Number of trees traversed in the walk when writing the pack.
-		 * @since 5.4*/
+		 *
+		 * @since 5.4
+		 */
 		public long treesTraversed;
 
 		/**
@@ -349,6 +364,16 @@
 	}
 
 	/**
+	 * Get the count of client wants that were not advertised by the server.
+	 *
+	 * @return count of client wants that were not advertised by the server.
+	 * @since 5.10
+	 */
+	public long getNotAdvertisedWants() {
+		return statistics.notAdvertisedWants;
+	}
+
+	/**
 	 * Time in ms spent in the negotiation phase. For non-bidirectional
 	 * transports (e.g., HTTP), this is only for the final request that sends
 	 * back the pack file.
@@ -604,6 +629,18 @@
 	}
 
 	/**
+	 * Get time in milliseconds spent checking if the client has access to the
+	 * commits they are requesting.
+	 *
+	 * @return time in milliseconds spent checking if the client has access to the
+	 * commits they are requesting.
+	 * @since 5.10
+	 */
+	public long getReachabilityCheckDuration() {
+		return statistics.reachabilityCheckDuration;
+	}
+
+	/**
 	 * @return number of trees traversed in the walk when writing the pack.
 	 * @since 5.4
 	 */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java
index 0ba5eb5..bf77021 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java
@@ -657,8 +657,11 @@
 	 *
 	 * @since 4.10
 	 * @return name
+	 * @throws ConfigInvalidException
+	 * @throws IOException
 	 */
-	public String getModuleName() {
+	public String getModuleName() throws IOException, ConfigInvalidException {
+		lazyLoadModulesConfig();
 		return getModuleName(path);
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
index 1417fae..3a36398 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
@@ -21,8 +21,11 @@
 import java.io.OutputStream;
 import java.text.MessageFormat;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.LinkedHashMap;
+import java.util.Map;
 import java.util.Set;
 
 import org.eclipse.jgit.errors.InvalidObjectIdException;
@@ -35,6 +38,7 @@
 import org.eclipse.jgit.lib.ObjectIdRef;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.SymbolicRef;
 import org.eclipse.jgit.util.io.InterruptTimer;
 import org.eclipse.jgit.util.io.TimeoutInputStream;
 import org.eclipse.jgit.util.io.TimeoutOutputStream;
@@ -49,6 +53,8 @@
  */
 abstract class BasePackConnection extends BaseConnection {
 
+	protected static final String CAPABILITY_SYMREF_PREFIX = "symref="; //$NON-NLS-1$
+
 	/** The repository this transport fetches into, or pushes out of. */
 	protected final Repository local;
 
@@ -228,10 +234,109 @@
 					throw duplicateAdvertisement(name);
 			}
 		}
+		updateWithSymRefs(avail, extractSymRefsFromCapabilities(remoteCapablities));
 		available(avail);
 	}
 
 	/**
+	 * Finds values in the given capabilities of the form:
+	 *
+	 * <pre>
+	 * symref=<em>source</em>:<em>target</em>
+	 * </pre>
+	 *
+	 * And returns a Map of source->target entries.
+	 *
+	 * @param capabilities
+	 *            the capabilities lines
+	 * @return a Map of the symref entries from capabilities
+	 * @throws NullPointerException
+	 *             if capabilities, or any entry in it, is null
+	 */
+	static Map<String, String> extractSymRefsFromCapabilities(Collection<String> capabilities) {
+		final Map<String, String> symRefs = new LinkedHashMap<>();
+		for (String option : capabilities) {
+			if (option.startsWith(CAPABILITY_SYMREF_PREFIX)) {
+				String[] symRef = option
+						.substring(CAPABILITY_SYMREF_PREFIX.length())
+						.split(":", 2); //$NON-NLS-1$
+				if (symRef.length == 2) {
+					symRefs.put(symRef[0], symRef[1]);
+				}
+			}
+		}
+		return symRefs;
+	}
+
+	/**
+	 * Updates the given refMap with {@link SymbolicRef}s defined by the given
+	 * symRefs.
+	 * <p>
+	 * For each entry, symRef, in symRefs, whose value is a key in refMap, adds
+	 * a new entry to refMap with that same key and value of a new
+	 * {@link SymbolicRef} with source=symRef.key and
+	 * target=refMap.get(symRef.value), then removes that entry from symRefs.
+	 * <p>
+	 * If refMap already contains an entry for symRef.key, it is replaced.
+	 * </p>
+	 * </p>
+	 * <p>
+	 * For example, given:
+	 * </p>
+	 *
+	 * <pre>
+	 * refMap.put("refs/heads/main", ref);
+	 * symRefs.put("HEAD", "refs/heads/main");
+	 * </pre>
+	 *
+	 * then:
+	 *
+	 * <pre>
+	 * updateWithSymRefs(refMap, symRefs);
+	 * </pre>
+	 *
+	 * has the <em>effect</em> of:
+	 *
+	 * <pre>
+	 * refMap.put("HEAD",
+	 * 		new SymbolicRef("HEAD", refMap.get(symRefs.remove("HEAD"))))
+	 * </pre>
+	 * <p>
+	 * Any entry in symRefs whose value is not a key in refMap is ignored. Any
+	 * circular symRefs are ignored.
+	 * </p>
+	 * <p>
+	 * Upon completion, symRefs will contain only any unresolvable entries.
+	 * </p>
+	 *
+	 * @param refMap
+	 *            a non-null, modifiable, Map to update, and the provider of
+	 *            symref targets.
+	 * @param symRefs
+	 *            a non-null, modifiable, Map of symrefs.
+	 * @throws NullPointerException
+	 *             if refMap or symRefs is null
+	 */
+	static void updateWithSymRefs(Map<String, Ref> refMap, Map<String, String> symRefs) {
+		boolean haveNewRefMapEntries = !refMap.isEmpty();
+		while (!symRefs.isEmpty() && haveNewRefMapEntries) {
+			haveNewRefMapEntries = false;
+			final Iterator<Map.Entry<String, String>> iterator = symRefs.entrySet().iterator();
+			while (iterator.hasNext()) {
+				final Map.Entry<String, String> symRef = iterator.next();
+				if (!symRefs.containsKey(symRef.getValue())) { // defer forward reference
+					final Ref r = refMap.get(symRef.getValue());
+					if (r != null) {
+						refMap.put(symRef.getKey(), new SymbolicRef(symRef.getKey(), r));
+						haveNewRefMapEntries = true;
+						iterator.remove();
+					}
+				}
+			}
+		}
+	}
+
+	/**
 	 * Create an exception to indicate problems finding a remote repository. The
 	 * caller is expected to throw the returned exception.
 	 *
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java
index 57eed3a..4649d33 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java
@@ -17,12 +17,16 @@
 import java.io.OutputStreamWriter;
 import java.io.Writer;
 import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeMap;
 
 import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.pack.CachedPack;
 import org.eclipse.jgit.internal.storage.pack.PackWriter;
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Constants;
@@ -62,6 +66,8 @@
 
 	private final Set<ObjectId> tagTargets;
 
+	private final List<CachedPack> cachedPacks = new ArrayList<>();
+
 	private PackConfig packConfig;
 
 	private ObjectCountCallback callback;
@@ -150,6 +156,26 @@
 	}
 
 	/**
+	 * Add objects to the bundle file.
+	 *
+	 * <p>
+	 * When this method is used, object traversal is disabled and specified pack
+	 * files are directly saved to the Git bundle file.
+	 *
+	 * <p>
+	 * Unlike {@link #include}, this doesn't affect the refs. Even if the
+	 * objects are not reachable from any ref, they will be included in the
+	 * bundle file.
+	 *
+	 * @param c
+	 *            pack to include
+	 * @since 5.9
+	 */
+	public void addObjectsAsIs(Collection<? extends CachedPack> c) {
+		cachedPacks.addAll(c);
+	}
+
+	/**
 	 * Assume a commit is available on the recipient's side.
 	 * <p>
 	 * In order to fetch from a bundle the recipient must have any assumed
@@ -187,19 +213,24 @@
 		try (PackWriter packWriter = newPackWriter()) {
 			packWriter.setObjectCountCallback(callback);
 
-			final HashSet<ObjectId> inc = new HashSet<>();
-			final HashSet<ObjectId> exc = new HashSet<>();
-			inc.addAll(include.values());
-			for (RevCommit r : assume)
-				exc.add(r.getId());
 			packWriter.setIndexDisabled(true);
 			packWriter.setDeltaBaseAsOffset(true);
-			packWriter.setThin(!exc.isEmpty());
 			packWriter.setReuseValidatingObjects(false);
-			if (exc.isEmpty()) {
-				packWriter.setTagTargets(tagTargets);
+			if (cachedPacks.isEmpty()) {
+				HashSet<ObjectId> inc = new HashSet<>();
+				HashSet<ObjectId> exc = new HashSet<>();
+				inc.addAll(include.values());
+				for (RevCommit r : assume) {
+					exc.add(r.getId());
+				}
+				if (exc.isEmpty()) {
+					packWriter.setTagTargets(tagTargets);
+				}
+				packWriter.setThin(!exc.isEmpty());
+				packWriter.preparePack(monitor, inc, exc);
+			} else {
+				packWriter.preparePack(cachedPacks);
 			}
-			packWriter.preparePack(monitor, inc, exc);
 
 			final Writer w = new OutputStreamWriter(os, UTF_8);
 			w.write(TransportBundle.V2_BUNDLE_SIGNATURE);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FilterSpec.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FilterSpec.java
index d09b557..a8cf849 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FilterSpec.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FilterSpec.java
@@ -10,6 +10,15 @@
 
 package org.eclipse.jgit.transport;
 
+import static java.math.BigInteger.ZERO;
+import static java.util.Objects.requireNonNull;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
+import static org.eclipse.jgit.lib.Constants.OBJ_TAG;
+import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
+import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_FILTER;
+
+import java.math.BigInteger;
 import java.text.MessageFormat;
 
 import org.eclipse.jgit.annotations.Nullable;
@@ -24,11 +33,54 @@
  */
 public final class FilterSpec {
 
+	/** Immutable bit-set representation of a set of Git object types. */
+	static class ObjectTypes {
+		static ObjectTypes ALL = allow(OBJ_BLOB, OBJ_TREE, OBJ_COMMIT, OBJ_TAG);
+
+		private final BigInteger val;
+
+		private ObjectTypes(BigInteger val) {
+			this.val = requireNonNull(val);
+		}
+
+		static ObjectTypes allow(int... types) {
+			BigInteger bits = ZERO;
+			for (int type : types) {
+				bits = bits.setBit(type);
+			}
+			return new ObjectTypes(bits);
+		}
+
+		boolean contains(int type) {
+			return val.testBit(type);
+		}
+
+		/** {@inheritDoc} */
+		@Override
+		public boolean equals(Object obj) {
+			if (!(obj instanceof ObjectTypes)) {
+				return false;
+			}
+
+			ObjectTypes other = (ObjectTypes) obj;
+			return other.val.equals(val);
+		}
+
+		/** {@inheritDoc} */
+		@Override
+		public int hashCode() {
+			return val.hashCode();
+		}
+	}
+
+	private final ObjectTypes types;
+
 	private final long blobLimit;
 
 	private final long treeDepthLimit;
 
-	private FilterSpec(long blobLimit, long treeDepthLimit) {
+	private FilterSpec(ObjectTypes types, long blobLimit, long treeDepthLimit) {
+		this.types = requireNonNull(types);
 		this.blobLimit = blobLimit;
 		this.treeDepthLimit = treeDepthLimit;
 	}
@@ -53,7 +105,8 @@
 	public static FilterSpec fromFilterLine(String filterLine)
 			throws PackProtocolException {
 		if (filterLine.equals("blob:none")) { //$NON-NLS-1$
-			return FilterSpec.withBlobLimit(0);
+			return FilterSpec.withObjectTypes(
+					ObjectTypes.allow(OBJ_TREE, OBJ_COMMIT, OBJ_TAG));
 		} else if (filterLine.startsWith("blob:limit=")) { //$NON-NLS-1$
 			long blobLimit = -1;
 			try {
@@ -86,8 +139,18 @@
 	}
 
 	/**
+	 * @param types
+	 *            set of permitted object types, for use in "blob:none" and
+	 *            "object:none" filters
+	 * @return a filter spec which restricts to objects of the specified types
+	 */
+	static FilterSpec withObjectTypes(ObjectTypes types) {
+		return new FilterSpec(types, -1, -1);
+	}
+
+	/**
 	 * @param blobLimit
-	 *            the blob limit in a "blob:[limit]" or "blob:none" filter line
+	 *            the blob limit in a "blob:[limit]" filter line
 	 * @return a filter spec which filters blobs above a certain size
 	 */
 	static FilterSpec withBlobLimit(long blobLimit) {
@@ -95,7 +158,7 @@
 			throw new IllegalArgumentException(
 					"blobLimit cannot be negative: " + blobLimit); //$NON-NLS-1$
 		}
-		return new FilterSpec(blobLimit, -1);
+		return new FilterSpec(ObjectTypes.ALL, blobLimit, -1);
 	}
 
 	/**
@@ -109,13 +172,25 @@
 			throw new IllegalArgumentException(
 					"treeDepthLimit cannot be negative: " + treeDepthLimit); //$NON-NLS-1$
 		}
-		return new FilterSpec(-1, treeDepthLimit);
+		return new FilterSpec(ObjectTypes.ALL, -1, treeDepthLimit);
 	}
 
 	/**
 	 * A placeholder that indicates no filtering.
 	 */
-	public static final FilterSpec NO_FILTER = new FilterSpec(-1, -1);
+	public static final FilterSpec NO_FILTER = new FilterSpec(ObjectTypes.ALL, -1, -1);
+
+	/**
+	 * @param type
+	 *            a Git object type, such as
+	 *            {@link org.eclipse.jgit.lib.Constants#OBJ_BLOB}
+	 * @return whether this filter allows objects of the specified type
+	 *
+	 * @since 5.9
+	 */
+	public boolean allowsType(int type) {
+		return types.contains(type);
+	}
 
 	/**
 	 * @return -1 if this filter does not filter blobs based on size, or a
@@ -138,7 +213,7 @@
 	 * @return true if this filter doesn't filter out anything
 	 */
 	public boolean isNoOp() {
-		return blobLimit == -1 && treeDepthLimit == -1;
+		return types.equals(ObjectTypes.ALL) && blobLimit == -1 && treeDepthLimit == -1;
 	}
 
 	/**
@@ -146,14 +221,17 @@
 	 */
 	@Nullable
 	public String filterLine() {
-		if (blobLimit == 0) {
-			return GitProtocolConstants.OPTION_FILTER + " blob:none"; //$NON-NLS-1$
+		if (isNoOp()) {
+			return null;
+		} else if (types.equals(ObjectTypes.allow(OBJ_TREE, OBJ_COMMIT, OBJ_TAG)) &&
+					blobLimit == -1 && treeDepthLimit == -1) {
+			return OPTION_FILTER + " blob:none"; //$NON-NLS-1$
+		} else if (types.equals(ObjectTypes.ALL) && blobLimit >= 0 && treeDepthLimit == -1) {
+			return OPTION_FILTER + " blob:limit=" + blobLimit; //$NON-NLS-1$
+		} else if (types.equals(ObjectTypes.ALL) && blobLimit == -1 && treeDepthLimit >= 0) {
+			return OPTION_FILTER + " tree:" + treeDepthLimit; //$NON-NLS-1$
+		} else {
+			throw new IllegalStateException();
 		}
-
-		if (blobLimit > 0) {
-			return GitProtocolConstants.OPTION_FILTER + " blob:limit=" + blobLimit; //$NON-NLS-1$
-		}
-
-		return null;
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java
index 79cba80..dc82f46 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java
@@ -14,9 +14,13 @@
 import java.io.IOException;
 import java.net.URISyntaxException;
 import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 import java.util.Set;
 import java.util.function.Supplier;
 
+import org.eclipse.jgit.annotations.NonNull;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.Config;
@@ -56,6 +60,20 @@
 	public static final String SSL_VERIFY_KEY = "sslVerify"; //$NON-NLS-1$
 
 	/**
+	 * git config key for the "userAgent" setting.
+	 *
+	 * @since 5.10
+	 */
+	public static final String USER_AGENT = "userAgent"; //$NON-NLS-1$
+
+	/**
+	 * git config key for the "extraHeader" setting.
+	 *
+	 * @since 5.10
+	 */
+	public static final String EXTRA_HEADER = "extraHeader"; //$NON-NLS-1$
+
+	/**
 	 * git config key for the "cookieFile" setting.
 	 *
 	 * @since 5.4
@@ -103,6 +121,8 @@
 		}
 	}).get().intValue();
 
+	private static final String ENV_HTTP_USER_AGENT = "GIT_HTTP_USER_AGENT"; //$NON-NLS-1$
+
 	/**
 	 * Config values for http.followRedirect.
 	 */
@@ -143,6 +163,10 @@
 
 	private int maxRedirects;
 
+	private String userAgent;
+
+	private List<String> extraHeaders;
+
 	private String cookieFile;
 
 	private boolean saveCookies;
@@ -186,6 +210,27 @@
 	}
 
 	/**
+	 * Get the "http.userAgent" setting
+	 *
+	 * @return the value of the "http.userAgent" setting
+	 * @since 5.10
+	 */
+	public String getUserAgent() {
+		return userAgent;
+	}
+
+	/**
+	 * Get the "http.extraHeader" setting
+	 *
+	 * @return the value of the "http.extraHeader" setting
+	 * @since 5.10
+	 */
+	@NonNull
+	public List<String> getExtraHeaders() {
+		return extraHeaders == null ? Collections.emptyList() : extraHeaders;
+	}
+
+	/**
 	 * Get the "http.cookieFile" setting
 	 *
 	 * @return the value of the "http.cookieFile" setting
@@ -265,11 +310,25 @@
 		if (redirectLimit < 0) {
 			redirectLimit = MAX_REDIRECTS;
 		}
+		String agent = config.getString(HTTP, null, USER_AGENT);
+		if (agent != null) {
+			agent = UserAgent.clean(agent);
+		}
+		userAgent = agent;
+		String[] headers = config.getStringList(HTTP, null, EXTRA_HEADER);
+		// https://git-scm.com/docs/git-config#Documentation/git-config.txt-httpextraHeader
+		// "an empty value will reset the extra headers to the empty list."
+		int start = findLastEmpty(headers) + 1;
+		if (start > 0) {
+			headers = Arrays.copyOfRange(headers, start, headers.length);
+		}
+		extraHeaders = Arrays.asList(headers);
 		cookieFile = config.getString(HTTP, null, COOKIE_FILE_KEY);
 		saveCookies = config.getBoolean(HTTP, SAVE_COOKIES_KEY, false);
 		cookieFileCacheLimit = config.getInt(HTTP, COOKIE_FILE_CACHE_LIMIT_KEY,
 				DEFAULT_COOKIE_FILE_CACHE_LIMIT);
 		String match = findMatch(config.getSubsections(HTTP), uri);
+
 		if (match != null) {
 			// Override with more specific items
 			postBufferSize = config.getInt(HTTP, match, POST_BUFFER_KEY,
@@ -283,6 +342,22 @@
 			if (newMaxRedirects >= 0) {
 				redirectLimit = newMaxRedirects;
 			}
+			String uriSpecificUserAgent = config.getString(HTTP, match,
+					USER_AGENT);
+			if (uriSpecificUserAgent != null) {
+				userAgent = UserAgent.clean(uriSpecificUserAgent);
+			}
+			String[] uriSpecificExtraHeaders = config.getStringList(HTTP, match,
+					EXTRA_HEADER);
+			if (uriSpecificExtraHeaders.length > 0) {
+				start = findLastEmpty(uriSpecificExtraHeaders) + 1;
+				if (start > 0) {
+					uriSpecificExtraHeaders = Arrays.copyOfRange(
+							uriSpecificExtraHeaders, start,
+							uriSpecificExtraHeaders.length);
+				}
+				extraHeaders = Arrays.asList(uriSpecificExtraHeaders);
+			}
 			String urlSpecificCookieFile = config.getString(HTTP, match,
 					COOKIE_FILE_KEY);
 			if (urlSpecificCookieFile != null) {
@@ -291,12 +366,26 @@
 			saveCookies = config.getBoolean(HTTP, match, SAVE_COOKIES_KEY,
 					saveCookies);
 		}
+		// Environment overrides config
+		agent = SystemReader.getInstance().getenv(ENV_HTTP_USER_AGENT);
+		if (!StringUtils.isEmptyOrNull(agent)) {
+			userAgent = UserAgent.clean(agent);
+		}
 		postBuffer = postBufferSize;
 		sslVerify = sslVerifyFlag;
 		followRedirects = followRedirectsMode;
 		maxRedirects = redirectLimit;
 	}
 
+	private int findLastEmpty(String[] values) {
+		for (int i = values.length - 1; i >= 0; i--) {
+			if (values[i] == null) {
+				return i;
+			}
+		}
+		return -1;
+	}
+
 	/**
 	 * Determines the best match from a set of subsection names (representing
 	 * prefix URLs) for the given {@link URIish}.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java
index 0801b8a..715cbb4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java
@@ -679,7 +679,8 @@
 
 			verifySafeObject(tempObjectId, type, visit.data);
 			if (isCheckObjectCollisions() && readCurs.has(tempObjectId)) {
-				checkObjectCollision(tempObjectId, type, visit.data);
+				checkObjectCollision(tempObjectId, type, visit.data,
+						visit.delta.sizeBeforeInflating);
 			}
 
 			PackedObjectInfo oe;
@@ -999,6 +1000,7 @@
 			UnresolvedDelta n = onEndDelta();
 			n.position = streamPosition;
 			n.next = baseByPos.put(base, n);
+			n.sizeBeforeInflating = streamPosition() - streamPosition;
 			deltaCount++;
 			break;
 		}
@@ -1020,6 +1022,7 @@
 			inflateAndSkip(Source.INPUT, sz);
 			UnresolvedDelta n = onEndDelta();
 			n.position = streamPosition;
+			n.sizeBeforeInflating = streamPosition() - streamPosition;
 			r.add(n);
 			deltaCount++;
 			break;
@@ -1071,9 +1074,11 @@
 			verifySafeObject(tempObjectId, type, data);
 		}
 
+		long sizeBeforeInflating = streamPosition() - pos;
 		PackedObjectInfo obj = newInfo(tempObjectId, null, null);
 		obj.setOffset(pos);
 		obj.setType(type);
+		obj.setSize(sizeBeforeInflating);
 		onEndWholeObject(obj);
 		if (data != null)
 			onInflatedObjectData(obj, type, data);
@@ -1148,6 +1153,8 @@
 					sz -= n;
 				}
 			}
+			stats.incrementObjectsDuplicated();
+			stats.incrementNumBytesDuplicated(obj.getSize());
 		} catch (MissingObjectException notLocal) {
 			// This is OK, we don't have a copy of the object locally
 			// but the API throws when we try to read it as usually it's
@@ -1155,15 +1162,17 @@
 		}
 	}
 
-	private void checkObjectCollision(AnyObjectId obj, int type, byte[] data)
-			throws IOException {
+	private void checkObjectCollision(AnyObjectId obj, int type, byte[] data,
+			long sizeBeforeInflating) throws IOException {
 		try {
 			final ObjectLoader ldr = readCurs.open(obj, type);
 			final byte[] existingData = ldr.getCachedBytes(data.length);
 			if (!Arrays.equals(data, existingData)) {
-				throw new IOException(MessageFormat.format(
-						JGitText.get().collisionOn, obj.name()));
+				throw new IOException(MessageFormat
+						.format(JGitText.get().collisionOn, obj.name()));
 			}
+			stats.incrementObjectsDuplicated();
+			stats.incrementNumBytesDuplicated(sizeBeforeInflating);
 		} catch (MissingObjectException notLocal) {
 			// This is OK, we don't have a copy of the object locally
 			// but the API throws when we try to read it as usually its
@@ -1653,6 +1662,8 @@
 
 		UnresolvedDelta next;
 
+		long sizeBeforeInflating;
+
 		/** @return offset within the input stream. */
 		public long getOffset() {
 			return position;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackedObjectInfo.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackedObjectInfo.java
index fc906de..fe1209b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackedObjectInfo.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackedObjectInfo.java
@@ -29,6 +29,8 @@
 
 	private int type = Constants.OBJ_BAD;
 
+	private long sizeBeforeInflating;
+
 	PackedObjectInfo(final long headerOffset, final int packedCRC,
 			final AnyObjectId id) {
 		super(id);
@@ -108,4 +110,12 @@
 	public void setType(int type) {
 		this.type = type;
 	}
+
+	void setSize(long sizeBeforeInflating) {
+		this.sizeBeforeInflating = sizeBeforeInflating;
+	}
+
+	long getSize() {
+		return sizeBeforeInflating;
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java
index 52a5576..350311e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java
@@ -1,7 +1,7 @@
 /*
- * Copyright (C) 2008-2010, Google Inc.
- * Copyright (C) 2008-2009, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
+ * Copyright (C) 2008, 2010 Google Inc.
+ * Copyright (C) 2008, 2009 Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, 2020 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
@@ -50,7 +50,7 @@
 	 *             strings in the input stream until the marker is reached.
 	 */
 	@Deprecated
-	public static final String END = new StringBuilder(0).toString(); 	/* must not string pool */
+	public static final String END = new String(); /* must not string pool */
 
 	/**
 	 * Magic return from {@link #readString()} when a delim packet is found.
@@ -60,7 +60,7 @@
 	 *             string is the delimiter.
 	 */
 	@Deprecated
-	public static final String DELIM = new StringBuilder(0).toString(); 	/* must not string pool */
+	public static final String DELIM = new String(); /* must not string pool */
 
 	enum AckNackResult {
 		/** NAK */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivedPackStatistics.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivedPackStatistics.java
index bd8f558..d7bc400 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivedPackStatistics.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivedPackStatistics.java
@@ -19,6 +19,7 @@
  */
 public class ReceivedPackStatistics {
 	private long numBytesRead;
+	private long numBytesDuplicated;
 
 	private long numWholeCommit;
 	private long numWholeTree;
@@ -26,6 +27,7 @@
 	private long numWholeTag;
 	private long numOfsDelta;
 	private long numRefDelta;
+	private long numObjectsDuplicated;
 
 	private long numDeltaCommit;
 	private long numDeltaTree;
@@ -42,6 +44,17 @@
 	}
 
 	/**
+	 * Get number of bytes of objects already in the local database
+	 *
+	 * @return number of bytes of objects appeared in both the pack sent by the
+	 *         client and the local database
+	 * @since 5.10
+	 */
+	public long getNumBytesDuplicated() {
+		return numBytesDuplicated;
+	}
+
+	/**
 	 * Get number of whole commit objects in the pack
 	 *
 	 * @return number of whole commit objects in the pack
@@ -96,6 +109,17 @@
 	}
 
 	/**
+	 * Get number of objects already in the local database
+	 *
+	 * @return number of objects appeared in both the pack sent by the client
+	 *         and the local database
+	 * @since 5.10
+	 */
+	public long getNumObjectsDuplicated() {
+		return numObjectsDuplicated;
+	}
+
+	/**
 	 * Get number of delta commit objects in the pack
 	 *
 	 * @return number of delta commit objects in the pack
@@ -134,6 +158,7 @@
 	/** A builder for {@link ReceivedPackStatistics}. */
 	public static class Builder {
 		private long numBytesRead;
+		private long numBytesDuplicated;
 
 		private long numWholeCommit;
 		private long numWholeTree;
@@ -141,6 +166,7 @@
 		private long numWholeTag;
 		private long numOfsDelta;
 		private long numRefDelta;
+		private long numObjectsDuplicated;
 
 		private long numDeltaCommit;
 		private long numDeltaTree;
@@ -157,6 +183,17 @@
 		}
 
 		/**
+		 * @param size
+		 *            additional bytes already in the local database
+		 * @return this
+		 * @since 5.10
+		 */
+		Builder incrementNumBytesDuplicated(long size) {
+			numBytesDuplicated += size;
+			return this;
+		}
+
+		/**
 		 * Increment a whole object count.
 		 *
 		 * @param type OBJ_COMMIT, OBJ_TREE, OBJ_BLOB, or OBJ_TAG
@@ -196,6 +233,17 @@
 		}
 
 		/**
+		 * Increment the duplicated object count.
+		 *
+		 * @return this
+		 * @since 5.10
+		 */
+		Builder incrementObjectsDuplicated() {
+			numObjectsDuplicated++;
+			return this;
+		}
+
+		/**
 		 * Increment a delta object count.
 		 *
 		 * @param type OBJ_COMMIT, OBJ_TREE, OBJ_BLOB, or OBJ_TAG
@@ -226,6 +274,7 @@
 		ReceivedPackStatistics build() {
 			ReceivedPackStatistics s = new ReceivedPackStatistics();
 			s.numBytesRead = numBytesRead;
+			s.numBytesDuplicated = numBytesDuplicated;
 			s.numWholeCommit = numWholeCommit;
 			s.numWholeTree = numWholeTree;
 			s.numWholeBlob = numWholeBlob;
@@ -236,6 +285,7 @@
 			s.numDeltaTree = numDeltaTree;
 			s.numDeltaBlob = numDeltaBlob;
 			s.numDeltaTag = numDeltaTag;
+			s.numObjectsDuplicated = numObjectsDuplicated;
 			return s;
 		}
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteSession.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteSession.java
index f9f50d4..dd4c967 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteSession.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteSession.java
@@ -4,7 +4,7 @@
  * Copyright (C) 2009, Google, Inc.
  * Copyright (C) 2009, JetBrains s.r.o.
  * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
+ * Copyright (C) 2008, 2020 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
@@ -18,26 +18,20 @@
 import java.io.IOException;
 
 /**
- * Create a remote "session" for executing remote commands.
- * <p>
- * Clients should subclass RemoteSession to create an alternate way for JGit to
- * execute remote commands. (The client application may already have this
- * functionality available.) Note that this class is just a factory for creating
- * remote processes. If the application already has a persistent connection to
- * the remote machine, RemoteSession may do nothing more than return a new
- * RemoteProcess when exec is called.
+ * An abstraction of a remote "session" for executing remote commands.
  */
 public interface RemoteSession {
+
 	/**
-	 * Generate a new remote process to execute the given command. This function
-	 * should also start execution and may need to create the streams prior to
-	 * execution.
+	 * Creates a new remote {@link Process} to execute the given command. The
+	 * returned process's streams exist and are connected, and execution of the
+	 * process is already started.
 	 *
 	 * @param commandName
 	 *            command to execute
 	 * @param timeout
-	 *            timeout value, in seconds, for command execution
-	 * @return a new remote process
+	 *            timeout value, in seconds, for creating the remote process
+	 * @return a new remote process, already started
 	 * @throws java.io.IOException
 	 *             may be thrown in several cases. For example, on problems
 	 *             opening input or output streams or on problems connecting or
@@ -48,7 +42,7 @@
 	Process exec(String commandName, int timeout) throws IOException;
 
 	/**
-	 * Obtain an {@link FtpChannel} for performing FTP operations over this
+	 * Obtains an {@link FtpChannel} for performing FTP operations over this
 	 * {@link RemoteSession}. The default implementation returns {@code null}.
 	 *
 	 * @return the {@link FtpChannel}
@@ -59,7 +53,7 @@
 	}
 
 	/**
-	 * Disconnect the remote session
+	 * Disconnects the remote session.
 	 */
 	void disconnect();
 }
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 b1fac2c..fff2938 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 Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2018, 2020 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
@@ -117,6 +117,34 @@
 	/** Key in an ssh config file. */
 	public static final String PROXY_COMMAND = "ProxyCommand";
 
+	/**
+	 * Comma-separated list of jump hosts, defining a jump host chain <em>in
+	 * reverse order</em>. Each jump host is a SSH URI or "[user@]host[:port]".
+	 * <p>
+	 * Reverse order means: to connect A->B->target, one can do in
+	 * {@code ~/.ssh/config} either of:
+	 * </p>
+	 *
+	 * <pre>
+	 * Host target
+	 *   ProxyJump B,A
+	 * </pre>
+	 * <p>
+	 * <em>or</em>
+	 * </p>
+	 *
+	 * <pre>
+	 * Host target
+	 *   ProxyJump B
+	 *
+	 * Host B
+	 *   ProxyJump A
+	 * </pre>
+	 *
+	 * @since 5.10
+	 */
+	public static final String PROXY_JUMP = "ProxyJump";
+
 	/** Key in an ssh config file. */
 	public static final String REMOTE_COMMAND = "RemoteCommand";
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java
index ef845f4..e216a56a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
+ * Copyright (C) 2008, 2020 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
@@ -27,12 +27,15 @@
  * Different implementations of the session factory may be used to control
  * communicating with the end-user as well as reading their personal SSH
  * configuration settings, such as known hosts and private keys.
+ * </p>
  * <p>
- * A {@link org.eclipse.jgit.transport.RemoteSession} must be returned to the
- * factory that created it. Callers are encouraged to retain the
- * SshSessionFactory for the duration of the period they are using the Session.
+ * A {@link RemoteSession} must be returned to the factory that created it.
+ * Callers are encouraged to retain the SshSessionFactory for the duration of
+ * the period they are using the session.
+ * </p>
  */
 public abstract class SshSessionFactory {
+
 	private static SshSessionFactory INSTANCE = loadSshSessionFactory();
 
 	private static SshSessionFactory loadSshSessionFactory() {
@@ -43,12 +46,13 @@
 		}
 		return null;
 	}
-	
+
 	/**
-	 * Get the currently configured JVM-wide factory.
+	 * Gets the currently configured JVM-wide factory.
 	 * <p>
-	 * By default the factory will read from the user's <code>$HOME/.ssh</code>
-	 * and assume OpenSSH compatibility.
+	 * By default the factory will read from the user's {@code $HOME/.ssh} and
+	 * assume OpenSSH compatibility.
+	 * </p>
 	 *
 	 * @return factory the current factory for this JVM.
 	 */
@@ -57,11 +61,11 @@
 	}
 
 	/**
-	 * Change the JVM-wide factory to a different implementation.
+	 * Changes the JVM-wide factory to a different implementation.
 	 *
 	 * @param newFactory
-	 *            factory for future sessions to be created through. If null the
-	 *            default factory will be restored.
+	 *            factory for future sessions to be created through; if
+	 *            {@code null} the default factory will be restored.
 	 */
 	public static void setInstance(SshSessionFactory newFactory) {
 		if (newFactory != null) {
@@ -85,26 +89,23 @@
 	}
 
 	/**
-	 * Open (or reuse) a session to a host.
-	 * <p>
-	 * A reasonable UserInfo that can interact with the end-user (if necessary)
-	 * is installed on the returned session by this method.
-	 * <p>
-	 * The caller must connect the session by invoking <code>connect()</code> if
-	 * it has not already been connected.
+	 * Opens (or reuses) a session to a host. The returned session is connected
+	 * and authenticated and is ready for further use.
 	 *
 	 * @param uri
-	 *            URI information about the remote host
+	 *            URI of the remote host to connect to
 	 * @param credentialsProvider
-	 *            provider to support authentication, may be null.
+	 *            provider to support authentication, may be {@code null} if no
+	 *            user input for authentication is needed
 	 * @param fs
-	 *            the file system abstraction which will be necessary to perform
-	 *            certain file system operations.
+	 *            the file system abstraction to use for certain file
+	 *            operations, such as reading configuration files
 	 * @param tms
-	 *            Timeout value, in milliseconds.
-	 * @return a session that can contact the remote host.
+	 *            connection timeout for creating the session, in milliseconds
+	 * @return a connected and authenticated session for communicating with the
+	 *         remote host given by the {@code uri}
 	 * @throws org.eclipse.jgit.errors.TransportException
-	 *             the session could not be created.
+	 *             if the session could not be created
 	 */
 	public abstract RemoteSession getSession(URIish uri,
 			CredentialsProvider credentialsProvider, FS fs, int tms)
@@ -120,7 +121,7 @@
 	public abstract String getType();
 
 	/**
-	 * Close (or recycle) a session to a host.
+	 * Closes (or recycles) a session to a host.
 	 *
 	 * @param session
 	 *            a session previously obtained from this factory's
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
index 16169f0..6768387 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
@@ -49,6 +49,7 @@
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.InvalidPathException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -900,7 +901,9 @@
 			conn.setRequestProperty(HDR_ACCEPT_ENCODING, ENCODING_GZIP);
 		}
 		conn.setRequestProperty(HDR_PRAGMA, "no-cache"); //$NON-NLS-1$
-		if (UserAgent.get() != null) {
+		if (http.getUserAgent() != null) {
+			conn.setRequestProperty(HDR_USER_AGENT, http.getUserAgent());
+		} else if (UserAgent.get() != null) {
 			conn.setRequestProperty(HDR_USER_AGENT, UserAgent.get());
 		}
 		int timeOut = getTimeout();
@@ -909,6 +912,7 @@
 			conn.setConnectTimeout(effTimeOut);
 			conn.setReadTimeout(effTimeOut);
 		}
+		addHeaders(conn, http.getExtraHeaders());
 		// set cookie header if necessary
 		if (!relevantCookies.isEmpty()) {
 			setCookieHeader(conn);
@@ -923,6 +927,44 @@
 		return conn;
 	}
 
+	/**
+	 * Adds a list of header strings to the connection. Headers are expected to
+	 * separate keys from values, i.e. "Key: Value". Headers without colon or
+	 * key are ignored (and logged), as are headers with keys that are not RFC
+	 * 7230 tokens or with non-ASCII values.
+	 *
+	 * @param conn
+	 *            The target HttpConnection
+	 * @param headersToAdd
+	 *            A list of header strings
+	 */
+	static void addHeaders(HttpConnection conn, List<String> headersToAdd) {
+		for (String header : headersToAdd) {
+			// Empty values are allowed according to
+			// https://tools.ietf.org/html/rfc7230
+			int colon = header.indexOf(':');
+			String key = null;
+			if (colon > 0) {
+				key = header.substring(0, colon).trim();
+			}
+			if (key == null || key.isEmpty()) {
+				LOG.warn(MessageFormat.format(
+						JGitText.get().invalidHeaderFormat, header));
+			} else if (HttpSupport.scanToken(key, 0) != key.length()) {
+				LOG.warn(MessageFormat.format(JGitText.get().invalidHeaderKey,
+						header));
+			} else {
+				String value = header.substring(colon + 1).trim();
+				if (!StandardCharsets.US_ASCII.newEncoder().canEncode(value)) {
+					LOG.warn(MessageFormat
+							.format(JGitText.get().invalidHeaderValue, header));
+				} else {
+					conn.setRequestProperty(key, value);
+				}
+			}
+		}
+	}
+
 	private void setCookieHeader(HttpConnection conn) {
 		StringBuilder cookieHeaderValue = new StringBuilder();
 		for (HttpCookie cookie : relevantCookies) {
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 9889015..1242ef1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
@@ -42,6 +42,8 @@
 import java.io.OutputStream;
 import java.io.UncheckedIOException;
 import java.text.MessageFormat;
+import java.time.Duration;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -1008,7 +1010,7 @@
 			else
 				advertised = refIdSet(getAdvertisedOrDefaultRefs().values());
 
-			long negotiateStart = System.currentTimeMillis();
+			Instant negotiateStart = Instant.now();
 			accumulator.advertised = advertised.size();
 
 			ProtocolV0Parser parser = new ProtocolV0Parser(transferConfig);
@@ -1050,8 +1052,8 @@
 			if (!req.getClientShallowCommits().isEmpty())
 				walk.assumeShallow(req.getClientShallowCommits());
 			sendPack = negotiate(req, accumulator, pckOut);
-			accumulator.timeNegotiating += System.currentTimeMillis()
-					- negotiateStart;
+			accumulator.timeNegotiating = Duration
+					.between(negotiateStart, Instant.now()).toMillis();
 
 			if (sendPack && !biDirectionalPipe) {
 				// Ensure the request was fully consumed. Any remaining input must
@@ -1137,6 +1139,9 @@
 			advertised = refIdSet(getAdvertisedOrDefaultRefs().values());
 		}
 
+		PackStatistics.Accumulator accumulator = new PackStatistics.Accumulator();
+		Instant negotiateStart = Instant.now();
+
 		ProtocolV2Parser parser = new ProtocolV2Parser(transferConfig);
 		FetchV2Request req = parser.parseFetchRequest(pckIn);
 		currentRequest = req;
@@ -1186,7 +1191,8 @@
 
 		if (req.wasDoneReceived()) {
 			processHaveLines(req.getPeerHas(), ObjectId.zeroId(),
-					new PacketLineOut(NullOutputStream.INSTANCE));
+					new PacketLineOut(NullOutputStream.INSTANCE),
+					accumulator);
 		} else {
 			pckOut.writeString("acknowledgments\n"); //$NON-NLS-1$
 			for (ObjectId id : req.getPeerHas()) {
@@ -1195,7 +1201,8 @@
 				}
 			}
 			processHaveLines(req.getPeerHas(), ObjectId.zeroId(),
-					new PacketLineOut(NullOutputStream.INSTANCE));
+					new PacketLineOut(NullOutputStream.INSTANCE),
+					accumulator);
 			if (okToGiveUp()) {
 				pckOut.writeString("ready\n"); //$NON-NLS-1$
 			} else if (commonBase.isEmpty()) {
@@ -1238,7 +1245,11 @@
 				// But sideband-all is not used, so we have to write it ourselves.
 				pckOut.writeString("packfile\n"); //$NON-NLS-1$
 			}
-			sendPack(new PackStatistics.Accumulator(),
+
+			accumulator.timeNegotiating = Duration
+					.between(negotiateStart, Instant.now()).toMillis();
+
+			sendPack(accumulator,
 					req,
 					req.getClientCapabilities().contains(OPTION_INCLUDE_TAG)
 						? db.getRefDatabase().getRefsByPrefix(R_TAGS)
@@ -1641,7 +1652,7 @@
 			}
 
 			if (PacketLineIn.isEnd(line)) {
-				last = processHaveLines(peerHas, last, pckOut);
+				last = processHaveLines(peerHas, last, pckOut, accumulator);
 				if (commonBase.isEmpty() || multiAck != MultiAck.OFF)
 					pckOut.writeString("NAK\n"); //$NON-NLS-1$
 				if (noDone && sentReady) {
@@ -1656,7 +1667,7 @@
 				peerHas.add(ObjectId.fromString(line.substring(5)));
 				accumulator.haves++;
 			} else if (line.equals("done")) { //$NON-NLS-1$
-				last = processHaveLines(peerHas, last, pckOut);
+				last = processHaveLines(peerHas, last, pckOut, accumulator);
 
 				if (commonBase.isEmpty())
 					pckOut.writeString("NAK\n"); //$NON-NLS-1$
@@ -1672,11 +1683,12 @@
 		}
 	}
 
-	private ObjectId processHaveLines(List<ObjectId> peerHas, ObjectId last, PacketLineOut out)
+	private ObjectId processHaveLines(List<ObjectId> peerHas, ObjectId last,
+			PacketLineOut out, PackStatistics.Accumulator accumulator)
 			throws IOException {
 		preUploadHook.onBeginNegotiateRound(this, wantIds, peerHas.size());
 		if (wantAll.isEmpty() && !wantIds.isEmpty())
-			parseWants();
+			parseWants(accumulator);
 		if (peerHas.isEmpty())
 			return last;
 
@@ -1773,7 +1785,7 @@
 		return last;
 	}
 
-	private void parseWants() throws IOException {
+	private void parseWants(PackStatistics.Accumulator accumulator) throws IOException {
 		List<ObjectId> notAdvertisedWants = null;
 		for (ObjectId obj : wantIds) {
 			if (!advertised.contains(obj)) {
@@ -1782,9 +1794,18 @@
 				notAdvertisedWants.add(obj);
 			}
 		}
-		if (notAdvertisedWants != null)
+		if (notAdvertisedWants != null) {
+			accumulator.notAdvertisedWants = notAdvertisedWants.size();
+
+			Instant startReachabilityChecking = Instant.now();
+
 			requestValidator.checkWants(this, notAdvertisedWants);
 
+			accumulator.reachabilityCheckDuration = Duration
+					.between(startReachabilityChecking, Instant.now())
+					.toMillis();
+		}
+
 		AsyncRevObjectQueue q = walk.parseAny(wantIds, true);
 		try {
 			RevObject obj;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UserAgent.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UserAgent.java
index 5b63635..604eb3a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UserAgent.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UserAgent.java
@@ -46,7 +46,7 @@
 		return "unknown"; //$NON-NLS-1$
 	}
 
-	private static String clean(String s) {
+	static String clean(String s) {
 		s = s.trim();
 		StringBuilder b = new StringBuilder(s.length());
 		for (int i = 0; i < s.length(); i++) {
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 994af26..72278dc 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-2013, Robin Rosenberg and others
+ * Copyright (C) 2012-2020, 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
@@ -25,6 +25,7 @@
 import java.nio.CharBuffer;
 import java.nio.charset.CharacterCodingException;
 import java.nio.charset.CharsetEncoder;
+import java.nio.file.Path;
 import java.text.MessageFormat;
 import java.time.Instant;
 import java.util.Arrays;
@@ -48,8 +49,8 @@
 import org.eclipse.jgit.ignore.FastIgnoreRule;
 import org.eclipse.jgit.ignore.IgnoreNode;
 import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.CoreConfig;
 import org.eclipse.jgit.lib.CoreConfig.CheckStat;
 import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
 import org.eclipse.jgit.lib.CoreConfig.SymLinks;
@@ -522,6 +523,17 @@
 		return state.options;
 	}
 
+	/**
+	 * Retrieves the {@link Repository} this {@link WorkingTreeIterator}
+	 * operates on.
+	 *
+	 * @return the {@link Repository}
+	 * @since 5.9
+	 */
+	public Repository getRepository() {
+		return repository;
+	}
+
 	/** {@inheritDoc} */
 	@Override
 	public int idOffset() {
@@ -1297,15 +1309,11 @@
 			}
 
 			FS fs = repository.getFS();
-			String path = repository.getConfig().get(CoreConfig.KEY)
-					.getExcludesFile();
+			Path path = repository.getConfig().getPath(
+					ConfigConstants.CONFIG_CORE_SECTION, null,
+					ConfigConstants.CONFIG_KEY_EXCLUDESFILE, fs, null, null);
 			if (path != null) {
-				File excludesfile;
-				if (path.startsWith("~/")) //$NON-NLS-1$
-					excludesfile = fs.resolve(fs.userHome(), path.substring(2));
-				else
-					excludesfile = fs.resolve(null, path);
-				loadRulesFromFile(r, excludesfile);
+				loadRulesFromFile(r, path.toFile());
 			}
 
 			File exclude = fs.resolve(repository.getDirectory(),
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/IndexDiffFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/IndexDiffFilter.java
index 19cda42..4731f34 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/IndexDiffFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/IndexDiffFilter.java
@@ -217,14 +217,15 @@
 	 */
 	private void copyUntrackedFolders(String currentPath) {
 		String pathToBeSaved = null;
-		while (!untrackedParentFolders.isEmpty()
-				&& !currentPath.startsWith(untrackedParentFolders.getFirst()
-						+ "/")) //$NON-NLS-1$
+		while (!untrackedParentFolders.isEmpty() && !currentPath
+				.startsWith(untrackedParentFolders.getFirst() + '/')) {
 			pathToBeSaved = untrackedParentFolders.removeFirst();
+		}
 		if (pathToBeSaved != null) {
-			while (!untrackedFolders.isEmpty()
-					&& untrackedFolders.getLast().startsWith(pathToBeSaved))
+			while (!untrackedFolders.isEmpty() && untrackedFolders.getLast()
+					.startsWith(pathToBeSaved + '/')) {
 				untrackedFolders.removeLast();
+			}
 			untrackedFolders.addLast(pathToBeSaved);
 		}
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
index 91574ef..d8cab35 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
@@ -52,7 +52,7 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
-import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
@@ -186,12 +186,18 @@
 	 */
 	public static final class FileStoreAttributes {
 
+		/**
+		 * Marker to detect undefined values when reading from the config file.
+		 */
 		private static final Duration UNDEFINED_DURATION = Duration
 				.ofNanos(Long.MAX_VALUE);
 
 		/**
 		 * Fallback filesystem timestamp resolution. The worst case timestamp
 		 * resolution on FAT filesystems is 2 seconds.
+		 * <p>
+		 * Must be at least 1 second.
+		 * </p>
 		 */
 		public static final Duration FALLBACK_TIMESTAMP_RESOLUTION = Duration
 				.ofMillis(2000);
@@ -204,6 +210,25 @@
 		public static final FileStoreAttributes FALLBACK_FILESTORE_ATTRIBUTES = new FileStoreAttributes(
 				FALLBACK_TIMESTAMP_RESOLUTION);
 
+		private static final long ONE_MICROSECOND = TimeUnit.MICROSECONDS
+				.toNanos(1);
+
+		private static final long ONE_MILLISECOND = TimeUnit.MILLISECONDS
+				.toNanos(1);
+
+		private static final long ONE_SECOND = TimeUnit.SECONDS.toNanos(1);
+
+		/**
+		 * Minimum file system timestamp resolution granularity to check, in
+		 * nanoseconds. Should be a positive power of ten smaller than
+		 * {@link #ONE_SECOND}. Must be strictly greater than zero, i.e.,
+		 * minimum value is 1 nanosecond.
+		 * <p>
+		 * Currently set to 1 microsecond, but could also be lower still.
+		 * </p>
+		 */
+		private static final long MINIMUM_RESOLUTION_NANOS = ONE_MICROSECOND;
+
 		private static final String JAVA_VERSION_PREFIX = System
 				.getProperty("java.vendor") + '|' //$NON-NLS-1$
 				+ System.getProperty("java.version") + '|'; //$NON-NLS-1$
@@ -235,9 +260,10 @@
 		 * @see java.util.concurrent.Executors#newCachedThreadPool()
 		 */
 		private static final Executor FUTURE_RUNNER = new ThreadPoolExecutor(0,
-				5, 30L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(),
+				5, 30L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
 				runnable -> {
-					Thread t = new Thread(runnable, "FileStoreAttributeReader-" //$NON-NLS-1$
+					Thread t = new Thread(runnable,
+							"JGit-FileStoreAttributeReader-" //$NON-NLS-1$
 							+ threadNumber.getAndIncrement());
 					// Make sure these threads don't prevent application/JVM
 					// shutdown.
@@ -246,12 +272,34 @@
 				});
 
 		/**
+		 * Use a separate executor with at most one thread to synchronize
+		 * writing to the config. We write asynchronously since the config
+		 * itself might be on a different file system, which might otherwise
+		 * lead to locking problems.
+		 * <p>
+		 * Writing the config must not use a daemon thread, otherwise we may
+		 * leave an inconsistent state on disk when the JVM shuts down. Use a
+		 * small keep-alive time to avoid delays on shut-down.
+		 * </p>
+		 */
+		private static final Executor SAVE_RUNNER = new ThreadPoolExecutor(0, 1,
+				1L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(),
+				runnable -> {
+					Thread t = new Thread(runnable,
+							"JGit-FileStoreAttributeWriter-" //$NON-NLS-1$
+							+ threadNumber.getAndIncrement());
+					// Make sure these threads do finish
+					t.setDaemon(false);
+					return t;
+				});
+
+		/**
 		 * Whether FileStore attributes should be determined asynchronously
 		 *
 		 * @param async
 		 *            whether FileStore attributes should be determined
-		 *            asynchronously. If false access to cached attributes may block
-		 *            for some seconds for the first call per FileStore
+		 *            asynchronously. If false access to cached attributes may
+		 *            block for some seconds for the first call per FileStore
 		 * @since 5.6.2
 		 */
 		public static void setBackground(boolean async) {
@@ -294,6 +342,10 @@
 					return cached;
 				}
 				FileStoreAttributes attrs = getFileStoreAttributes(dir);
+				if (attrs == null) {
+					// Don't cache, result might be late
+					return FALLBACK_FILESTORE_ATTRIBUTES;
+				}
 				attrCacheByPath.put(dir, attrs);
 				return attrs;
 			} catch (SecurityException e) {
@@ -367,7 +419,9 @@
 									if (LOG.isDebugEnabled()) {
 										LOG.debug(c.toString());
 									}
-									saveToConfig(s, c);
+									FileStoreAttributes newAttrs = c;
+									SAVE_RUNNER.execute(
+											() -> saveToConfig(s, newAttrs));
 								}
 								attributes = Optional.of(c);
 							} finally {
@@ -382,12 +436,16 @@
 				});
 				// even if measuring in background wait a little - if the result
 				// arrives, it's better than returning the large fallback
-				Optional<FileStoreAttributes> d = background.get() ? f.get(
+				boolean runInBackground = background.get();
+				Optional<FileStoreAttributes> d = runInBackground ? f.get(
 						100, TimeUnit.MILLISECONDS) : f.get();
 				if (d.isPresent()) {
 					return d.get();
+				} else if (runInBackground) {
+					// return null until measurement is finished
+					return null;
 				}
-				// return fallback until measurement is finished
+				// fall through and return fallback
 			} catch (IOException | InterruptedException
 					| ExecutionException | CancellationException e) {
 				LOG.error(e.getMessage(), e);
@@ -467,24 +525,21 @@
 
 		private static Optional<Duration> measureFsTimestampResolution(
 			FileStore s, Path dir) {
-			LOG.debug("{}: start measure timestamp resolution {} in {}", //$NON-NLS-1$
-					Thread.currentThread(), s, dir);
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("{}: start measure timestamp resolution {} in {}", //$NON-NLS-1$
+						Thread.currentThread(), s, dir);
+			}
 			Path probe = dir.resolve(".probe-" + UUID.randomUUID()); //$NON-NLS-1$
 			try {
 				Files.createFile(probe);
-				FileTime t1 = Files.getLastModifiedTime(probe);
-				FileTime t2 = t1;
-				Instant t1i = t1.toInstant();
-				for (long i = 1; t2.compareTo(t1) <= 0; i += 1 + i / 20) {
-					Files.setLastModifiedTime(probe,
-							FileTime.from(t1i.plusNanos(i * 1000)));
-					t2 = Files.getLastModifiedTime(probe);
-				}
-				Duration fsResolution = Duration.between(t1.toInstant(), t2.toInstant());
+				Duration fsResolution = getFsResolution(s, dir, probe);
 				Duration clockResolution = measureClockResolution();
 				fsResolution = fsResolution.plus(clockResolution);
-				LOG.debug("{}: end measure timestamp resolution {} in {}", //$NON-NLS-1$
-						Thread.currentThread(), s, dir);
+				if (LOG.isDebugEnabled()) {
+					LOG.debug(
+							"{}: end measure timestamp resolution {} in {}; got {}", //$NON-NLS-1$
+							Thread.currentThread(), s, dir, fsResolution);
+				}
 				return Optional.of(fsResolution);
 			} catch (SecurityException e) {
 				// Log it here; most likely deleteProbe() below will also run
@@ -501,6 +556,92 @@
 			return Optional.empty();
 		}
 
+		private static Duration getFsResolution(FileStore s, Path dir,
+				Path probe) throws IOException {
+			File probeFile = probe.toFile();
+			FileTime t1 = Files.getLastModifiedTime(probe);
+			Instant t1i = t1.toInstant();
+			FileTime t2;
+			Duration last = FALLBACK_TIMESTAMP_RESOLUTION;
+			long minScale = MINIMUM_RESOLUTION_NANOS;
+			long scale = ONE_SECOND;
+			long high = TimeUnit.MILLISECONDS.toSeconds(last.toMillis());
+			long low = 0;
+			// Try up-front at microsecond and millisecond
+			long[] tries = { ONE_MICROSECOND, ONE_MILLISECOND };
+			for (long interval : tries) {
+				if (interval >= ONE_MILLISECOND) {
+					probeFile.setLastModified(
+							t1i.plusNanos(interval).toEpochMilli());
+				} else {
+					Files.setLastModifiedTime(probe,
+							FileTime.from(t1i.plusNanos(interval)));
+				}
+				t2 = Files.getLastModifiedTime(probe);
+				if (t2.compareTo(t1) > 0) {
+					Duration diff = Duration.between(t1i, t2.toInstant());
+					if (!diff.isZero() && !diff.isNegative()
+							&& diff.compareTo(last) < 0) {
+						scale = interval;
+						high = 1;
+						last = diff;
+						break;
+					}
+				} else {
+					// Makes no sense going below
+					minScale = Math.max(minScale, interval);
+				}
+			}
+			// Binary search loop
+			while (high > low) {
+				long mid = (high + low) / 2;
+				if (mid == 0) {
+					// Smaller than current scale. Adjust scale.
+					long newScale = scale / 10;
+					if (newScale < minScale) {
+						break;
+					}
+					high *= scale / newScale;
+					low *= scale / newScale;
+					scale = newScale;
+					mid = (high + low) / 2;
+				}
+				long delta = mid * scale;
+				if (scale >= ONE_MILLISECOND) {
+					probeFile.setLastModified(
+							t1i.plusNanos(delta).toEpochMilli());
+				} else {
+					Files.setLastModifiedTime(probe,
+							FileTime.from(t1i.plusNanos(delta)));
+				}
+				t2 = Files.getLastModifiedTime(probe);
+				int cmp = t2.compareTo(t1);
+				if (cmp > 0) {
+					high = mid;
+					Duration diff = Duration.between(t1i, t2.toInstant());
+					if (diff.isZero() || diff.isNegative()) {
+						LOG.warn(JGitText.get().logInconsistentFiletimeDiff,
+								Thread.currentThread(), s, dir, t2, t1, diff,
+								last);
+						break;
+					} else if (diff.compareTo(last) > 0) {
+						LOG.warn(JGitText.get().logLargerFiletimeDiff,
+								Thread.currentThread(), s, dir, diff, last);
+						break;
+					}
+					last = diff;
+				} else if (cmp < 0) {
+					LOG.warn(JGitText.get().logSmallerFiletime,
+							Thread.currentThread(), s, dir, t2, t1, last);
+					break;
+				} else {
+					// No discernible difference
+					low = mid + 1;
+				}
+			}
+			return last;
+		}
+
 		private static Duration measureClockResolution() {
 			Duration clockResolution = Duration.ZERO;
 			for (int i = 0; i < 10; i++) {
@@ -939,8 +1080,9 @@
 	}
 
 	/**
-	 * Set the last modified time of a file system object. If the OS/JRE support
-	 * symbolic links, the link is modified, not the target,
+	 * Set the last modified time of a file system object.
+	 * <p>
+	 * For symlinks it sets the modified time of the link target.
 	 *
 	 * @param f
 	 *            a {@link java.io.File} object.
@@ -956,8 +1098,9 @@
 	}
 
 	/**
-	 * Set the last modified time of a file system object. If the OS/JRE support
-	 * symbolic links, the link is modified, not the target,
+	 * Set the last modified time of a file system object.
+	 * <p>
+	 * For symlinks it sets the modified time of the link target.
 	 *
 	 * @param p
 	 *            a {@link Path} object.
@@ -1356,7 +1499,7 @@
 		String v;
 		try {
 			v = readPipe(gitExe.getParentFile(),
-				new String[] { "git", "--version" }, //$NON-NLS-1$ //$NON-NLS-2$
+					new String[] { gitExe.getPath(), "--version" }, //$NON-NLS-1$
 				Charset.defaultCharset().name());
 		} catch (CommandFailedException e) {
 			LOG.warn(e.getMessage());
@@ -1375,7 +1518,8 @@
 		String w;
 		try {
 			w = readPipe(gitExe.getParentFile(),
-				new String[] { "git", "config", "--system", "--edit" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+					new String[] { gitExe.getPath(), "config", "--system", //$NON-NLS-1$ //$NON-NLS-2$
+							"--edit" }, //$NON-NLS-1$
 				Charset.defaultCharset().name(), env);
 		} catch (CommandFailedException e) {
 			LOG.warn(e.getMessage());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java
index c9d2770..fb63dc0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java
@@ -54,6 +54,8 @@
 public class FS_POSIX extends FS {
 	private static final Logger LOG = LoggerFactory.getLogger(FS_POSIX.class);
 
+	private static final String DEFAULT_GIT_LOCATION = "/usr/bin/git"; //$NON-NLS-1$
+
 	private static final int DEFAULT_UMASK = 0022;
 	private volatile int umask = -1;
 
@@ -138,24 +140,46 @@
 		String path = SystemReader.getInstance().getenv("PATH"); //$NON-NLS-1$
 		File gitExe = searchPath(path, "git"); //$NON-NLS-1$
 
-		if (gitExe == null) {
-			if (SystemReader.getInstance().isMacOS()) {
+		if (SystemReader.getInstance().isMacOS()) {
+			if (gitExe == null
+					|| DEFAULT_GIT_LOCATION.equals(gitExe.getPath())) {
 				if (searchPath(path, "bash") != null) { //$NON-NLS-1$
 					// On MacOSX, PATH is shorter when Eclipse is launched from the
 					// Finder than from a terminal. Therefore try to launch bash as a
 					// login shell and search using that.
-					String w;
 					try {
-						w = readPipe(userHome(),
+						String w = readPipe(userHome(),
 							new String[]{"bash", "--login", "-c", "which git"}, // //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
 							Charset.defaultCharset().name());
+						if (!StringUtils.isEmptyOrNull(w)) {
+							gitExe = new File(w);
+						}
 					} catch (CommandFailedException e) {
 						LOG.warn(e.getMessage());
-						return null;
 					}
-					if (!StringUtils.isEmptyOrNull(w)) {
-						gitExe = new File(w);
+				}
+			}
+			if (gitExe != null
+					&& DEFAULT_GIT_LOCATION.equals(gitExe.getPath())) {
+				// If we still have the default git exe, it's an XCode wrapper
+				// that may prompt the user to install the XCode command line
+				// tools if not already present. Avoid the prompt by returning
+				// null if no XCode git is there.
+				try {
+					String w = readPipe(userHome(),
+							new String[] { "xcode-select", "-p" }, //$NON-NLS-1$ //$NON-NLS-2$
+							Charset.defaultCharset().name());
+					if (StringUtils.isEmptyOrNull(w)) {
+						gitExe = null;
+					} else {
+						File realGitExe = new File(new File(w),
+								DEFAULT_GIT_LOCATION.substring(1));
+						if (!realGitExe.exists()) {
+							gitExe = null;
+						}
 					}
+				} catch (CommandFailedException e) {
+					gitExe = null;
 				}
 			}
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
index c43956e..aa39a44 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
@@ -710,6 +710,8 @@
 	}
 
 	/**
+	 * Set the last modified time of a file system object.
+	 *
 	 * @param file
 	 * @param time
 	 * @throws IOException
@@ -720,6 +722,8 @@
 	}
 
 	/**
+	 * Set the last modified time of a file system object.
+	 *
 	 * @param path
 	 * @param time
 	 * @throws IOException
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java
index 8ff649b..04b3eab 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java
@@ -424,6 +424,69 @@
 		}
 	}
 
+	/**
+	 * Scan a RFC 7230 token as it appears in HTTP headers.
+	 *
+	 * @param header
+	 *            to scan in
+	 * @param from
+	 *            index in {@code header} to start scanning at
+	 * @return the index after the token, that is, on the first non-token
+	 *         character or {@code header.length}
+	 * @throws IndexOutOfBoundsException
+	 *             if {@code from < 0} or {@code from > header.length()}
+	 *
+	 * @see <a href="https://tools.ietf.org/html/rfc7230#appendix-B">RFC 7230,
+	 *      Appendix B: Collected Grammar; "token" production</a>
+	 * @since 5.10
+	 */
+	public static int scanToken(String header, int from) {
+		int length = header.length();
+		int i = from;
+		if (i < 0 || i > length) {
+			throw new IndexOutOfBoundsException();
+		}
+		while (i < length) {
+			char c = header.charAt(i);
+			switch (c) {
+			case '!':
+			case '#':
+			case '$':
+			case '%':
+			case '&':
+			case '\'':
+			case '*':
+			case '+':
+			case '-':
+			case '.':
+			case '^':
+			case '_':
+			case '`':
+			case '|':
+			case '~':
+			case '0':
+			case '1':
+			case '2':
+			case '3':
+			case '4':
+			case '5':
+			case '6':
+			case '7':
+			case '8':
+			case '9':
+				i++;
+				break;
+			default:
+				if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
+					i++;
+					break;
+				}
+				return i;
+			}
+		}
+		return i;
+	}
+
 	private HttpSupport() {
 		// Utility class only.
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SshSupport.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SshSupport.java
index a151cd3..e297041 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SshSupport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SshSupport.java
@@ -11,6 +11,7 @@
 
 import java.io.IOException;
 import java.text.MessageFormat;
+import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.errors.CommandFailedException;
@@ -61,11 +62,21 @@
 		CommandFailedException failure = null;
 		@SuppressWarnings("resource")
 		MessageWriter stderr = new MessageWriter();
+		@SuppressWarnings("resource")
+		MessageWriter stdout = new MessageWriter();
 		String out;
-		try (MessageWriter stdout = new MessageWriter()) {
+		try {
+			long start = System.nanoTime();
 			session = SshSessionFactory.getInstance().getSession(sshUri,
 					provider, fs, 1000 * timeout);
-			process = session.exec(command, 0);
+			int commandTimeout = timeout;
+			if (timeout > 0) {
+				commandTimeout = checkTimeout(command, timeout, start);
+			}
+			process = session.exec(command, commandTimeout);
+			if (timeout > 0) {
+				commandTimeout = checkTimeout(command, timeout, start);
+			}
 			errorThread = new StreamCopyThread(process.getErrorStream(),
 					stderr.getRawStream());
 			errorThread.start();
@@ -73,9 +84,15 @@
 					stdout.getRawStream());
 			outThread.start();
 			try {
-				// waitFor with timeout has a bug - JSch' exitValue() throws the
-				// wrong exception type :(
-				if (process.waitFor() == 0) {
+				boolean finished = false;
+				if (timeout <= 0) {
+					process.waitFor();
+					finished = true;
+				} else {
+					finished = process.waitFor(commandTimeout,
+							TimeUnit.SECONDS);
+				}
+				if (finished) {
 					out = stdout.toString();
 				} else {
 					out = null; // still running after timeout
@@ -103,15 +120,26 @@
 				}
 			}
 			if (process != null) {
-				if (process.exitValue() != 0) {
-					failure = new CommandFailedException(process.exitValue(),
+				try {
+					if (process.exitValue() != 0) {
+						failure = new CommandFailedException(
+								process.exitValue(),
+								MessageFormat.format(
+										JGitText.get().sshCommandFailed,
+										command, stderr.toString()));
+					}
+					// It was successful after all
+					out = stdout.toString();
+				} catch (IllegalThreadStateException e) {
+					failure = new CommandFailedException(0,
 							MessageFormat.format(
-							JGitText.get().sshCommandFailed, command,
-							stderr.toString()));
+									JGitText.get().sshCommandTimeout, command,
+									Integer.valueOf(timeout)));
 				}
 				process.destroy();
 			}
 			stderr.close();
+			stdout.close();
 			if (session != null) {
 				SshSessionFactory.getInstance().releaseSession(session);
 			}
@@ -122,4 +150,17 @@
 		return out;
 	}
 
+	private static int checkTimeout(String command, int timeout, long since)
+			throws CommandFailedException {
+		long elapsed = System.nanoTime() - since;
+		int newTimeout = timeout
+				- (int) TimeUnit.NANOSECONDS.toSeconds(elapsed);
+		if (newTimeout <= 0) {
+			// All time used up for connecting the session
+			throw new CommandFailedException(0,
+					MessageFormat.format(JGitText.get().sshCommandTimeout,
+							command, Integer.valueOf(timeout)));
+		}
+		return newTimeout;
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
index bcb8380..447f417 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
@@ -17,7 +17,6 @@
 import java.io.IOException;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
-import java.nio.file.Files;
 import java.nio.file.InvalidPathException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -116,11 +115,9 @@
 						.getAbsolutePath();
 			}
 			try {
-				Path xdgHomePath = Paths.get(configHomePath);
-				Files.createDirectories(xdgHomePath);
-				return xdgHomePath;
-			} catch (IOException | InvalidPathException e) {
-				LOG.error(JGitText.get().createXDGConfigHomeFailed,
+				return Paths.get(configHomePath);
+			} catch (InvalidPathException e) {
+				LOG.error(JGitText.get().logXDGConfigHomeInvalid,
 						configHomePath, e);
 			}
 			return null;
@@ -130,16 +127,9 @@
 		public FileBasedConfig openJGitConfig(Config parent, FS fs) {
 			Path xdgPath = getXDGConfigHome(fs);
 			if (xdgPath != null) {
-				Path configPath = null;
-				try {
-					configPath = xdgPath.resolve("jgit"); //$NON-NLS-1$
-					Files.createDirectories(configPath);
-					configPath = configPath.resolve(Constants.CONFIG);
-					return new FileBasedConfig(parent, configPath.toFile(), fs);
-				} catch (IOException e) {
-					LOG.error(JGitText.get().createJGitConfigFailed, configPath,
-							e);
-				}
+				Path configPath = xdgPath.resolve("jgit") //$NON-NLS-1$
+						.resolve(Constants.CONFIG);
+				return new FileBasedConfig(parent, configPath.toFile(), fs);
 			}
 			return new FileBasedConfig(parent,
 					new File(fs.userHome(), ".jgitconfig"), fs); //$NON-NLS-1$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java
index 8c9b1bf..0e335a9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2010, 2013 Marc Strapetz <marc.strapetz@syntevo.com>
- * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com> and others
+ * Copyright (C) 2015, 2020 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 v. 1.0 which is available at
@@ -13,26 +13,58 @@
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.Set;
 
 import org.eclipse.jgit.diff.RawText;
 
 /**
  * An InputStream that normalizes CRLF to LF.
- *
- * Existing single CR are not changed to LF, but retained as is.
- *
- * Optionally, a binary check on the first 8000 bytes is performed and in case
- * of binary files, canonicalization is turned off (for the complete file).
  * <p>
- * This is the former EolCanonicalizingInputStream with a new name in order to
- * have same naming for all LF / CRLF streams
+ * Existing single CR are not changed to LF but are retained as is.
+ * </p>
+ * <p>
+ * Optionally, a binary check on the first 8kB is performed and in case of
+ * binary files, canonicalization is turned off (for the complete file). If
+ * binary checking determines that the input is CR/LF-delimited text and the
+ * stream has been created for checkout, canonicalization is also turned off.
+ * </p>
  *
  * @since 4.3
  */
 public class AutoLFInputStream extends InputStream {
+
+	// This is the former EolCanonicalizingInputStream with a new name in order
+	// to have same naming for all LF / CRLF streams.
+
+	/**
+	 * Flags for controlling auto-detection of binary vs. text content (for
+	 * text=auto).
+	 *
+	 * @since 5.9
+	 */
+	public enum StreamFlag {
+		/**
+		 * Check the first 8kB for binary content and switch off
+		 * canonicalization off for the whole file if so.
+		 */
+		DETECT_BINARY,
+		/**
+		 * If {@link #DETECT_BINARY} is set, throw an {@link IsBinaryException}
+		 * if binary content is detected.
+		 */
+		ABORT_IF_BINARY,
+		/**
+		 * If {@link #DETECT_BINARY} is set and content is found to be CR-LF
+		 * delimited text, switch off canonicalization.
+		 */
+		FOR_CHECKOUT
+	}
+
 	private final byte[] single = new byte[1];
 
-	private final byte[] buf = new byte[8096];
+	private final byte[] buf = new byte[8 * 1024];
 
 	private final InputStream in;
 
@@ -40,11 +72,23 @@
 
 	private int ptr;
 
+	/**
+	 * Set to {@code true} if no CR/LF processing is to be done: if the input is
+	 * binary data, or CR/LF-delimited text and {@link StreamFlag#FOR_CHECKOUT}
+	 * was given.
+	 */
+	private boolean passAsIs;
+
+	/**
+	 * Set to {@code true} if the input was detected to be binary data.
+	 */
 	private boolean isBinary;
 
 	private boolean detectBinary;
 
-	private boolean abortIfBinary;
+	private final boolean abortIfBinary;
+
+	private final boolean forCheckout;
 
 	/**
 	 * A special exception thrown when {@link AutoLFInputStream} is told to
@@ -62,20 +106,64 @@
 	}
 
 	/**
-	 * Creates a new InputStream, wrapping the specified stream
+	 * Factory method for creating an {@link AutoLFInputStream} with the
+	 * specified {@link StreamFlag flags}.
+	 *
+	 * @param in
+	 *            raw input stream
+	 * @param flags
+	 *            {@link StreamFlag}s controlling the stream behavior
+	 * @return a new {@link AutoLFInputStream}
+	 * @since 5.9
+	 */
+	public static AutoLFInputStream create(InputStream in,
+			StreamFlag... flags) {
+		if (flags == null) {
+			return new AutoLFInputStream(in, null);
+		}
+		EnumSet<StreamFlag> set = EnumSet.noneOf(StreamFlag.class);
+		set.addAll(Arrays.asList(flags));
+		return new AutoLFInputStream(in, set);
+	}
+
+	/**
+	 * Creates a new InputStream, wrapping the specified stream.
+	 *
+	 * @param in
+	 *            raw input stream
+	 * @param flags
+	 *            {@link StreamFlag}s controlling the stream behavior;
+	 *            {@code null} is treated as an empty set
+	 * @since 5.9
+	 */
+	public AutoLFInputStream(InputStream in, Set<StreamFlag> flags) {
+		this.in = in;
+		this.detectBinary = flags != null
+				&& flags.contains(StreamFlag.DETECT_BINARY);
+		this.abortIfBinary = flags != null
+				&& flags.contains(StreamFlag.ABORT_IF_BINARY);
+		this.forCheckout = flags != null
+				&& flags.contains(StreamFlag.FOR_CHECKOUT);
+	}
+
+	/**
+	 * Creates a new InputStream, wrapping the specified stream.
 	 *
 	 * @param in
 	 *            raw input stream
 	 * @param detectBinary
 	 *            whether binaries should be detected
 	 * @since 2.0
+	 * @deprecated since 5.9, use {@link #create(InputStream, StreamFlag...)}
+	 *             instead
 	 */
+	@Deprecated
 	public AutoLFInputStream(InputStream in, boolean detectBinary) {
 		this(in, detectBinary, false);
 	}
 
 	/**
-	 * Creates a new InputStream, wrapping the specified stream
+	 * Creates a new InputStream, wrapping the specified stream.
 	 *
 	 * @param in
 	 *            raw input stream
@@ -84,12 +172,16 @@
 	 * @param abortIfBinary
 	 *            throw an IOException if the file is binary
 	 * @since 3.3
+	 * @deprecated since 5.9, use {@link #create(InputStream, StreamFlag...)}
+	 *             instead
 	 */
+	@Deprecated
 	public AutoLFInputStream(InputStream in, boolean detectBinary,
 			boolean abortIfBinary) {
 		this.in = in;
 		this.detectBinary = detectBinary;
 		this.abortIfBinary = abortIfBinary;
+		this.forCheckout = false;
 	}
 
 	/** {@inheritDoc} */
@@ -118,7 +210,7 @@
 			}
 
 			byte b = buf[ptr++];
-			if (isBinary || b != '\r') {
+			if (passAsIs || b != '\r') {
 				// Logic for binary files ends here
 				bs[i++] = b;
 				continue;
@@ -170,9 +262,14 @@
 		}
 		if (detectBinary) {
 			isBinary = RawText.isBinary(buf, cnt);
+			passAsIs = isBinary;
 			detectBinary = false;
-			if (isBinary && abortIfBinary)
+			if (isBinary && abortIfBinary) {
 				throw new IsBinaryException();
+			}
+			if (!passAsIs && forCheckout) {
+				passAsIs = RawText.isCrLfText(buf, cnt);
+			}
 		}
 		ptr = 0;
 		return true;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java
index e235aa0..195fdb4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java
@@ -1,43 +1,12 @@
 /*
  * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com>
+ * Copyright (C) 2020, 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 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.util.io;
@@ -49,11 +18,15 @@
 
 /**
  * An OutputStream that reduces CRLF to LF.
- *
+ * <p>
  * Existing single CR are not changed to LF, but retained as is.
- *
+ * </p>
+ * <p>
  * A binary check on the first 8000 bytes is performed and in case of binary
- * files, canonicalization is turned off (for the complete file).
+ * files, canonicalization is turned off (for the complete file). If the binary
+ * check determines that the input is not binary but text with CR/LF,
+ * canonicalization is also turned off.
+ * </p>
  *
  * @since 4.3
  */
@@ -76,9 +49,7 @@
 	private boolean isBinary;
 
 	/**
-	 * <p>
 	 * Constructor for AutoLFOutputStream.
-	 * </p>
 	 *
 	 * @param out
 	 *            an {@link java.io.OutputStream} object.
@@ -88,9 +59,7 @@
 	}
 
 	/**
-	 * <p>
 	 * Constructor for AutoLFOutputStream.
-	 * </p>
 	 *
 	 * @param out
 	 *            an {@link java.io.OutputStream} object.
@@ -123,14 +92,11 @@
 	public void write(byte[] b, int startOff, int startLen)
 			throws IOException {
 		final int overflow = buffer(b, startOff, startLen);
-		if (overflow < 0) {
+		if (overflow <= 0) {
 			return;
 		}
 		final int off = startOff + startLen - overflow;
 		final int len = overflow;
-		if (len == 0) {
-			return;
-		}
 		int lastw = off;
 		if (isBinary) {
 			out.write(b, off, len);
@@ -190,6 +156,9 @@
 	private void decideMode() throws IOException {
 		if (detectBinary) {
 			isBinary = RawText.isBinary(binbuf, binbufcnt);
+			if (!isBinary) {
+				isBinary = RawText.isCrLfText(binbuf, binbufcnt);
+			}
 			detectBinary = false;
 		}
 		int cachedLen = binbufcnt;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolStreamTypeUtil.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolStreamTypeUtil.java
index c33c869..88ee2ae 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolStreamTypeUtil.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolStreamTypeUtil.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com> and others
+ * Copyright (C) 2015, 2020 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 v. 1.0 which is available at
@@ -12,12 +12,14 @@
 
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.EnumSet;
 
 import org.eclipse.jgit.attributes.Attributes;
 import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
 import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
 import org.eclipse.jgit.treewalk.WorkingTreeOptions;
 import org.eclipse.jgit.util.SystemReader;
+import org.eclipse.jgit.util.io.AutoLFInputStream.StreamFlag;
 
 /**
  * Utility used to create input and output stream wrappers for
@@ -71,7 +73,7 @@
 
 	/**
 	 * Wrap the input stream depending on
-	 * {@link org.eclipse.jgit.lib.CoreConfig.EolStreamType}
+	 * {@link org.eclipse.jgit.lib.CoreConfig.EolStreamType}.
 	 *
 	 * @param in
 	 *            original stream
@@ -82,15 +84,38 @@
 	 */
 	public static InputStream wrapInputStream(InputStream in,
 			EolStreamType conversion) {
+		return wrapInputStream(in, conversion, false);
+	}
+
+	/**
+	 * Wrap the input stream depending on
+	 * {@link org.eclipse.jgit.lib.CoreConfig.EolStreamType}.
+	 *
+	 * @param in
+	 *            original stream
+	 * @param conversion
+	 *            to be performed
+	 * @param forCheckout
+	 *            whether the stream is for checking out from the repository
+	 * @return the converted stream depending on
+	 *         {@link org.eclipse.jgit.lib.CoreConfig.EolStreamType}
+	 * @since 5.9
+	 */
+	public static InputStream wrapInputStream(InputStream in,
+			EolStreamType conversion, boolean forCheckout) {
 		switch (conversion) {
 		case TEXT_CRLF:
 			return new AutoCRLFInputStream(in, false);
 		case TEXT_LF:
-			return new AutoLFInputStream(in, false);
+			return AutoLFInputStream.create(in);
 		case AUTO_CRLF:
 			return new AutoCRLFInputStream(in, true);
 		case AUTO_LF:
-			return new AutoLFInputStream(in, true);
+			EnumSet<StreamFlag> flags = forCheckout
+					? EnumSet.of(StreamFlag.DETECT_BINARY,
+							StreamFlag.FOR_CHECKOUT)
+					: EnumSet.of(StreamFlag.DETECT_BINARY);
+			return new AutoLFInputStream(in, flags);
 		default:
 			return in;
 		}
@@ -98,7 +123,7 @@
 
 	/**
 	 * Wrap the output stream depending on
-	 * {@link org.eclipse.jgit.lib.CoreConfig.EolStreamType}
+	 * {@link org.eclipse.jgit.lib.CoreConfig.EolStreamType}.
 	 *
 	 * @param out
 	 *            original stream
diff --git a/pom.xml b/pom.xml
index a714f23..cf581fe 100644
--- a/pom.xml
+++ b/pom.xml
@@ -33,13 +33,13 @@
   </description>
 
   <scm>
-    <url>http://git.eclipse.org/c/jgit/jgit.git/</url>
+    <url>https://git.eclipse.org/r/plugins/gitiles/jgit/jgit</url>
     <connection>scm:git:https://git.eclipse.org/r/jgit/jgit</connection>
   </scm>
 
   <ciManagement>
-    <system>hudson</system>
-    <url>https://hudson.eclipse.org/jgit/</url>
+    <system>Jenkins</system>
+    <url>https://ci.eclipse.org/jgit</url>
   </ciManagement>
 
   <developers>
@@ -140,7 +140,7 @@
   </licenses>
 
   <properties>
-    <jgit-url>http://www.eclipse.org/jgit/</jgit-url>
+    <jgit-url>https://www.eclipse.org/jgit/</jgit-url>
     <jgit-copyright>Copyright (c) 2005, 2009 Shawn Pearce, Robin Rosenberg, et.al.</jgit-copyright>
     <jgit.website.url>scp://build.eclipse.org/home/data/httpd/download.eclipse.org/jgit/site/${project.version}/</jgit.website.url>
 
@@ -151,7 +151,7 @@
     <maven.compiler.target>1.8</maven.compiler.target>
     <bundle-manifest>${project.build.directory}/META-INF/MANIFEST.MF</bundle-manifest>
 
-    <jgit-last-release-version>5.7.0.202003110725-r</jgit-last-release-version>
+    <jgit-last-release-version>5.9.0.202009080501-r</jgit-last-release-version>
     <apache-sshd-version>2.4.0</apache-sshd-version>
     <jsch-version>0.1.55</jsch-version>
     <jzlib-version>1.1.1</jzlib-version>
@@ -162,22 +162,23 @@
     <commons-compress-version>1.19</commons-compress-version>
     <osgi-core-version>4.3.1</osgi-core-version>
     <servlet-api-version>3.1.0</servlet-api-version>
-    <jetty-version>9.4.28.v20200408</jetty-version>
+    <jetty-version>9.4.30.v20200611</jetty-version>
     <japicmp-version>0.14.3</japicmp-version>
     <httpclient-version>4.5.10</httpclient-version>
     <httpcore-version>4.4.12</httpcore-version>
-    <slf4j-version>1.7.2</slf4j-version>
+    <slf4j-version>1.7.30</slf4j-version>
     <log4j-version>1.2.15</log4j-version>
     <maven-javadoc-plugin-version>3.2.0</maven-javadoc-plugin-version>
     <tycho-extras-version>1.7.0</tycho-extras-version>
     <gson-version>2.8.2</gson-version>
     <bouncycastle-version>1.65</bouncycastle-version>
-    <spotbugs-maven-plugin-version>4.0.0</spotbugs-maven-plugin-version>
-    <maven-project-info-reports-plugin-version>3.1.0</maven-project-info-reports-plugin-version>
+    <spotbugs-maven-plugin-version>4.1.3</spotbugs-maven-plugin-version>
+    <maven-project-info-reports-plugin-version>3.1.1</maven-project-info-reports-plugin-version>
     <maven-jxr-plugin-version>3.0.0</maven-jxr-plugin-version>
     <maven-surefire-plugin-version>3.0.0-M4</maven-surefire-plugin-version>
     <maven-surefire-report-plugin-version>${maven-surefire-plugin-version}</maven-surefire-report-plugin-version>
     <maven-compiler-plugin-version>3.8.1</maven-compiler-plugin-version>
+    <plexus-compiler-version>2.8.8</plexus-compiler-version>
 
     <!-- Properties to enable jacoco code coverage analysis -->
     <sonar.core.codeCoveragePlugin>jacoco</sonar.core.codeCoveragePlugin>
@@ -275,7 +276,7 @@
         <plugin>
           <groupId>org.codehaus.mojo</groupId>
           <artifactId>build-helper-maven-plugin</artifactId>
-          <version>3.1.0</version>
+          <version>3.2.0</version>
         </plugin>
 
         <plugin>
@@ -336,12 +337,12 @@
         <plugin>
           <groupId>org.jacoco</groupId>
           <artifactId>jacoco-maven-plugin</artifactId>
-          <version>0.8.5</version>
+          <version>0.8.6</version>
         </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-site-plugin</artifactId>
-          <version>3.9.0</version>
+          <version>3.9.1</version>
           <dependencies>
             <dependency><!-- add support for ssh/scp -->
               <groupId>org.apache.maven.wagon</groupId>
@@ -383,7 +384,7 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-resources-plugin</artifactId>
-          <version>3.1.0</version>
+          <version>3.2.0</version>
         </plugin>
         <plugin>
           <groupId>org.springframework.boot</groupId>
@@ -846,12 +847,12 @@
               <dependency>
                 <groupId>org.codehaus.plexus</groupId>
                 <artifactId>plexus-compiler-javac</artifactId>
-                <version>2.8.6</version>
+                <version>${plexus-compiler-version}</version>
               </dependency>
               <dependency>
                 <groupId>org.codehaus.plexus</groupId>
                 <artifactId>plexus-compiler-javac-errorprone</artifactId>
-                <version>2.8.6</version>
+                <version>${plexus-compiler-version}</version>
               </dependency>
               <!-- override plexus-compiler-javac-errorprone's dependency on
                   Error Prone with the latest version -->
@@ -890,12 +891,12 @@
               <dependency>
                 <groupId>org.codehaus.plexus</groupId>
                 <artifactId>plexus-compiler-eclipse</artifactId>
-                <version>2.8.6</version>
+                <version>${plexus-compiler-version}</version>
               </dependency>
               <dependency>
                 <groupId>org.eclipse.jdt</groupId>
                 <artifactId>ecj</artifactId>
-                <version>3.21.0</version>
+                <version>3.23.0</version>
               </dependency>
             </dependencies>
           </plugin>
@@ -924,12 +925,6 @@
           <plugin>
             <groupId>org.eclipse.tycho.extras</groupId>
             <artifactId>tycho-pack200a-plugin</artifactId>
-            <!-- TODO remove this configuration when https://git.eclipse.org/r/#/c/16027 is available -->
-            <configuration>
-              <supportedProjectTypes>
-                <supportedProjectType>jar</supportedProjectType>
-              </supportedProjectTypes>
-            </configuration>
             <executions>
               <execution>
                 <id>pack200-normalize</id>
@@ -956,12 +951,6 @@
           <plugin>
             <groupId>org.eclipse.tycho.extras</groupId>
             <artifactId>tycho-pack200b-plugin</artifactId>
-            <!-- TODO remove this configuration when https://git.eclipse.org/r/#/c/16027 is available -->
-            <configuration>
-              <supportedProjectTypes>
-                <supportedProjectType>jar</supportedProjectType>
-              </supportedProjectTypes>
-            </configuration>
             <executions>
               <execution>
                 <id>pack200-pack</id>
diff --git a/tools/maven-central/deploy.rb b/tools/maven-central/deploy.rb
index 7cab322..834fa94 100755
--- a/tools/maven-central/deploy.rb
+++ b/tools/maven-central/deploy.rb
@@ -51,6 +51,7 @@
 artifacts = [group,
              group + '.ant',
              group + '.archive',
+             group + '.gpg.bc',
              group + '.http.apache',
              group + '.http.server',
              group + '.junit',
@@ -60,6 +61,7 @@
              group + '.lfs.server',
              group + '.pgm',
              group + '.ssh.apache',
+             group + '.ssh.jsch',
              group + '.ui']
 
 prefix = ["mvn", "gpg:sign-and-deploy-file", "-Dgpg.passphrase=#{passphrase}",
diff --git a/tools/maven-central/download.rb b/tools/maven-central/download.rb
index 543ae87..bc48c82 100755
--- a/tools/maven-central/download.rb
+++ b/tools/maven-central/download.rb
@@ -11,6 +11,7 @@
 artifacts = [group,
              group + '.ant',
              group + '.archive',
+             group + '.gpg.bc',
              group + '.http.apache',
              group + '.http.server',
              group + '.junit',
@@ -20,6 +21,7 @@
              group + '.lfs.server',
              group + '.pgm',
              group + '.ssh.apache',
+             group + '.ssh.jsch',
              group + '.ui']
 
 puts 'Deleting current files'
diff --git a/tools/workspace_status.py b/tools/workspace_status.py
new file mode 100644
index 0000000..ca9e0a9
--- /dev/null
+++ b/tools/workspace_status.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+# Copyright (C) 2020, David Ostrovsky <david@ostrovsky.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
+# http://www.eclipse.org/org/documents/edl-v10.php.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+
+# This script will be run by bazel when the build process starts to
+# generate key-value information that represents the status of the
+# workspace. The output should be like
+#
+# KEY1 VALUE1
+# KEY2 VALUE2
+#
+# If the script exits with non-zero code, it's considered as a failure
+# and the output will be discarded.
+
+from __future__ import print_function
+import os
+import subprocess
+import sys
+
+ROOT = os.path.abspath(__file__)
+while not os.path.exists(os.path.join(ROOT, 'WORKSPACE')):
+    ROOT = os.path.dirname(ROOT)
+CMD = ['git', 'describe', '--always', '--match', 'v[0-9].*', '--dirty']
+
+
+def revision(directory, parent):
+    try:
+        os.chdir(directory)
+        return subprocess.check_output(CMD).strip().decode("utf-8")
+    except OSError as err:
+        print('could not invoke git: %s' % err, file=sys.stderr)
+        sys.exit(1)
+    except subprocess.CalledProcessError as err:
+        # ignore "not a git repository error" to report unknown version
+        return None
+    finally:
+        os.chdir(parent)
+
+
+print("STABLE_BUILD_JGIT_LABEL %s" % revision(ROOT, ROOT))