Merge branch 'stable-5.1' into stable-5.2

* stable-5.1:
  Prepare 5.1.9-SNAPSHOT builds
  JGit v5.1.8.201906050907-r
  Test detecting modified packfiles
  Enhance fsTick() to use filesystem timer resolution
  Add debug trace to measure time needed to open pack index
  Extend FileSnapshot for packfiles to also use checksum to detect changes
  Wait opening new packfile until it can't be racy anymore
  Avoid null PackConfig in GC
  Add FileSnapshot test testing recognition of file size changes
  Capture reason for result of FileSnapshot#isModified
  Skip FileSnapshotTest#testSimulatePackfileReplacement on Windows
  Tune max heap size for tests
  Fix FileSnapshotTest.testNewFileNoWait() to match its javadoc
  ObjectDirectory: fix closing of obsolete packs
  Include filekey file attribute when comparing FileSnapshots
  Measure file timestamp resolution used in FileSnapshot
  Fix FileSnapshot's consideration of file size
  Fix API problem filters

Change-Id: I3ac77bfa03f7436de12ab86e1bba29afee5ccd01
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
diff --git a/.mailmap b/.mailmap
index 7c9dc3d..f0bc990 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1,10 +1,18 @@
-Han-Wen Nienhuys <hanwen@google.com>         Han-Wen NIenhuys <hanwen@google.com>
-Mark Ingram <markdingram@gmail.com>          markdingram <markdingram@gmail.com>
-Roberto Tyley <roberto.tyley@guardian.co.uk> roberto <roberto.tyley@guardian.co.uk>
-Saša Živkov <sasa.zivkov@sap.com>            Sasa Zivkov <sasa.zivkov@sap.com>
-Saša Živkov <sasa.zivkov@sap.com>            Saša Živkov <zivkov@gmail.com>
-Saša Živkov <sasa.zivkov@sap.com>            Sasa Zivkov <zivkov@gmail.com>
-Shawn Pearce <spearce@spearce.org>           Shawn O. Pearce <sop@google.com>
-Shawn Pearce <spearce@spearce.org>           Shawn Pearce <sop@google.com>
-Shawn Pearce <spearce@spearce.org>           Shawn O. Pearce <spearce@spearce.org>
-Terry Parker <tparker@google.com>            tparker <tparker@google.com>
+Chris Aniszczyk <caniszczyk@gmail.com>                      Chris Aniszczyk <zx@eclipsesource.com>
+Christian Halstrick <christian.halstrick@sap.com>           Christian Halstrick <christian.halstrick@gmail.com>
+Dani Megert <Daniel_Megert@ch.ibm.com>                      Daniel Megert <daniel_megert@ch.ibm.com>
+David Pursehouse <david.pursehouse@gmail.com>               David Pursehouse <david.pursehouse@sonymobile.com>
+Han-Wen Nienhuys <hanwen@google.com>                        Han-Wen NIenhuys <hanwen@google.com>
+Hector Oswaldo Caballero <hector.caballero@ericsson.com>    Hector Caballero <hector.caballero@ericsson.com>
+Lars Vogel <Lars.Vogel@vogella.com>                         Lars Vogel <Lars.Vogel@gmail.com>
+Mark Ingram <markdingram@gmail.com>                         markdingram <markdingram@gmail.com>
+Markus Duft <markus.duft@ssi-schaefer.com>                  Markus Duft <markus.duft@salomon.at>
+Michael Keppler <michael.keppler@gmx.de>                    Michael Keppler <Michael.Keppler@gmx.de>
+Roberto Tyley <roberto.tyley@guardian.co.uk>                roberto <roberto.tyley@guardian.co.uk>
+Saša Živkov <sasa.zivkov@sap.com>                           Sasa Zivkov <sasa.zivkov@sap.com>
+Saša Živkov <sasa.zivkov@sap.com>                           Saša Živkov <zivkov@gmail.com>
+Saša Živkov <sasa.zivkov@sap.com>                           Sasa Zivkov <zivkov@gmail.com>
+Shawn Pearce <spearce@spearce.org>                          Shawn O. Pearce <sop@google.com>
+Shawn Pearce <spearce@spearce.org>                          Shawn Pearce <sop@google.com>
+Shawn Pearce <spearce@spearce.org>                          Shawn O. Pearce <spearce@spearce.org>
+Terry Parker <tparker@google.com>                           tparker <tparker@google.com>
diff --git a/WORKSPACE b/WORKSPACE
index 9d723d2..0eabecc 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -1,5 +1,18 @@
 workspace(name = "jgit")
 
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+
+http_archive(
+    name = "bazel_skylib",
+    sha256 = "bbccf674aa441c266df9894182d80de104cabd19be98be002f6d478aaa31574d",
+    strip_prefix = "bazel-skylib-2169ae1c374aab4a09aa90e65efe1a3aad4e279b",
+    urls = ["https://github.com/bazelbuild/bazel-skylib/archive/2169ae1c374aab4a09aa90e65efe1a3aad4e279b.tar.gz"],
+)
+
+load("@bazel_skylib//lib:versions.bzl", "versions")
+
+versions.check(minimum_bazel_version = "0.19.0")
+
 load("//tools:bazlets.bzl", "load_bazlets")
 
 load_bazlets(commit = "3afbeab55ece585dbfc7a980bf7214b24ddbbe86")
@@ -10,6 +23,12 @@
 )
 
 maven_jar(
+    name = "eddsa",
+    artifact = "net.i2p.crypto:eddsa:0.3.0",
+    sha1 = "1901c8d4d8bffb7d79027686cfb91e704217c3e1",
+)
+
+maven_jar(
     name = "jsch",
     artifact = "com.jcraft:jsch:0.1.54",
     sha1 = "da3584329a263616e277e15462b387addd1b208d",
@@ -40,6 +59,18 @@
 )
 
 maven_jar(
+    name = "sshd-core",
+    artifact = "org.apache.sshd:sshd-core:2.0.0",
+    sha1 = "f4275079a2463cfd2bf1548a80e1683288a8e86b",
+)
+
+maven_jar(
+    name = "sshd-sftp",
+    artifact = "org.apache.sshd:sshd-sftp:2.0.0",
+    sha1 = "a12d64dc2d5d23271a4dc58075e55f9c64a68494",
+)
+
+maven_jar(
     name = "commons-codec",
     artifact = "commons-codec:commons-codec:1.10",
     sha1 = "4b95f4897fa13f2cd904aee711aeafc0c5295cd8",
@@ -71,8 +102,8 @@
 
 maven_jar(
     name = "commons-compress",
-    artifact = "org.apache.commons:commons-compress:1.15",
-    sha1 = "b686cd04abaef1ea7bc5e143c080563668eec17e",
+    artifact = "org.apache.commons:commons-compress:1.18",
+    sha1 = "1191f9f2bc0c47a8cce69193feb1ff0a8bcb37d5",
 )
 
 maven_jar(
@@ -106,6 +137,32 @@
 )
 
 maven_jar(
+    name = "mockito",
+    artifact = "org.mockito:mockito-core:2.13.0",
+    sha1 = "8e372943974e4a121fb8617baced8ebfe46d54f0",
+)
+
+BYTE_BUDDY_VERSION = "1.7.9"
+
+maven_jar(
+    name = "byte_buddy",
+    artifact = "net.bytebuddy:byte-buddy:" + BYTE_BUDDY_VERSION,
+    sha1 = "51218a01a882c04d0aba8c028179cce488bbcb58",
+)
+
+maven_jar(
+    name = "byte_buddy_agent",
+    artifact = "net.bytebuddy:byte-buddy-agent:" + BYTE_BUDDY_VERSION,
+    sha1 = "a6c65f9da7f467ee1f02ff2841ffd3155aee2fc9",
+)
+
+maven_jar(
+    name = "objenesis",
+    artifact = "org.objenesis:objenesis:2.6",
+    sha1 = "639033469776fd37c08358c6b92a4761feb2af4b",
+)
+
+maven_jar(
     name = "gson",
     artifact = "com.google.code.gson:gson:2.8.2",
     sha1 = "3edcfe49d2c6053a70a2a47e4e1c2f94998a49cf",
diff --git a/lib/BUILD b/lib/BUILD
index ebba0da..0c5c224 100644
--- a/lib/BUILD
+++ b/lib/BUILD
@@ -23,12 +23,20 @@
 
 java_library(
     name = "commons-logging",
-    testonly = 1,
     visibility = ["//visibility:public"],
     exports = ["@commons-logging//jar"],
 )
 
 java_library(
+    name = "eddsa",
+    visibility = [
+        "//org.eclipse.jgit.ssh.apache:__pkg__",
+        "//org.eclipse.jgit.ssh.apache.test:__pkg__",
+    ],
+    exports = ["@eddsa//jar"],
+)
+
+java_library(
     name = "gson",
     visibility = [
         "//org.eclipse.jgit.lfs:__pkg__",
@@ -60,6 +68,28 @@
 )
 
 java_library(
+    name = "sshd-core",
+    visibility = [
+        "//org.eclipse.jgit.junit.ssh:__pkg__",
+        "//org.eclipse.jgit.ssh.apache:__pkg__",
+        "//org.eclipse.jgit.ssh.apache.test:__pkg__",
+        "//org.eclipse.jgit.test:__pkg__",
+    ],
+    exports = ["@sshd-core//jar"],
+)
+
+java_library(
+    name = "sshd-sftp",
+    visibility = [
+        "//org.eclipse.jgit.junit.ssh:__pkg__",
+        "//org.eclipse.jgit.ssh.apache:__pkg__",
+        "//org.eclipse.jgit.ssh.apache.test:__pkg__",
+        "//org.eclipse.jgit.test:__pkg__",
+    ],
+    exports = ["@sshd-sftp//jar"],
+)
+
+java_library(
     name = "javaewah",
     visibility = ["//visibility:public"],
     exports = ["@javaewah//jar"],
@@ -138,6 +168,18 @@
 )
 
 java_library(
+    name = "mockito",
+    testonly = 1,
+    visibility = ["//visibility:public"],
+    exports = [
+        "@byte_buddy//jar",
+        "@byte_buddy_agent//jar",
+        "@mockito//jar",
+        "@objenesis//jar",
+    ],
+)
+
+java_library(
     name = "servlet-api",
     visibility = [
         "//org.eclipse.jgit.http.apache:__pkg__",
diff --git a/org.eclipse.jgit.ant.test/.classpath b/org.eclipse.jgit.ant.test/.classpath
index eca7bdb..3e5654f 100644
--- a/org.eclipse.jgit.ant.test/.classpath
+++ b/org.eclipse.jgit.ant.test/.classpath
@@ -2,6 +2,10 @@
 <classpath>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
 	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
-	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="src" path="src">
+		<attributes>
+			<attribute name="test" value="true"/>
+		</attributes>
+	</classpathentry>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/org.eclipse.jgit.ant.test/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.ant.test/.settings/org.eclipse.jdt.core.prefs
index 794592d..2ca78ff 100644
--- a/org.eclipse.jgit.ant.test/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.ant.test/.settings/org.eclipse.jdt.core.prefs
@@ -91,7 +91,7 @@
 org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
 org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
 org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning
-org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error
 org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
diff --git a/org.eclipse.jgit.ant.test/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.ant.test/.settings/org.eclipse.mylyn.team.ui.prefs
index 0cba949..2fca432 100644
--- a/org.eclipse.jgit.ant.test/.settings/org.eclipse.mylyn.team.ui.prefs
+++ b/org.eclipse.jgit.ant.test/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -1,3 +1,3 @@
 #Tue Jul 19 20:11:28 CEST 2011
-commit.comment.template=${task.description} \n\nBug\: ${task.key}
+commit.comment.template=${task.description}\n\nBug\: ${task.key}
 eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF
index 76770f3..ad64078 100644
--- a/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF
@@ -4,13 +4,13 @@
 Bundle-Name: %plugin_name
 Automatic-Module-Name: org.eclipse.jgit.ant.test
 Bundle-SymbolicName: org.eclipse.jgit.ant.test
-Bundle-Version: 5.1.9.qualifier
+Bundle-Version: 5.2.3.qualifier
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Import-Package: org.apache.tools.ant,
- org.eclipse.jgit.ant.tasks;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.junit;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lib;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.util;version="[5.1.9,5.2.0)",
+ org.eclipse.jgit.ant.tasks;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.junit;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lib;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.util;version="[5.2.3,5.3.0)",
  org.hamcrest.core;version="[1.1.0,2.0.0)",
  org.junit;version="[4.12,5.0.0)"
diff --git a/org.eclipse.jgit.ant.test/pom.xml b/org.eclipse.jgit.ant.test/pom.xml
index 3c13383..d75acdb 100644
--- a/org.eclipse.jgit.ant.test/pom.xml
+++ b/org.eclipse.jgit.ant.test/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>5.1.9-SNAPSHOT</version>
+    <version>5.2.3-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.ant.test</artifactId>
@@ -105,7 +105,7 @@
       <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
         <configuration>
-          <argLine>@{argLine} -Xmx512m -Dfile.encoding=UTF-8 -Djava.io.tmpdir=${project.build.directory}</argLine>
+          <argLine>-Xmx256m -Dfile.encoding=UTF-8 -Djava.io.tmpdir=${project.build.directory}</argLine>
         </configuration>
       </plugin>
     </plugins>
diff --git a/org.eclipse.jgit.ant/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.ant/.settings/org.eclipse.jdt.core.prefs
index 565b75c..94a1c4f 100644
--- a/org.eclipse.jgit.ant/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.ant/.settings/org.eclipse.jdt.core.prefs
@@ -91,7 +91,7 @@
 org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
 org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
 org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning
-org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error
 org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
diff --git a/org.eclipse.jgit.ant/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.ant/.settings/org.eclipse.mylyn.team.ui.prefs
index 0cba949..2fca432 100644
--- a/org.eclipse.jgit.ant/.settings/org.eclipse.mylyn.team.ui.prefs
+++ b/org.eclipse.jgit.ant/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -1,3 +1,3 @@
 #Tue Jul 19 20:11:28 CEST 2011
-commit.comment.template=${task.description} \n\nBug\: ${task.key}
+commit.comment.template=${task.description}\n\nBug\: ${task.key}
 eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.ant/META-INF/MANIFEST.MF b/org.eclipse.jgit.ant/META-INF/MANIFEST.MF
index 99ca3b2..f0bbb7e 100644
--- a/org.eclipse.jgit.ant/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ant/META-INF/MANIFEST.MF
@@ -3,11 +3,11 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.ant
 Bundle-SymbolicName: org.eclipse.jgit.ant
-Bundle-Version: 5.1.9.qualifier
+Bundle-Version: 5.2.3.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Import-Package: org.apache.tools.ant,
-  org.eclipse.jgit.storage.file;version="[5.1.9,5.2.0)"
+  org.eclipse.jgit.storage.file;version="[5.2.3,5.3.0)"
 Bundle-Localization: plugin
 Bundle-Vendor: %Provider-Name
-Export-Package: org.eclipse.jgit.ant.tasks;version="5.1.9";
+Export-Package: org.eclipse.jgit.ant.tasks;version="5.2.3";
  uses:="org.apache.tools.ant.types,org.apache.tools.ant"
diff --git a/org.eclipse.jgit.ant/pom.xml b/org.eclipse.jgit.ant/pom.xml
index f12d417..f68a015 100644
--- a/org.eclipse.jgit.ant/pom.xml
+++ b/org.eclipse.jgit.ant/pom.xml
@@ -48,7 +48,7 @@
 	<parent>
 		<groupId>org.eclipse.jgit</groupId>
 		<artifactId>org.eclipse.jgit-parent</artifactId>
-		<version>5.1.9-SNAPSHOT</version>
+		<version>5.2.3-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>org.eclipse.jgit.ant</artifactId>
@@ -72,7 +72,7 @@
 		<dependency>
 			<groupId>org.apache.ant</groupId>
 			<artifactId>ant</artifactId>
-			<version>1.9.6</version>
+			<version>1.10.5</version>
 		</dependency>
 	</dependencies>
 
diff --git a/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.core.prefs
index 13c32a6..ef6f5e7 100644
--- a/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.core.prefs
@@ -91,7 +91,7 @@
 org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
 org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
 org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning
-org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error
 org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
diff --git a/org.eclipse.jgit.archive/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.archive/.settings/org.eclipse.mylyn.team.ui.prefs
index 0cba949..2fca432 100644
--- a/org.eclipse.jgit.archive/.settings/org.eclipse.mylyn.team.ui.prefs
+++ b/org.eclipse.jgit.archive/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -1,3 +1,3 @@
 #Tue Jul 19 20:11:28 CEST 2011
-commit.comment.template=${task.description} \n\nBug\: ${task.key}
+commit.comment.template=${task.description}\n\nBug\: ${task.key}
 eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.archive/META-INF/MANIFEST.MF b/org.eclipse.jgit.archive/META-INF/MANIFEST.MF
index d1ed4da..8c854b8 100644
--- a/org.eclipse.jgit.archive/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.archive/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
 Bundle-Name: %plugin_name
 Automatic-Module-Name: org.eclipse.jgit.archive
 Bundle-SymbolicName: org.eclipse.jgit.archive
-Bundle-Version: 5.1.9.qualifier
+Bundle-Version: 5.2.3.qualifier
 Bundle-Vendor: %provider_name
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
@@ -13,15 +13,15 @@
  org.apache.commons.compress.compressors.bzip2;version="[1.4,2.0)",
  org.apache.commons.compress.compressors.gzip;version="[1.4,2.0)",
  org.apache.commons.compress.compressors.xz;version="[1.4,2.0)",
- org.eclipse.jgit.api;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lib;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.nls;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.revwalk;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.util;version="[5.1.9,5.2.0)",
+ org.eclipse.jgit.api;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lib;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.nls;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.revwalk;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.util;version="[5.2.3,5.3.0)",
  org.osgi.framework;version="[1.3.0,2.0.0)"
 Bundle-ActivationPolicy: lazy
 Bundle-Activator: org.eclipse.jgit.archive.FormatActivator
-Export-Package: org.eclipse.jgit.archive;version="5.1.9";
+Export-Package: org.eclipse.jgit.archive;version="5.2.3";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.api,
    org.apache.commons.compress.archivers,
diff --git a/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF
index 5c19758..aab58a6 100644
--- a/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.archive - Sources
 Bundle-SymbolicName: org.eclipse.jgit.archive.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 5.1.9.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.archive;version="5.1.9.qualifier";roots="."
+Bundle-Version: 5.2.3.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.archive;version="5.2.3.qualifier";roots="."
diff --git a/org.eclipse.jgit.archive/pom.xml b/org.eclipse.jgit.archive/pom.xml
index cb1e66b..f36a3a6 100644
--- a/org.eclipse.jgit.archive/pom.xml
+++ b/org.eclipse.jgit.archive/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>5.1.9-SNAPSHOT</version>
+    <version>5.2.3-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.archive</artifactId>
diff --git a/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TarFormat.java b/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TarFormat.java
index 9ed60d9..aee80d8 100644
--- a/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TarFormat.java
+++ b/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TarFormat.java
@@ -42,7 +42,7 @@
  */
 package org.eclipse.jgit.archive;
 
-import static org.eclipse.jgit.lib.Constants.CHARACTER_ENCODING;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.io.IOException;
 import java.io.OutputStream;
@@ -85,7 +85,7 @@
 	public ArchiveOutputStream createArchiveOutputStream(OutputStream s,
 			Map<String, Object> o) throws IOException {
 		TarArchiveOutputStream out = new TarArchiveOutputStream(s,
-				CHARACTER_ENCODING);
+				UTF_8.name());
 		out.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
 		out.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_POSIX);
 		return applyFormatOptions(out, o);
@@ -99,8 +99,7 @@
 		if (mode == FileMode.SYMLINK) {
 			final TarArchiveEntry entry = new TarArchiveEntry(
 					path, TarConstants.LF_SYMLINK);
-			entry.setLinkName(new String(
-					loader.getCachedBytes(100), CHARACTER_ENCODING));
+			entry.setLinkName(new String(loader.getCachedBytes(100), UTF_8));
 			out.putArchiveEntry(entry);
 			out.closeArchiveEntry();
 			return;
diff --git a/org.eclipse.jgit.http.apache/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.http.apache/.settings/org.eclipse.jdt.core.prefs
index 565b75c..94a1c4f 100644
--- a/org.eclipse.jgit.http.apache/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.http.apache/.settings/org.eclipse.jdt.core.prefs
@@ -91,7 +91,7 @@
 org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
 org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
 org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning
-org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error
 org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
diff --git a/org.eclipse.jgit.http.apache/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.http.apache/.settings/org.eclipse.mylyn.team.ui.prefs
index 0cba949..2fca432 100644
--- a/org.eclipse.jgit.http.apache/.settings/org.eclipse.mylyn.team.ui.prefs
+++ b/org.eclipse.jgit.http.apache/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -1,3 +1,3 @@
 #Tue Jul 19 20:11:28 CEST 2011
-commit.comment.template=${task.description} \n\nBug\: ${task.key}
+commit.comment.template=${task.description}\n\nBug\: ${task.key}
 eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF
index 5177a60..fb3dec7 100644
--- a/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.http.apache
 Bundle-SymbolicName: org.eclipse.jgit.http.apache
-Bundle-Version: 5.1.9.qualifier
+Bundle-Version: 5.2.3.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Bundle-Localization: plugin
 Bundle-Vendor: %Provider-Name
@@ -23,10 +23,11 @@
  org.apache.http.impl.client;version="[4.3.0,5.0.0)",
  org.apache.http.impl.conn;version="[4.3.0,5.0.0)",
  org.apache.http.params;version="[4.3.0,5.0.0)",
- org.eclipse.jgit.nls;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.transport.http;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.util;version="[5.1.9,5.2.0)"
-Export-Package: org.eclipse.jgit.transport.http.apache;version="5.1.9";
+ org.eclipse.jgit.annotations;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.nls;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.transport.http;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.util;version="[5.2.3,5.3.0)"
+Export-Package: org.eclipse.jgit.transport.http.apache;version="5.2.3";
   uses:="org.apache.http.client,
    org.eclipse.jgit.transport.http,
    org.apache.http.entity,
diff --git a/org.eclipse.jgit.http.apache/pom.xml b/org.eclipse.jgit.http.apache/pom.xml
index b9e11f7..a1cb74c 100644
--- a/org.eclipse.jgit.http.apache/pom.xml
+++ b/org.eclipse.jgit.http.apache/pom.xml
@@ -48,7 +48,7 @@
 	<parent>
 		<groupId>org.eclipse.jgit</groupId>
 		<artifactId>org.eclipse.jgit-parent</artifactId>
-		<version>5.1.9-SNAPSHOT</version>
+		<version>5.2.3-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>org.eclipse.jgit.http.apache</artifactId>
diff --git a/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnection.java b/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnection.java
index 77c5dc0..4ac81a5 100644
--- a/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnection.java
+++ b/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnection.java
@@ -58,10 +58,13 @@
 import java.security.KeyManagementException;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.KeyManager;
@@ -90,6 +93,7 @@
 import org.apache.http.impl.client.HttpClientBuilder;
 import org.apache.http.impl.client.HttpClients;
 import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
+import org.eclipse.jgit.annotations.NonNull;
 import org.eclipse.jgit.transport.http.HttpConnection;
 import org.eclipse.jgit.transport.http.apache.internal.HttpApacheText;
 import org.eclipse.jgit.util.TemporaryBuffer;
@@ -347,11 +351,17 @@
 	// will return only the first field
 	/** {@inheritDoc} */
 	@Override
-	public String getHeaderField(String name) {
+	public String getHeaderField(@NonNull String name) {
 		Header header = resp.getFirstHeader(name);
 		return (header == null) ? null : header.getValue();
 	}
 
+	@Override
+	public List<String> getHeaderFields(@NonNull String name) {
+		return Collections.unmodifiableList(Arrays.asList(resp.getHeaders(name))
+				.stream().map(Header::getValue).collect(Collectors.toList()));
+	}
+
 	/** {@inheritDoc} */
 	@Override
 	public int getContentLength() {
diff --git a/org.eclipse.jgit.http.server/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.http.server/.settings/org.eclipse.jdt.core.prefs
index 565b75c..94a1c4f 100644
--- a/org.eclipse.jgit.http.server/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.http.server/.settings/org.eclipse.jdt.core.prefs
@@ -91,7 +91,7 @@
 org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
 org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
 org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning
-org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error
 org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
diff --git a/org.eclipse.jgit.http.server/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.http.server/.settings/org.eclipse.mylyn.team.ui.prefs
index 0cba949..2fca432 100644
--- a/org.eclipse.jgit.http.server/.settings/org.eclipse.mylyn.team.ui.prefs
+++ b/org.eclipse.jgit.http.server/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -1,3 +1,3 @@
 #Tue Jul 19 20:11:28 CEST 2011
-commit.comment.template=${task.description} \n\nBug\: ${task.key}
+commit.comment.template=${task.description}\n\nBug\: ${task.key}
 eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF
index dd46980..b65ee84 100644
--- a/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF
@@ -3,13 +3,13 @@
 Bundle-Name: %plugin_name
 Automatic-Module-Name: org.eclipse.jgit.http.server
 Bundle-SymbolicName: org.eclipse.jgit.http.server
-Bundle-Version: 5.1.9.qualifier
+Bundle-Version: 5.2.3.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
-Export-Package: org.eclipse.jgit.http.server;version="5.1.9",
- org.eclipse.jgit.http.server.glue;version="5.1.9";
+Export-Package: org.eclipse.jgit.http.server;version="5.2.3",
+ org.eclipse.jgit.http.server.glue;version="5.2.3";
   uses:="javax.servlet,javax.servlet.http",
- org.eclipse.jgit.http.server.resolver;version="5.1.9";
+ org.eclipse.jgit.http.server.resolver;version="5.2.3";
   uses:="org.eclipse.jgit.transport.resolver,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.transport,
@@ -18,12 +18,13 @@
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Import-Package: javax.servlet;version="[2.5.0,3.2.0)",
  javax.servlet.http;version="[2.5.0,3.2.0)",
- org.eclipse.jgit.errors;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lib;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.nls;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.revwalk;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.transport;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.transport.resolver;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.util;version="[5.1.9,5.2.0)"
+ org.eclipse.jgit.errors;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal.transport.parser;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lib;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.nls;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.revwalk;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.transport;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.transport.resolver;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.util;version="[5.2.3,5.3.0)"
diff --git a/org.eclipse.jgit.http.server/pom.xml b/org.eclipse.jgit.http.server/pom.xml
index 472c942..78191ef 100644
--- a/org.eclipse.jgit.http.server/pom.xml
+++ b/org.eclipse.jgit.http.server/pom.xml
@@ -52,7 +52,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>5.1.9-SNAPSHOT</version>
+    <version>5.2.3-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.http.server</artifactId>
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ClientVersionUtil.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ClientVersionUtil.java
index 38a9ea7..18b9b2a 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ClientVersionUtil.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ClientVersionUtil.java
@@ -43,18 +43,12 @@
 
 package org.eclipse.jgit.http.server;
 
-import static org.eclipse.jgit.http.server.ServletUtils.isChunked;
-
 import javax.servlet.http.HttpServletRequest;
 
 /**
  * Parses Git client User-Agent strings.
  */
 public class ClientVersionUtil {
-	private static final int[] v1_7_5 = { 1, 7, 5 };
-	private static final int[] v1_7_8_6 = { 1, 7, 8, 6 };
-	private static final int[] v1_7_9 = { 1, 7, 9 };
-
 	/**
 	 * An invalid version of Git
 	 *
@@ -174,17 +168,11 @@
 	 * @param version
 	 *            parsed version of the Git client software.
 	 * @return true if the bug is present.
+	 * @deprecated no widely used Git versions need this any more
 	 */
+	@Deprecated
 	public static boolean hasPushStatusBug(int[] version) {
-		int cmp = compare(version, v1_7_8_6);
-		if (cmp < 0)
-			return true; // Everything before 1.7.8.6 is known broken.
-		else if (cmp == 0)
-			return false; // 1.7.8.6 contained the bug fix.
-
-		if (compare(version, v1_7_9) <= 0)
-			return true; // 1.7.9 shipped before 1.7.8.6 and has the bug.
-		return false; // 1.7.9.1 and later are fixed.
+		return false;
 	}
 
 	/**
@@ -198,10 +186,12 @@
 	 * @param request
 	 *            incoming HTTP request.
 	 * @return true if the client has the chunked encoding bug.
+	 * @deprecated no widely used Git versions need this any more
 	 */
+	@Deprecated
 	public static boolean hasChunkedEncodingRequestBug(
 			int[] version, HttpServletRequest request) {
-		return compare(version, v1_7_5) == 0 && isChunked(request);
+		return false;
 	}
 
 	private ClientVersionUtil() {
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitSmartHttpTools.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitSmartHttpTools.java
index b9ca12b..ee4b32e 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitSmartHttpTools.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitSmartHttpTools.java
@@ -63,6 +63,7 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.eclipse.jgit.internal.transport.parser.FirstWant;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.transport.PacketLineIn;
 import org.eclipse.jgit.transport.PacketLineOut;
@@ -246,9 +247,9 @@
 			// not have an UploadPack, or it might not have read any of the request.
 			// So, cheat and read the first line.
 			String line = new PacketLineIn(req.getInputStream()).readString();
-			UploadPack.FirstLine parsed = new UploadPack.FirstLine(line);
-			return (parsed.getOptions().contains(OPTION_SIDE_BAND)
-					|| parsed.getOptions().contains(OPTION_SIDE_BAND_64K));
+			FirstWant parsed = FirstWant.fromLine(line);
+			return (parsed.getCapabilities().contains(OPTION_SIDE_BAND)
+					|| parsed.getCapabilities().contains(OPTION_SIDE_BAND_64K));
 		} catch (IOException e) {
 			// Probably the connection is closed and a subsequent write will fail, but
 			// try it just in case.
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java
index 1a9d192..b084b0d 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java
@@ -69,7 +69,7 @@
 		// Assume a dumb client and send back the dumb client
 		// version of the info/refs file.
 		rsp.setContentType(HttpSupport.TEXT_PLAIN);
-		rsp.setCharacterEncoding(Constants.CHARACTER_ENCODING);
+		rsp.setCharacterEncoding(UTF_8.name());
 
 		final Repository db = getRepository(req);
 		try (OutputStreamWriter out = new OutputStreamWriter(
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java
index a46652e..aed3656 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java
@@ -43,14 +43,10 @@
 
 package org.eclipse.jgit.http.server;
 
-import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
 import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
 import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
 import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
 import static javax.servlet.http.HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE;
-import static org.eclipse.jgit.http.server.ClientVersionUtil.hasChunkedEncodingRequestBug;
-import static org.eclipse.jgit.http.server.ClientVersionUtil.hasPushStatusBug;
-import static org.eclipse.jgit.http.server.ClientVersionUtil.parseVersion;
 import static org.eclipse.jgit.http.server.GitSmartHttpTools.RECEIVE_PACK;
 import static org.eclipse.jgit.http.server.GitSmartHttpTools.RECEIVE_PACK_REQUEST_TYPE;
 import static org.eclipse.jgit.http.server.GitSmartHttpTools.RECEIVE_PACK_RESULT_TYPE;
@@ -174,13 +170,6 @@
 			return;
 		}
 
-		int[] version = parseVersion(req.getHeader(HDR_USER_AGENT));
-		if (hasChunkedEncodingRequestBug(version, req)) {
-			GitSmartHttpTools.sendError(req, rsp, SC_BAD_REQUEST, "\n\n"
-					+ HttpServerText.get().clientHas175ChunkedEncodingBug);
-			return;
-		}
-
 		SmartOutputStream out = new SmartOutputStream(req, rsp, false) {
 			@Override
 			public void flush() throws IOException {
@@ -191,7 +180,6 @@
 		ReceivePack rp = (ReceivePack) req.getAttribute(ATTRIBUTE_HANDLER);
 		try {
 			rp.setBiDirectionalPipe(false);
-			rp.setEchoCommandFailures(hasPushStatusBug(version));
 			rsp.setContentType(RECEIVE_PACK_RESULT_TYPE);
 
 			rp.receive(getInputStream(req), out, null);
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ServletUtils.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ServletUtils.java
index 9601c8c..b6d73b5 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ServletUtils.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ServletUtils.java
@@ -43,6 +43,7 @@
 
 package org.eclipse.jgit.http.server;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.util.HttpSupport.ENCODING_GZIP;
 import static org.eclipse.jgit.util.HttpSupport.ENCODING_X_GZIP;
 import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT_ENCODING;
@@ -191,9 +192,9 @@
 	public static void sendPlainText(final String content,
 			final HttpServletRequest req, final HttpServletResponse rsp)
 			throws IOException {
-		final byte[] raw = content.getBytes(Constants.CHARACTER_ENCODING);
+		final byte[] raw = content.getBytes(UTF_8);
 		rsp.setContentType(TEXT_PLAIN);
-		rsp.setCharacterEncoding(Constants.CHARACTER_ENCODING);
+		rsp.setCharacterEncoding(UTF_8.name());
 		send(raw, req, rsp);
 	}
 
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartOutputStream.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartOutputStream.java
index ad5e8d4..06bdce6 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartOutputStream.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartOutputStream.java
@@ -105,6 +105,7 @@
 			// If output hasn't started yet, the entire thing fit into our
 			// buffer. Try to use a proper Content-Length header, and also
 			// deflate the response with gzip if it will be smaller.
+			@SuppressWarnings("resource")
 			TemporaryBuffer out = this;
 
 			if (256 < out.length() && acceptsGzipEncoding(req)) {
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java
index ca6b749..0f40371 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java
@@ -43,13 +43,10 @@
 
 package org.eclipse.jgit.http.server;
 
-import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
 import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
 import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
 import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
 import static javax.servlet.http.HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE;
-import static org.eclipse.jgit.http.server.ClientVersionUtil.hasChunkedEncodingRequestBug;
-import static org.eclipse.jgit.http.server.ClientVersionUtil.parseVersion;
 import static org.eclipse.jgit.http.server.GitSmartHttpTools.UPLOAD_PACK;
 import static org.eclipse.jgit.http.server.GitSmartHttpTools.UPLOAD_PACK_REQUEST_TYPE;
 import static org.eclipse.jgit.http.server.GitSmartHttpTools.UPLOAD_PACK_RESULT_TYPE;
@@ -193,13 +190,6 @@
 			return;
 		}
 
-		int[] version = parseVersion(req.getHeader(HDR_USER_AGENT));
-		if (hasChunkedEncodingRequestBug(version, req)) {
-			GitSmartHttpTools.sendError(req, rsp, SC_BAD_REQUEST, "\n\n"
-					+ HttpServerText.get().clientHas175ChunkedEncodingBug);
-			return;
-		}
-
 		SmartOutputStream out = new SmartOutputStream(req, rsp, false) {
 			@Override
 			public void flush() throws IOException {
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinder.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinder.java
index b2b4748..2a03633 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinder.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinder.java
@@ -57,7 +57,7 @@
 	 *            the filter to trigger while processing the path.
 	 * @return {@code this}.
 	 */
-	public ServletBinder through(Filter filter);
+	ServletBinder through(Filter filter);
 
 	/**
 	 * Set the servlet to execute on this path
@@ -65,5 +65,5 @@
 	 * @param servlet
 	 *            the servlet to execute on this path.
 	 */
-	public void with(HttpServlet servlet);
+	void with(HttpServlet servlet);
 }
diff --git a/org.eclipse.jgit.http.test/.classpath b/org.eclipse.jgit.http.test/.classpath
index e6014c7..b258380 100644
--- a/org.eclipse.jgit.http.test/.classpath
+++ b/org.eclipse.jgit.http.test/.classpath
@@ -2,7 +2,15 @@
 <classpath>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
 	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
-	<classpathentry kind="src" path="tst"/>
-	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="src" path="tst">
+		<attributes>
+			<attribute name="test" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="src" path="src">
+		<attributes>
+			<attribute name="test" value="true"/>
+		</attributes>
+	</classpathentry>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/org.eclipse.jgit.http.test/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.http.test/.settings/org.eclipse.jdt.core.prefs
index 794592d..2ca78ff 100644
--- a/org.eclipse.jgit.http.test/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.http.test/.settings/org.eclipse.jdt.core.prefs
@@ -91,7 +91,7 @@
 org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
 org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
 org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning
-org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error
 org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
diff --git a/org.eclipse.jgit.http.test/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.http.test/.settings/org.eclipse.mylyn.team.ui.prefs
index 0cba949..2fca432 100644
--- a/org.eclipse.jgit.http.test/.settings/org.eclipse.mylyn.team.ui.prefs
+++ b/org.eclipse.jgit.http.test/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -1,3 +1,3 @@
 #Tue Jul 19 20:11:28 CEST 2011
-commit.comment.template=${task.description} \n\nBug\: ${task.key}
+commit.comment.template=${task.description}\n\nBug\: ${task.key}
 eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF
index 4b8bff8..495d36d 100644
--- a/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
 Bundle-Name: %plugin_name
 Automatic-Module-Name: org.eclipse.jgit.http.test
 Bundle-SymbolicName: org.eclipse.jgit.http.test
-Bundle-Version: 5.1.9.qualifier
+Bundle-Version: 5.2.3.qualifier
 Bundle-Vendor: %provider_name
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
@@ -28,25 +28,25 @@
  org.eclipse.jetty.util.log;version="[9.4.5,10.0.0)",
  org.eclipse.jetty.util.security;version="[9.4.5,10.0.0)",
  org.eclipse.jetty.util.thread;version="[9.4.5,10.0.0)",
- org.eclipse.jgit.errors;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.http.server;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.http.server.glue;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.http.server.resolver;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.internal;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.internal.storage.reftable;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.junit;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.junit.http;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lib;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.nls;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.revwalk;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.storage.file;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.transport;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.transport.http;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.transport.http.apache;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.transport.resolver;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.util;version="[5.1.9,5.2.0)",
+ org.eclipse.jgit.errors;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.http.server;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.http.server.glue;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.http.server.resolver;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal.storage.reftable;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.junit;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.junit.http;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lib;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.nls;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.revwalk;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.storage.file;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.transport;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.transport.http;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.transport.http.apache;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.transport.resolver;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.util;version="[5.2.3,5.3.0)",
  org.hamcrest;version="[1.1.0,2.0.0)",
  org.hamcrest.core;version="[1.1.0,2.0.0)",
  org.junit;version="[4.12,5.0.0)",
diff --git a/org.eclipse.jgit.http.test/pom.xml b/org.eclipse.jgit.http.test/pom.xml
index fa0d8ea..e8e697e 100644
--- a/org.eclipse.jgit.http.test/pom.xml
+++ b/org.eclipse.jgit.http.test/pom.xml
@@ -51,7 +51,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>5.1.9-SNAPSHOT</version>
+    <version>5.2.3-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.http.test</artifactId>
@@ -139,7 +139,7 @@
       <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
         <configuration>
-          <argLine>@{argLine} -Djava.io.tmpdir=${project.build.directory}  -Xmx512m</argLine>
+          <argLine>-Djava.io.tmpdir=${project.build.directory}  -Xmx300m</argLine>
           <includes>
             <include>**/*Test.java</include>
             <include>**/*Tests.java</include>
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/server/ClientVersionUtilTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/server/ClientVersionUtilTest.java
index c7260e3..9dca279 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/server/ClientVersionUtilTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/server/ClientVersionUtilTest.java
@@ -43,11 +43,8 @@
 
 package org.eclipse.jgit.http.server;
 
-import static org.eclipse.jgit.http.server.ClientVersionUtil.hasPushStatusBug;
 import static org.eclipse.jgit.http.server.ClientVersionUtil.invalidVersion;
 import static org.eclipse.jgit.http.server.ClientVersionUtil.parseVersion;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
 
 import org.junit.Assert;
 import org.junit.Test;
@@ -67,18 +64,6 @@
 		assertEquals(ClientVersionUtil.toString(invalidVersion()), parseVersion("foo"));
 	}
 
-	@Test
-	public void testPushStatusBug() {
-		assertTrue(hasPushStatusBug(parseVersion("git/1.6.6")));
-		assertTrue(hasPushStatusBug(parseVersion("git/1.6.6.1")));
-		assertTrue(hasPushStatusBug(parseVersion("git/1.7.9")));
-
-		assertFalse(hasPushStatusBug(parseVersion("git/1.7.8.6")));
-		assertFalse(hasPushStatusBug(parseVersion("git/1.7.9.1")));
-		assertFalse(hasPushStatusBug(parseVersion("git/1.7.9.2")));
-		assertFalse(hasPushStatusBug(parseVersion("git/1.7.10")));
-	}
-
 	private static void assertEquals(String exp, int[] act) {
 		Assert.assertEquals(exp, ClientVersionUtil.toString(act));
 	}
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/FileResolverTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/FileResolverTest.java
index 82e79b8..553c340 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/FileResolverTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/FileResolverTest.java
@@ -147,6 +147,7 @@
 
 		try {
 			resolver.open(null, name);
+			fail("opened non-git repository");
 		} catch (RepositoryNotFoundException e) {
 			assertEquals("repository not found: " + name, e.getMessage());
 
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/RegexPipelineTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/RegexPipelineTest.java
index 725a590..59c2b4e 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/RegexPipelineTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/RegexPipelineTest.java
@@ -43,11 +43,14 @@
 
 package org.eclipse.jgit.http.test;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 
 import java.io.BufferedReader;
+import java.io.BufferedWriter;
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.net.HttpURLConnection;
 import java.net.URI;
@@ -82,7 +85,8 @@
 		protected void doGet(HttpServletRequest req, HttpServletResponse res)
 				throws IOException {
 			res.setStatus(200);
-			PrintWriter out = new PrintWriter(res.getOutputStream());
+			PrintWriter out = new PrintWriter(new BufferedWriter(
+					new OutputStreamWriter(res.getOutputStream(), UTF_8)));
 			out.write(name);
 			out.write("\n");
 			out.write(String.valueOf(req.getServletPath()));
@@ -120,7 +124,8 @@
 		c = ((HttpURLConnection) uri.resolve("/a").toURL()
 				.openConnection());
 		assertEquals(200, c.getResponseCode());
-		r = new BufferedReader(new InputStreamReader(c.getInputStream()));
+		r = new BufferedReader(
+				new InputStreamReader(c.getInputStream(), UTF_8));
 		assertEquals("test", r.readLine());
 		assertEquals("", r.readLine());
 		assertEquals("/a", r.readLine());
@@ -129,7 +134,8 @@
 		c = ((HttpURLConnection) uri.resolve("/b").toURL()
 				.openConnection());
 		assertEquals(200, c.getResponseCode());
-		r = new BufferedReader(new InputStreamReader(c.getInputStream()));
+		r = new BufferedReader(
+				new InputStreamReader(c.getInputStream(), UTF_8));
 		assertEquals("test", r.readLine());
 		assertEquals("", r.readLine());
 		assertEquals("/b", r.readLine());
@@ -155,7 +161,8 @@
 		c = ((HttpURLConnection) uri.resolve("/a").toURL()
 				.openConnection());
 		assertEquals(200, c.getResponseCode());
-		r = new BufferedReader(new InputStreamReader(c.getInputStream()));
+		r = new BufferedReader(
+				new InputStreamReader(c.getInputStream(), UTF_8));
 		assertEquals("test1", r.readLine());
 		assertEquals("", r.readLine());
 		assertEquals("/a", r.readLine());
@@ -183,7 +190,8 @@
 		c = ((HttpURLConnection) uri.resolve("/a/b").toURL()
 				.openConnection());
 		assertEquals(200, c.getResponseCode());
-		r = new BufferedReader(new InputStreamReader(c.getInputStream()));
+		r = new BufferedReader(
+				new InputStreamReader(c.getInputStream(), UTF_8));
 		assertEquals("test1", r.readLine());
 		assertEquals("", r.readLine());
 		// No RegexGroupFilter defaults to first group.
@@ -193,7 +201,8 @@
 		c = ((HttpURLConnection) uri.resolve("/c/d").toURL()
 				.openConnection());
 		assertEquals(200, c.getResponseCode());
-		r = new BufferedReader(new InputStreamReader(c.getInputStream()));
+		r = new BufferedReader(
+				new InputStreamReader(c.getInputStream(), UTF_8));
 		assertEquals("test2", r.readLine());
 		assertEquals("", r.readLine());
 		assertEquals("/c", r.readLine());
@@ -202,7 +211,8 @@
 		c = ((HttpURLConnection) uri.resolve("/e/f/g").toURL()
 				.openConnection());
 		assertEquals(200, c.getResponseCode());
-		r = new BufferedReader(new InputStreamReader(c.getInputStream()));
+		r = new BufferedReader(
+				new InputStreamReader(c.getInputStream(), UTF_8));
 		assertEquals("test3", r.readLine());
 		assertEquals("/e/f", r.readLine());
 		assertEquals("/g", r.readLine());
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
index 2b4a251..b26324d 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
@@ -298,7 +298,7 @@
 					throws IOException, ServletException {
 				final HttpServletResponse r = (HttpServletResponse) response;
 				r.setContentType("text/plain");
-				r.setCharacterEncoding(Constants.CHARACTER_ENCODING);
+				r.setCharacterEncoding(UTF_8.name());
 				try (PrintWriter w = r.getWriter()) {
 					w.print("OK");
 				}
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/transport/http/apache/HttpClientConnectionTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/transport/http/apache/HttpClientConnectionTest.java
index 72c4921..8a0d59c 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/transport/http/apache/HttpClientConnectionTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/transport/http/apache/HttpClientConnectionTest.java
@@ -76,7 +76,7 @@
 		assertTrue(headerValues.contains("NTLM"));
 	}
 
-	private class HttpResponseMock extends AbstractHttpMessage
+	private static class HttpResponseMock extends AbstractHttpMessage
 			implements HttpResponse {
 		@Override
 		public StatusLine getStatusLine() {
diff --git a/org.eclipse.jgit.junit.http/.classpath b/org.eclipse.jgit.junit.http/.classpath
index b862a29..3e5654f 100644
--- a/org.eclipse.jgit.junit.http/.classpath
+++ b/org.eclipse.jgit.junit.http/.classpath
@@ -1,7 +1,11 @@
-<?xml version="1.0" encoding="UTF-8"?>

-<classpath>

-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>

-	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>

-	<classpathentry kind="src" path="src"/>

-	<classpathentry kind="output" path="bin"/>

-</classpath>

+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+	<classpathentry kind="src" path="src">
+		<attributes>
+			<attribute name="test" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/org.eclipse.jgit.junit.http/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.junit.http/.settings/org.eclipse.jdt.core.prefs
index 794592d..2ca78ff 100644
--- a/org.eclipse.jgit.junit.http/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.junit.http/.settings/org.eclipse.jdt.core.prefs
@@ -91,7 +91,7 @@
 org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
 org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
 org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning
-org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error
 org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
diff --git a/org.eclipse.jgit.junit.http/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.junit.http/.settings/org.eclipse.mylyn.team.ui.prefs
index 0cba949..2fca432 100644
--- a/org.eclipse.jgit.junit.http/.settings/org.eclipse.mylyn.team.ui.prefs
+++ b/org.eclipse.jgit.junit.http/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -1,3 +1,3 @@
 #Tue Jul 19 20:11:28 CEST 2011
-commit.comment.template=${task.description} \n\nBug\: ${task.key}
+commit.comment.template=${task.description}\n\nBug\: ${task.key}
 eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF
index 8d6357e..8432c45 100644
--- a/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
 Bundle-Name: %plugin_name
 Automatic-Module-Name: org.eclipse.jgit.junit.http
 Bundle-SymbolicName: org.eclipse.jgit.junit.http
-Bundle-Version: 5.1.9.qualifier
+Bundle-Version: 5.2.3.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
@@ -22,16 +22,16 @@
  org.eclipse.jetty.util.log;version="[9.4.5,10.0.0)",
  org.eclipse.jetty.util.security;version="[9.4.5,10.0.0)",
  org.eclipse.jetty.util.ssl;version="[9.4.5,10.0.0)",
- org.eclipse.jgit.errors;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.http.server;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.junit;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lib;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.revwalk;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.transport;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.transport.resolver;version="[5.1.9,5.2.0)",
+ org.eclipse.jgit.errors;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.http.server;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.junit;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lib;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.revwalk;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.transport;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.transport.resolver;version="[5.2.3,5.3.0)",
  org.junit;version="[4.12,5.0.0)"
-Export-Package: org.eclipse.jgit.junit.http;version="5.1.9";
+Export-Package: org.eclipse.jgit.junit.http;version="5.2.3";
   uses:="org.eclipse.jgit.transport,
    org.eclipse.jgit.junit,
    javax.servlet.http,
diff --git a/org.eclipse.jgit.junit.http/pom.xml b/org.eclipse.jgit.junit.http/pom.xml
index cda2f66..b766974 100644
--- a/org.eclipse.jgit.junit.http/pom.xml
+++ b/org.eclipse.jgit.junit.http/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>5.1.9-SNAPSHOT</version>
+    <version>5.2.3-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.junit.http</artifactId>
diff --git a/org.eclipse.jgit.junit.ssh/.classpath b/org.eclipse.jgit.junit.ssh/.classpath
new file mode 100644
index 0000000..eca7bdb
--- /dev/null
+++ b/org.eclipse.jgit.junit.ssh/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/org.eclipse.jgit.junit.ssh/.gitignore b/org.eclipse.jgit.junit.ssh/.gitignore
new file mode 100644
index 0000000..934e0e0
--- /dev/null
+++ b/org.eclipse.jgit.junit.ssh/.gitignore
@@ -0,0 +1,2 @@
+/bin
+/target
diff --git a/org.eclipse.jgit.junit.ssh/.project b/org.eclipse.jgit.junit.ssh/.project
new file mode 100644
index 0000000..3a0c494
--- /dev/null
+++ b/org.eclipse.jgit.junit.ssh/.project
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.eclipse.jgit.junit.ssh</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.ManifestBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.SchemaBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.api.tools.apiAnalysisBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.eclipse.pde.api.tools.apiAnalysisNature</nature>
+	</natures>
+</projectDescription>
diff --git a/org.eclipse.jgit.junit.ssh/.settings/org.eclipse.core.resources.prefs b/org.eclipse.jgit.junit.ssh/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..f77db3b
--- /dev/null
+++ b/org.eclipse.jgit.junit.ssh/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,3 @@
+#Sat Dec 20 21:21:24 CET 2008
+eclipse.preferences.version=1
+encoding/<project>=UTF-8
diff --git a/org.eclipse.jgit.junit.ssh/.settings/org.eclipse.core.runtime.prefs b/org.eclipse.jgit.junit.ssh/.settings/org.eclipse.core.runtime.prefs
new file mode 100644
index 0000000..9f733ee
--- /dev/null
+++ b/org.eclipse.jgit.junit.ssh/.settings/org.eclipse.core.runtime.prefs
@@ -0,0 +1,3 @@
+#Mon Mar 24 18:55:56 EDT 2008
+eclipse.preferences.version=1
+line.separator=\n
diff --git a/org.eclipse.jgit.junit.ssh/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.junit.ssh/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..2ca78ff
--- /dev/null
+++ b/org.eclipse.jgit.junit.ssh/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,399 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
+org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
+org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
+org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
+org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.doc.comment.support=enabled
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.autoboxing=warning
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=error
+org.eclipse.jdt.core.compiler.problem.deadCode=error
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=warning
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=warning
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
+org.eclipse.jdt.core.compiler.problem.fieldHiding=warning
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=error
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=error
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=enabled
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=error
+org.eclipse.jdt.core.compiler.problem.invalidJavadoc=error
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=private
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=error
+org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=error
+org.eclipse.jdt.core.compiler.problem.missingJavadocComments=ignore
+org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag
+org.eclipse.jdt.core.compiler.problem.missingJavadocTags=error
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=error
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=error
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
+org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
+org.eclipse.jdt.core.compiler.problem.nullReference=error
+org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=error
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning
+org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore
+org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning
+org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning
+org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=error
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=error
+org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=error
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedImport=error
+org.eclipse.jdt.core.compiler.problem.unusedLabel=error
+org.eclipse.jdt.core.compiler.problem.unusedLocal=error
+org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning
+org.eclipse.jdt.core.compiler.problem.unusedParameter=warning
+org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=error
+org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=error
+org.eclipse.jdt.core.compiler.source=1.8
+org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_assignment=0
+org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
+org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
+org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
+org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
+org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
+org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_after_package=1
+org.eclipse.jdt.core.formatter.blank_lines_before_field=1
+org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
+org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
+org.eclipse.jdt.core.formatter.blank_lines_before_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
+org.eclipse.jdt.core.formatter.blank_lines_before_package=0
+org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
+org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
+org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
+org.eclipse.jdt.core.formatter.comment.format_block_comments=true
+org.eclipse.jdt.core.formatter.comment.format_comments=true
+org.eclipse.jdt.core.formatter.comment.format_header=false
+org.eclipse.jdt.core.formatter.comment.format_html=true
+org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
+org.eclipse.jdt.core.formatter.comment.format_line_comments=true
+org.eclipse.jdt.core.formatter.comment.format_source_code=true
+org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
+org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
+org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
+org.eclipse.jdt.core.formatter.comment.line_length=80
+org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
+org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
+org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
+org.eclipse.jdt.core.formatter.compact_else_if=true
+org.eclipse.jdt.core.formatter.continuation_indentation=2
+org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
+org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
+org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
+org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
+org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
+org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_empty_lines=false
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
+org.eclipse.jdt.core.formatter.indentation.size=4
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
+org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
+org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.join_lines_in_comments=true
+org.eclipse.jdt.core.formatter.join_wrapped_lines=true
+org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.lineSplit=80
+org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
+org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
+org.eclipse.jdt.core.formatter.tabulation.char=tab
+org.eclipse.jdt.core.formatter.tabulation.size=4
+org.eclipse.jdt.core.formatter.use_on_off_tags=true
+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
+org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
+org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
diff --git a/org.eclipse.jgit.junit.ssh/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.jgit.junit.ssh/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 0000000..fef3713
--- /dev/null
+++ b/org.eclipse.jgit.junit.ssh/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,66 @@
+eclipse.preferences.version=1
+editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+formatter_profile=_JGit Format
+formatter_settings_version=12
+org.eclipse.jdt.ui.ignorelowercasenames=true
+org.eclipse.jdt.ui.importorder=java;javax;org;com;
+org.eclipse.jdt.ui.ondemandthreshold=99
+org.eclipse.jdt.ui.staticondemandthreshold=99
+org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8"?><templates/>
+sp_cleanup.add_default_serial_version_id=true
+sp_cleanup.add_generated_serial_version_id=false
+sp_cleanup.add_missing_annotations=true
+sp_cleanup.add_missing_deprecated_annotations=true
+sp_cleanup.add_missing_methods=false
+sp_cleanup.add_missing_nls_tags=false
+sp_cleanup.add_missing_override_annotations=true
+sp_cleanup.add_missing_override_annotations_interface_methods=true
+sp_cleanup.add_serial_version_id=false
+sp_cleanup.always_use_blocks=true
+sp_cleanup.always_use_parentheses_in_expressions=false
+sp_cleanup.always_use_this_for_non_static_field_access=false
+sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_functional_interfaces=false
+sp_cleanup.convert_to_enhanced_for_loop=false
+sp_cleanup.correct_indentation=false
+sp_cleanup.format_source_code=true
+sp_cleanup.format_source_code_changes_only=true
+sp_cleanup.insert_inferred_type_arguments=false
+sp_cleanup.make_local_variable_final=false
+sp_cleanup.make_parameters_final=false
+sp_cleanup.make_private_fields_final=true
+sp_cleanup.make_type_abstract_if_missing_method=false
+sp_cleanup.make_variable_declarations_final=false
+sp_cleanup.never_use_blocks=false
+sp_cleanup.never_use_parentheses_in_expressions=true
+sp_cleanup.on_save_use_additional_actions=true
+sp_cleanup.organize_imports=false
+sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_type_arguments=true
+sp_cleanup.remove_trailing_whitespaces=true
+sp_cleanup.remove_trailing_whitespaces_all=true
+sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=true
+sp_cleanup.remove_unused_imports=false
+sp_cleanup.remove_unused_local_variables=false
+sp_cleanup.remove_unused_private_fields=true
+sp_cleanup.remove_unused_private_members=false
+sp_cleanup.remove_unused_private_methods=true
+sp_cleanup.remove_unused_private_types=true
+sp_cleanup.sort_members=false
+sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
+sp_cleanup.use_blocks=false
+sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=false
+sp_cleanup.use_parentheses_in_expressions=false
+sp_cleanup.use_this_for_non_static_field_access=false
+sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+sp_cleanup.use_this_for_non_static_method_access=false
+sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/org.eclipse.jgit.junit.ssh/.settings/org.eclipse.mylyn.tasks.ui.prefs b/org.eclipse.jgit.junit.ssh/.settings/org.eclipse.mylyn.tasks.ui.prefs
new file mode 100644
index 0000000..823c0f5
--- /dev/null
+++ b/org.eclipse.jgit.junit.ssh/.settings/org.eclipse.mylyn.tasks.ui.prefs
@@ -0,0 +1,4 @@
+#Tue Jul 19 20:11:28 CEST 2011
+eclipse.preferences.version=1
+project.repository.kind=bugzilla
+project.repository.url=https\://bugs.eclipse.org/bugs
diff --git a/org.eclipse.jgit.junit.ssh/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.junit.ssh/.settings/org.eclipse.mylyn.team.ui.prefs
new file mode 100644
index 0000000..2fca432
--- /dev/null
+++ b/org.eclipse.jgit.junit.ssh/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -0,0 +1,3 @@
+#Tue Jul 19 20:11:28 CEST 2011
+commit.comment.template=${task.description}\n\nBug\: ${task.key}
+eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.junit.ssh/.settings/org.eclipse.pde.api.tools.prefs b/org.eclipse.jgit.junit.ssh/.settings/org.eclipse.pde.api.tools.prefs
new file mode 100644
index 0000000..c0030de
--- /dev/null
+++ b/org.eclipse.jgit.junit.ssh/.settings/org.eclipse.pde.api.tools.prefs
@@ -0,0 +1,104 @@
+ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error
+ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error
+ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
+ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error
+ANNOTATION_ELEMENT_TYPE_REMOVED_METHOD=Error
+ANNOTATION_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
+API_COMPONENT_ELEMENT_TYPE_REMOVED_API_TYPE=Error
+API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error
+API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error
+API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error
+API_USE_SCAN_FIELD_SEVERITY=Error
+API_USE_SCAN_METHOD_SEVERITY=Error
+API_USE_SCAN_TYPE_SEVERITY=Error
+CLASS_ELEMENT_TYPE_ADDED_FIELD=Error
+CLASS_ELEMENT_TYPE_ADDED_METHOD=Error
+CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
+CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
+CLASS_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error
+CLASS_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error
+CLASS_ELEMENT_TYPE_CHANGED_NON_ABSTRACT_TO_ABSTRACT=Error
+CLASS_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error
+CLASS_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
+CLASS_ELEMENT_TYPE_REMOVED_CONSTRUCTOR=Error
+CLASS_ELEMENT_TYPE_REMOVED_FIELD=Error
+CLASS_ELEMENT_TYPE_REMOVED_METHOD=Error
+CLASS_ELEMENT_TYPE_REMOVED_SUPERCLASS=Error
+CLASS_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
+CLASS_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+CONSTRUCTOR_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
+CONSTRUCTOR_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error
+CONSTRUCTOR_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error
+CONSTRUCTOR_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+ENUM_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error
+ENUM_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
+ENUM_ELEMENT_TYPE_REMOVED_ENUM_CONSTANT=Error
+ENUM_ELEMENT_TYPE_REMOVED_FIELD=Error
+ENUM_ELEMENT_TYPE_REMOVED_METHOD=Error
+ENUM_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
+FIELD_ELEMENT_TYPE_ADDED_VALUE=Error
+FIELD_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error
+FIELD_ELEMENT_TYPE_CHANGED_FINAL_TO_NON_FINAL_STATIC_CONSTANT=Error
+FIELD_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error
+FIELD_ELEMENT_TYPE_CHANGED_NON_STATIC_TO_STATIC=Error
+FIELD_ELEMENT_TYPE_CHANGED_STATIC_TO_NON_STATIC=Error
+FIELD_ELEMENT_TYPE_CHANGED_TYPE=Error
+FIELD_ELEMENT_TYPE_CHANGED_VALUE=Error
+FIELD_ELEMENT_TYPE_REMOVED_TYPE_ARGUMENT=Error
+FIELD_ELEMENT_TYPE_REMOVED_VALUE=Error
+ILLEGAL_EXTEND=Warning
+ILLEGAL_IMPLEMENT=Warning
+ILLEGAL_INSTANTIATE=Warning
+ILLEGAL_OVERRIDE=Warning
+ILLEGAL_REFERENCE=Warning
+INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error
+INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error
+INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error
+INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
+INTERFACE_ELEMENT_TYPE_ADDED_SUPER_INTERFACE_WITH_METHODS=Error
+INTERFACE_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
+INTERFACE_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error
+INTERFACE_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
+INTERFACE_ELEMENT_TYPE_REMOVED_FIELD=Error
+INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error
+INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
+INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+INVALID_ANNOTATION=Ignore
+INVALID_JAVADOC_TAG=Ignore
+INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error
+LEAK_EXTEND=Warning
+LEAK_FIELD_DECL=Warning
+LEAK_IMPLEMENT=Warning
+LEAK_METHOD_PARAM=Warning
+LEAK_METHOD_RETURN_TYPE=Warning
+METHOD_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
+METHOD_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
+METHOD_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error
+METHOD_ELEMENT_TYPE_CHANGED_NON_ABSTRACT_TO_ABSTRACT=Error
+METHOD_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error
+METHOD_ELEMENT_TYPE_CHANGED_NON_STATIC_TO_STATIC=Error
+METHOD_ELEMENT_TYPE_CHANGED_STATIC_TO_NON_STATIC=Error
+METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error
+METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error
+METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+MISSING_EE_DESCRIPTIONS=Warning
+TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error
+TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error
+TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error
+TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_INTERFACE_BOUND=Error
+TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_CLASS_BOUND=Error
+TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error
+UNUSED_PROBLEM_FILTERS=Warning
+automatically_removed_unused_problem_filters=false
+changed_execution_env=Error
+eclipse.preferences.version=1
+incompatible_api_component_version=Error
+incompatible_api_component_version_include_major_without_breaking_change=Disabled
+incompatible_api_component_version_include_minor_without_api_change=Disabled
+incompatible_api_component_version_report_major_without_breaking_change=Warning
+incompatible_api_component_version_report_minor_without_api_change=Ignore
+invalid_since_tag_version=Error
+malformed_since_tag=Error
+missing_since_tag=Error
+report_api_breakage_when_major_version_incremented=Disabled
+report_resolution_errors_api_component=Warning
diff --git a/org.eclipse.jgit.junit.ssh/.settings/org.eclipse.pde.core.prefs b/org.eclipse.jgit.junit.ssh/.settings/org.eclipse.pde.core.prefs
new file mode 100644
index 0000000..82793f2
--- /dev/null
+++ b/org.eclipse.jgit.junit.ssh/.settings/org.eclipse.pde.core.prefs
@@ -0,0 +1,3 @@
+#Thu Jan 14 14:34:32 CST 2010
+eclipse.preferences.version=1
+resolve.requirebundle=false
diff --git a/org.eclipse.jgit.junit.ssh/BUILD b/org.eclipse.jgit.junit.ssh/BUILD
new file mode 100644
index 0000000..e9a04c7
--- /dev/null
+++ b/org.eclipse.jgit.junit.ssh/BUILD
@@ -0,0 +1,15 @@
+package(default_visibility = ["//visibility:public"])
+
+java_library(
+    name = "junit-ssh",
+    testonly = 1,
+    srcs = glob(["src/**/*.java"]),
+    resource_strip_prefix = "org.eclipse.jgit.junit.ssh/resources",
+    resources = glob(["resources/**"]),
+    deps = [
+        "//lib:sshd-core",
+        "//lib:sshd-sftp",
+        # We want these deps to be provided_deps
+        "//org.eclipse.jgit:jgit",
+    ],
+)
diff --git a/org.eclipse.jgit.junit.ssh/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit.ssh/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..1467499
--- /dev/null
+++ b/org.eclipse.jgit.junit.ssh/META-INF/MANIFEST.MF
@@ -0,0 +1,36 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %plugin_name
+Automatic-Module-Name: org.eclipse.jgit.junit.ssh
+Bundle-SymbolicName: org.eclipse.jgit.junit.ssh
+Bundle-Version: 5.2.3.qualifier
+Bundle-Localization: plugin
+Bundle-Vendor: %provider_name
+Bundle-ActivationPolicy: lazy
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Import-Package: org.apache.sshd.common;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.config.keys;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.file.virtualfs;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.helpers;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.io;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.kex;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.keyprovider;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.session;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.util.buffer;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.util.logging;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.util.security;version="[2.0.0,2.1.0)",
+ org.apache.sshd.server;version="[2.0.0,2.1.0)",
+ org.apache.sshd.server.auth;version="[2.0.0,2.1.0)",
+ org.apache.sshd.server.auth.gss;version="[2.0.0,2.1.0)",
+ org.apache.sshd.server.auth.keyboard;version="[2.0.0,2.1.0)",
+ org.apache.sshd.server.auth.password;version="[2.0.0,2.1.0)",
+ org.apache.sshd.server.command;version="[2.0.0,2.1.0)",
+ org.apache.sshd.server.session;version="[2.0.0,2.1.0)",
+ org.apache.sshd.server.shell;version="[2.0.0,2.1.0)",
+ org.apache.sshd.server.subsystem;version="[2.0.0,2.1.0)",
+ org.apache.sshd.server.subsystem.sftp;version="[2.0.0,2.1.0)",
+ org.eclipse.jgit.annotations;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lib;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.transport;version="[5.2.3,5.3.0)",
+ org.slf4j;version="[1.7.0,2.0.0)"
+Export-Package: org.eclipse.jgit.junit.ssh;version="5.2.3"
diff --git a/org.eclipse.jgit.junit.ssh/build.properties b/org.eclipse.jgit.junit.ssh/build.properties
new file mode 100644
index 0000000..aa1a008
--- /dev/null
+++ b/org.eclipse.jgit.junit.ssh/build.properties
@@ -0,0 +1,5 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               plugin.properties
diff --git a/org.eclipse.jgit.junit.ssh/plugin.properties b/org.eclipse.jgit.junit.ssh/plugin.properties
new file mode 100644
index 0000000..5689b94
--- /dev/null
+++ b/org.eclipse.jgit.junit.ssh/plugin.properties
@@ -0,0 +1,2 @@
+plugin_name=JGit JUnit Ssh Utility Classes
+provider_name=Eclipse JGit
diff --git a/org.eclipse.jgit.junit.ssh/pom.xml b/org.eclipse.jgit.junit.ssh/pom.xml
new file mode 100644
index 0000000..4cd68eb
--- /dev/null
+++ b/org.eclipse.jgit.junit.ssh/pom.xml
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Copyright (C) 2010, Jens Baumgart <jens.baumgart@sap.com>
+   and other copyright owners as documented in the project's IP log.
+
+   This program and the accompanying materials are made available
+   under the terms of the Eclipse Distribution License v1.0 which
+   accompanies this distribution, is reproduced below, and is
+   available at http://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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.eclipse.jgit</groupId>
+    <artifactId>org.eclipse.jgit-parent</artifactId>
+    <version>5.2.3-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>org.eclipse.jgit.junit.ssh</artifactId>
+  <name>JGit - JUnit Ssh Utility Classes</name>
+
+  <description>
+    Utility classes to support Ssh based JUnit testing of JGit applications.
+  </description>
+
+  <properties>
+    <translate-qualifier/>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.sshd</groupId>
+      <artifactId>sshd-core</artifactId>
+      <version>${apache-sshd-version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.sshd</groupId>
+      <artifactId>sshd-sftp</artifactId>
+      <version>${apache-sshd-version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <sourceDirectory>src/</sourceDirectory>
+
+    <resources>
+      <resource>
+        <directory>.</directory>
+        <includes>
+          <include>plugin.properties</include>
+        </includes>
+      </resource>
+    </resources>
+
+    <plugins>
+      <plugin>
+        <artifactId>maven-jar-plugin</artifactId>
+        <configuration>
+          <archive>
+            <manifestFile>${bundle-manifest}</manifestFile>
+          </archive>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
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
new file mode 100644
index 0000000..f5af2e5
--- /dev/null
+++ b/org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestGitServer.java
@@ -0,0 +1,392 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.junit.ssh;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.SshConstants;
+import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
+import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.security.SecurityUtils;
+import org.apache.sshd.server.ServerAuthenticationManager;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.auth.UserAuth;
+import org.apache.sshd.server.auth.gss.GSSAuthenticator;
+import org.apache.sshd.server.auth.gss.UserAuthGSS;
+import org.apache.sshd.server.auth.gss.UserAuthGSSFactory;
+import org.apache.sshd.server.auth.keyboard.DefaultKeyboardInteractiveAuthenticator;
+import org.apache.sshd.server.command.AbstractCommandSupport;
+import org.apache.sshd.server.command.Command;
+import org.apache.sshd.server.session.ServerSession;
+import org.apache.sshd.server.shell.UnknownCommand;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.ReceivePack;
+import org.eclipse.jgit.transport.RemoteConfig;
+import org.eclipse.jgit.transport.UploadPack;
+
+/**
+ * A simple ssh/sftp git <em>test</em> server based on Apache MINA sshd.
+ * <p>
+ * Supports only a single repository. Authenticates only the given test user
+ * against his given test public key. Supports fetch and push.
+ * </p>
+ *
+ * @since 5.2
+ */
+public class SshTestGitServer {
+
+	@NonNull
+	protected final String testUser;
+
+	@NonNull
+	protected final Repository repository;
+
+	@NonNull
+	protected final List<KeyPair> hostKeys = new ArrayList<>();
+
+	protected final SshServer server;
+
+	@NonNull
+	protected PublicKey testKey;
+
+	private final ExecutorService executorService = Executors
+			.newFixedThreadPool(2);
+
+	/**
+	 * 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
+	 *            <em>private</em> key file of the test user; the server will
+	 *            only user the public key from it
+	 * @param repository
+	 *            to serve
+	 * @param hostKey
+	 *            the unencrypted private key to use as host key
+	 * @throws IOException
+	 * @throws GeneralSecurityException
+	 */
+	public SshTestGitServer(@NonNull String testUser, @NonNull Path testKey,
+			@NonNull Repository repository, @NonNull byte[] hostKey)
+			throws IOException, GeneralSecurityException {
+		this.testUser = testUser;
+		setTestUserPublicKey(testKey);
+		this.repository = repository;
+		server = SshServer.setUpDefaultServer();
+		// Set host key
+		try (ByteArrayInputStream in = new ByteArrayInputStream(hostKey)) {
+			hostKeys.add(SecurityUtils.loadKeyPairIdentity("", in, null));
+		} catch (IOException | GeneralSecurityException e) {
+			// Ignore.
+		}
+		server.setKeyPairProvider(() -> hostKeys);
+
+		configureAuthentication();
+
+		List<NamedFactory<Command>> subsystems = configureSubsystems();
+		if (!subsystems.isEmpty()) {
+			server.setSubsystemFactories(subsystems);
+		}
+
+		configureShell();
+
+		server.setCommandFactory(command -> {
+			if (command.startsWith(RemoteConfig.DEFAULT_UPLOAD_PACK)) {
+				return new GitUploadPackCommand(command, executorService);
+			} else if (command.startsWith(RemoteConfig.DEFAULT_RECEIVE_PACK)) {
+				return new GitReceivePackCommand(command, executorService);
+			}
+			return new UnknownCommand(command);
+		});
+	}
+
+	private static class FakeUserAuthGSS extends UserAuthGSS {
+		@Override
+		protected Boolean doAuth(Buffer buffer, boolean initial)
+				throws Exception {
+			// We always reply that we did do this, but then we fail at the
+			// first token message. That way we can test that the client-side
+			// sends the correct initial request and then is skipped correctly,
+			// even if it causes a GSSException if Kerberos isn't configured at
+			// all.
+			if (initial) {
+				ServerSession session = getServerSession();
+				Buffer b = session.createBuffer(
+						SshConstants.SSH_MSG_USERAUTH_INFO_REQUEST);
+				b.putBytes(KRB5_MECH.getDER());
+				session.writePacket(b);
+				return null;
+			}
+			return Boolean.FALSE;
+		}
+	}
+
+	private List<NamedFactory<UserAuth>> getAuthFactories() {
+		List<NamedFactory<UserAuth>> authentications = new ArrayList<>();
+		authentications.add(new UserAuthGSSFactory() {
+			@Override
+			public UserAuth create() {
+				return new FakeUserAuthGSS();
+			}
+		});
+		authentications.add(
+				ServerAuthenticationManager.DEFAULT_USER_AUTH_PUBLIC_KEY_FACTORY);
+		authentications.add(
+				ServerAuthenticationManager.DEFAULT_USER_AUTH_KB_INTERACTIVE_FACTORY);
+		authentications.add(
+				ServerAuthenticationManager.DEFAULT_USER_AUTH_PASSWORD_FACTORY);
+		return authentications;
+	}
+
+	/**
+	 * Configures the authentication mechanisms of this test server. Invoked
+	 * from the constructor. The default sets up public key authentication for
+	 * the test user, and a gssapi-with-mic authenticator that pretends to
+	 * support this mechanism, but that then refuses to authenticate anyone.
+	 */
+	protected void configureAuthentication() {
+		server.setUserAuthFactories(getAuthFactories());
+		// Disable some authentications
+		server.setPasswordAuthenticator(null);
+		server.setKeyboardInteractiveAuthenticator(null);
+		server.setHostBasedAuthenticator(null);
+		// Pretend we did gssapi-with-mic.
+		server.setGSSAuthenticator(new GSSAuthenticator() {
+			@Override
+			public boolean validateInitialUser(ServerSession session,
+					String user) {
+				return false;
+			}
+		});
+		// Accept only the test user/public key
+		server.setPublickeyAuthenticator((userName, publicKey, session) -> {
+			return SshTestGitServer.this.testUser.equals(userName) && KeyUtils
+					.compareKeys(SshTestGitServer.this.testKey, publicKey);
+		});
+	}
+
+	/**
+	 * Configures the test server's subsystems (sftp, scp). Invoked from the
+	 * constructor. The default provides a simple SFTP setup with the root
+	 * directory as the given repository's .git directory's parent. (I.e., at
+	 * the directory containing the .git directory.)
+	 *
+	 * @return A possibly empty collection of subsystems.
+	 */
+	@NonNull
+	protected List<NamedFactory<Command>> configureSubsystems() {
+		// SFTP.
+		server.setFileSystemFactory(new VirtualFileSystemFactory() {
+
+			@Override
+			protected Path computeRootDir(Session session) throws IOException {
+				return SshTestGitServer.this.repository.getDirectory()
+						.getParentFile().getAbsoluteFile().toPath();
+			}
+		});
+		return Collections
+				.singletonList((new SftpSubsystemFactory.Builder()).build());
+	}
+
+	/**
+	 * Configures shell access for the test server. The default provides no
+	 * shell at all.
+	 */
+	protected void configureShell() {
+		// No shell
+		server.setShellFactory(null);
+	}
+
+	/**
+	 * Adds an additional host key to the server.
+	 *
+	 * @param key
+	 *            path to the private key file; should not be encrypted
+	 * @param inFront
+	 *            whether to add the new key before other existing keys
+	 * @throws IOException
+	 *             if the file denoted by the {@link Path} {@code key} cannot be
+	 *             read
+	 * @throws GeneralSecurityException
+	 *             if the key contained in the file cannot be read
+	 */
+	public void addHostKey(@NonNull Path key, boolean inFront)
+			throws IOException, GeneralSecurityException {
+		try (InputStream in = Files.newInputStream(key)) {
+			KeyPair pair = SecurityUtils.loadKeyPairIdentity(key.toString(), in,
+					null);
+			if (inFront) {
+				hostKeys.add(0, pair);
+			} else {
+				hostKeys.add(pair);
+			}
+		}
+	}
+
+	/**
+	 * Enable password authentication. The server will accept the test user's
+	 * name, converted to all upper-case, as password.
+	 */
+	public void enablePasswordAuthentication() {
+		server.setPasswordAuthenticator((user, pwd, session) -> {
+			return testUser.equals(user)
+					&& testUser.toUpperCase(Locale.ROOT).equals(pwd);
+		});
+	}
+
+	/**
+	 * Enable keyboard-interactive authentication. The server will accept the
+	 * test user's name, converted to all upper-case, as password.
+	 */
+	public void enableKeyboardInteractiveAuthentication() {
+		server.setPasswordAuthenticator((user, pwd, session) -> {
+			return testUser.equals(user)
+					&& testUser.toUpperCase(Locale.ROOT).equals(pwd);
+		});
+		server.setKeyboardInteractiveAuthenticator(
+				DefaultKeyboardInteractiveAuthenticator.INSTANCE);
+	}
+
+	/**
+	 * Starts the test server, listening on a random port.
+	 *
+	 * @return the port the server listens on; test clients should connect to
+	 *         that port
+	 * @throws IOException
+	 */
+	public int start() throws IOException {
+		server.start();
+		return server.getPort();
+	}
+
+	/**
+	 * Stops the test server.
+	 *
+	 * @throws IOException
+	 */
+	public void stop() throws IOException {
+		executorService.shutdownNow();
+		server.stop(true);
+	}
+
+	public void setTestUserPublicKey(Path key)
+			throws IOException, GeneralSecurityException {
+		this.testKey = AuthorizedKeyEntry.readAuthorizedKeys(key).get(0)
+				.resolvePublicKey(PublicKeyEntryResolver.IGNORING);
+	}
+
+	private class GitUploadPackCommand extends AbstractCommandSupport {
+
+		protected GitUploadPackCommand(String command,
+				ExecutorService executorService) {
+			super(command, executorService, false);
+		}
+
+		@Override
+		public void run() {
+			UploadPack uploadPack = new UploadPack(repository);
+			String gitProtocol = getEnvironment().getEnv().get("GIT_PROTOCOL");
+			if (gitProtocol != null) {
+				uploadPack
+						.setExtraParameters(Collections.singleton(gitProtocol));
+			}
+			try {
+				uploadPack.upload(getInputStream(), getOutputStream(),
+						getErrorStream());
+				onExit(0);
+			} catch (IOException e) {
+				log.warn(
+						MessageFormat.format("Could not run {0}", getCommand()),
+						e);
+				onExit(-1, e.toString());
+			}
+		}
+
+	}
+
+	private class GitReceivePackCommand extends AbstractCommandSupport {
+
+		protected GitReceivePackCommand(String command,
+				ExecutorService executorService) {
+			super(command, executorService, false);
+		}
+
+		@Override
+		public void run() {
+			try {
+				new ReceivePack(repository).receive(getInputStream(),
+						getOutputStream(), getErrorStream());
+				onExit(0);
+			} catch (IOException e) {
+				log.warn(
+						MessageFormat.format("Could not run {0}", getCommand()),
+						e);
+				onExit(-1, e.toString());
+			}
+		}
+
+	}
+}
diff --git a/org.eclipse.jgit.junit/.settings/.api_filters b/org.eclipse.jgit.junit/.settings/.api_filters
deleted file mode 100644
index e5de787..0000000
--- a/org.eclipse.jgit.junit/.settings/.api_filters
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<component id="org.eclipse.jgit.junit" version="2">
-    <resource path="src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java" type="org.eclipse.jgit.junit.LocalDiskRepositoryTestCase">
-        <filter comment="Don't care about non-API in tests." id="643842064">
-            <message_arguments>
-                <message_argument value="FileRepository"/>
-                <message_argument value="LocalDiskRepositoryTestCase"/>
-                <message_argument value="createBareRepository()"/>
-            </message_arguments>
-        </filter>
-        <filter comment="Don't care about non-API in tests." id="643842064">
-            <message_arguments>
-                <message_argument value="FileRepository"/>
-                <message_argument value="LocalDiskRepositoryTestCase"/>
-                <message_argument value="createRepository(boolean, boolean)"/>
-            </message_arguments>
-        </filter>
-        <filter comment="Don't care about non-API in tests." id="643842064">
-            <message_arguments>
-                <message_argument value="FileRepository"/>
-                <message_argument value="LocalDiskRepositoryTestCase"/>
-                <message_argument value="createWorkRepository()"/>
-            </message_arguments>
-        </filter>
-    </resource>
-    <resource path="src/org/eclipse/jgit/junit/RepositoryTestCase.java" type="org.eclipse.jgit.junit.RepositoryTestCase">
-        <filter comment="Don't care about non-API in tests." id="627060751">
-            <message_arguments>
-                <message_argument value="FileRepository"/>
-                <message_argument value="RepositoryTestCase"/>
-                <message_argument value="db"/>
-            </message_arguments>
-        </filter>
-    </resource>
-</component>
diff --git a/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.core.prefs
index 794592d..2ca78ff 100644
--- a/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.core.prefs
@@ -91,7 +91,7 @@
 org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
 org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
 org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning
-org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error
 org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
diff --git a/org.eclipse.jgit.junit/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.junit/.settings/org.eclipse.mylyn.team.ui.prefs
index 0cba949..2fca432 100644
--- a/org.eclipse.jgit.junit/.settings/org.eclipse.mylyn.team.ui.prefs
+++ b/org.eclipse.jgit.junit/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -1,3 +1,3 @@
 #Tue Jul 19 20:11:28 CEST 2011
-commit.comment.template=${task.description} \n\nBug\: ${task.key}
+commit.comment.template=${task.description}\n\nBug\: ${task.key}
 eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
index b9cd4a3..a416ae7 100644
--- a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
@@ -3,31 +3,34 @@
 Bundle-Name: %plugin_name
 Automatic-Module-Name: org.eclipse.jgit.junit
 Bundle-SymbolicName: org.eclipse.jgit.junit
-Bundle-Version: 5.1.9.qualifier
+Bundle-Version: 5.2.3.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Import-Package: org.eclipse.jgit.api;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.api.errors;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.dircache;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.errors;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.internal.storage.pack;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lib;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.merge;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.revwalk;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.storage.file;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.treewalk;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.treewalk.filter;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.util;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.util.io;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.util.time;version="[5.1.9,5.2.0)",
+Import-Package: org.eclipse.jgit.annotations;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.api;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.api.errors;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.dircache;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.errors;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lib;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.merge;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.revwalk;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.storage.file;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.transport;version="5.2.3",
+ org.eclipse.jgit.treewalk;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.treewalk.filter;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.util;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.util.io;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.util.time;version="[5.2.3,5.3.0)",
  org.junit;version="[4.12,5.0.0)",
  org.junit.rules;version="[4.12,5.0.0)",
  org.junit.runner;version="[4.12,5.0.0)",
- org.junit.runners.model;version="[4.12,5.0.0)"
-Export-Package: org.eclipse.jgit.junit;version="5.1.9";
+ org.junit.runners.model;version="[4.12,5.0.0)",
+ org.slf4j;version="[1.7.0,2.0.0)"
+Export-Package: org.eclipse.jgit.junit;version="5.2.3";
   uses:="org.eclipse.jgit.dircache,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
@@ -35,5 +38,9 @@
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.util,
    org.eclipse.jgit.storage.file,
-   org.eclipse.jgit.api",
- org.eclipse.jgit.junit.time;version="5.1.9"
+   org.eclipse.jgit.api,
+   org.junit.rules,
+   org.junit.runners.model,
+   org.junit.runner,
+   org.eclipse.jgit.util.time",
+ org.eclipse.jgit.junit.time;version="5.2.3";uses:="org.eclipse.jgit.util.time"
diff --git a/org.eclipse.jgit.junit/pom.xml b/org.eclipse.jgit.junit/pom.xml
index ab20002..e198193 100644
--- a/org.eclipse.jgit.junit/pom.xml
+++ b/org.eclipse.jgit.junit/pom.xml
@@ -52,7 +52,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>5.1.9-SNAPSHOT</version>
+    <version>5.2.3-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.junit</artifactId>
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java
index d3d7d68..8de386e 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java
@@ -70,7 +70,7 @@
  * Mock {@link org.eclipse.jgit.util.SystemReader} for tests.
  */
 public class MockSystemReader extends SystemReader {
-	private final class MockConfig extends FileBasedConfig {
+	private static final class MockConfig extends FileBasedConfig {
 		private MockConfig(File cfgLocation, FS fs) {
 			super(cfgLocation, fs);
 		}
diff --git a/org.eclipse.jgit.lfs.server.test/.classpath b/org.eclipse.jgit.lfs.server.test/.classpath
index 4f9f6bf..f08af0a 100644
--- a/org.eclipse.jgit.lfs.server.test/.classpath
+++ b/org.eclipse.jgit.lfs.server.test/.classpath
@@ -2,6 +2,10 @@
 <classpath>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
 	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
-	<classpathentry kind="src" path="tst"/>
+	<classpathentry kind="src" path="tst">
+		<attributes>
+			<attribute name="test" value="true"/>
+		</attributes>
+	</classpathentry>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.jdt.core.prefs
index 794592d..2ca78ff 100644
--- a/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.jdt.core.prefs
@@ -91,7 +91,7 @@
 org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
 org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
 org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning
-org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error
 org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
diff --git a/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.mylyn.team.ui.prefs
index ce7a0f0..984263d 100644
--- a/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.mylyn.team.ui.prefs
+++ b/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -1,2 +1,2 @@
-commit.comment.template=${task.description} \n\nBug\: ${task.key}
+commit.comment.template=${task.description}\n\nBug\: ${task.key}
 eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF
index 4f05e9d..32bdf98 100644
--- a/org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
 Bundle-Name: %plugin_name
 Automatic-Module-Name: org.eclipse.jgit.lfs.server.test
 Bundle-SymbolicName: org.eclipse.jgit.lfs.server.test
-Bundle-Version: 5.1.9.qualifier
+Bundle-Version: 5.2.3.qualifier
 Bundle-Vendor: %provider_name
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
@@ -28,24 +28,24 @@
  org.eclipse.jetty.util.log;version="[9.4.5,10.0.0)",
  org.eclipse.jetty.util.security;version="[9.4.5,10.0.0)",
  org.eclipse.jetty.util.thread;version="[9.4.5,10.0.0)",
- org.eclipse.jgit.api;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.api.errors;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.junit;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.junit.http;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lfs;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lfs.errors;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lfs.lib;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lfs.server;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lfs.server.fs;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lfs.test;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lib;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.revwalk;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.storage.file;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.transport;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.treewalk;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.treewalk.filter;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.util;version="[5.1.9,5.2.0)",
+ org.eclipse.jgit.api;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.api.errors;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.junit;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.junit.http;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lfs;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lfs.errors;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lfs.lib;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lfs.server;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lfs.server.fs;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lfs.test;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lib;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.revwalk;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.storage.file;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.transport;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.treewalk;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.treewalk.filter;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.util;version="[5.2.3,5.3.0)",
  org.hamcrest.core;version="[1.1.0,2.0.0)",
  org.junit;version="[4.12,5.0.0)",
  org.junit.rules;version="[4.12,5.0.0)",
diff --git a/org.eclipse.jgit.lfs.server.test/pom.xml b/org.eclipse.jgit.lfs.server.test/pom.xml
index b2cb9cb..1e26b0a 100644
--- a/org.eclipse.jgit.lfs.server.test/pom.xml
+++ b/org.eclipse.jgit.lfs.server.test/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>5.1.9-SNAPSHOT</version>
+    <version>5.2.3-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.lfs.server.test</artifactId>
@@ -137,7 +137,7 @@
       <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
         <configuration>
-          <argLine>@{argLine} -Djava.io.tmpdir=${project.build.directory}  -Xmx512m</argLine>
+          <argLine>-Djava.io.tmpdir=${project.build.directory}  -Xmx300m</argLine>
         </configuration>
       </plugin>
     </plugins>
diff --git a/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/PushTest.java b/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/PushTest.java
index b081a8e..4eb4a0d 100644
--- a/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/PushTest.java
+++ b/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/PushTest.java
@@ -42,6 +42,7 @@
  */
 package org.eclipse.jgit.lfs.server.fs;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 
 import java.io.InputStream;
@@ -149,7 +150,7 @@
 							new String(IO
 									.readWholeStream(is,
 											(int) ldr.getSize())
-									.array()));
+									.array(), UTF_8));
 				}
 			}
 
diff --git a/org.eclipse.jgit.lfs.server/.settings/.api_filters b/org.eclipse.jgit.lfs.server/.settings/.api_filters
index 84e6b19..3e60680 100644
--- a/org.eclipse.jgit.lfs.server/.settings/.api_filters
+++ b/org.eclipse.jgit.lfs.server/.settings/.api_filters
@@ -3,8 +3,8 @@
     <resource path="META-INF/MANIFEST.MF">
         <filter id="924844039">
             <message_arguments>
-                <message_argument value="5.1.8"/>
-                <message_argument value="5.1.0"/>
+                <message_argument value="5.2.3"/>
+                <message_argument value="5.2.0"/>
             </message_arguments>
         </filter>
     </resource>
diff --git a/org.eclipse.jgit.lfs.server/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.lfs.server/.settings/org.eclipse.jdt.core.prefs
index 89394ec..525ac67 100644
--- a/org.eclipse.jgit.lfs.server/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.lfs.server/.settings/org.eclipse.jdt.core.prefs
@@ -91,7 +91,7 @@
 org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
 org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
 org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning
-org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error
 org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
diff --git a/org.eclipse.jgit.lfs.server/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.lfs.server/.settings/org.eclipse.mylyn.team.ui.prefs
index ce7a0f0..984263d 100644
--- a/org.eclipse.jgit.lfs.server/.settings/org.eclipse.mylyn.team.ui.prefs
+++ b/org.eclipse.jgit.lfs.server/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -1,2 +1,2 @@
-commit.comment.template=${task.description} \n\nBug\: ${task.key}
+commit.comment.template=${task.description}\n\nBug\: ${task.key}
 eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF
index 0dec217..7bed133 100644
--- a/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF
@@ -3,19 +3,19 @@
 Bundle-Name: %plugin_name
 Automatic-Module-Name: org.eclipse.jgit.lfs.server
 Bundle-SymbolicName: org.eclipse.jgit.lfs.server
-Bundle-Version: 5.1.9.qualifier
+Bundle-Version: 5.2.3.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
-Export-Package: org.eclipse.jgit.lfs.server;version="5.1.9";
+Export-Package: org.eclipse.jgit.lfs.server;version="5.2.3";
   uses:="javax.servlet.http,
    org.eclipse.jgit.lfs.lib",
- org.eclipse.jgit.lfs.server.fs;version="5.1.9";
+ org.eclipse.jgit.lfs.server.fs;version="5.2.3";
   uses:="javax.servlet,
    javax.servlet.http,
    org.eclipse.jgit.lfs.server,
    org.eclipse.jgit.lfs.lib",
- org.eclipse.jgit.lfs.server.internal;version="5.1.9";x-internal:=true,
- org.eclipse.jgit.lfs.server.s3;version="5.1.9";
+ org.eclipse.jgit.lfs.server.internal;version="5.2.3";x-internal:=true,
+ org.eclipse.jgit.lfs.server.s3;version="5.2.3";
   uses:="org.eclipse.jgit.lfs.server,
    org.eclipse.jgit.lfs.lib"
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
@@ -25,15 +25,15 @@
  javax.servlet.http;version="[3.1.0,4.0.0)",
  org.apache.http;version="[4.3.0,5.0.0)",
  org.apache.http.client;version="[4.3.0,5.0.0)",
- org.eclipse.jgit.annotations;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.internal;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lfs.errors;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lfs.internal;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lfs.lib;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lib;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.nls;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.transport.http;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.transport.http.apache;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.util;version="[5.1.9,5.2.0)",
+ org.eclipse.jgit.annotations;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lfs.errors;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lfs.internal;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lfs.lib;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lib;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.nls;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.transport.http;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.transport.http.apache;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.util;version="[5.2.3,5.3.0)",
  org.slf4j;version="[1.7.0,2.0.0)"
diff --git a/org.eclipse.jgit.lfs.server/pom.xml b/org.eclipse.jgit.lfs.server/pom.xml
index 543420c..4a2b38a 100644
--- a/org.eclipse.jgit.lfs.server/pom.xml
+++ b/org.eclipse.jgit.lfs.server/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>5.1.9-SNAPSHOT</version>
+    <version>5.2.3-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.lfs.server</artifactId>
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LargeFileRepository.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LargeFileRepository.java
index cfa53af..c2439ad 100644
--- a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LargeFileRepository.java
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LargeFileRepository.java
@@ -61,7 +61,7 @@
 	 *            id of the object to download
 	 * @return Action for downloading the object
 	 */
-	public Response.Action getDownloadAction(AnyLongObjectId id);
+	Response.Action getDownloadAction(AnyLongObjectId id);
 
 	/**
 	 * Get upload action
@@ -72,7 +72,7 @@
 	 *            size of the object to be uploaded
 	 * @return Action for uploading the object
 	 */
-	public Response.Action getUploadAction(AnyLongObjectId id, long size);
+	Response.Action getUploadAction(AnyLongObjectId id, long size);
 
 	/**
 	 * Get verify action
@@ -82,7 +82,8 @@
 	 * @return Action for verifying the object, or {@code null} if the server
 	 *         doesn't support or require verification
 	 */
-	public @Nullable Response.Action getVerifyAction(AnyLongObjectId id);
+	@Nullable
+	Response.Action getVerifyAction(AnyLongObjectId id);
 
 	/**
 	 * Get size of an object
@@ -93,5 +94,5 @@
 	 *         exist
 	 * @throws java.io.IOException
 	 */
-	public long getSize(AnyLongObjectId id) throws IOException;
+	long getSize(AnyLongObjectId id) throws IOException;
 }
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsRepository.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsRepository.java
index 55d9093..0a7c37c 100644
--- a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsRepository.java
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsRepository.java
@@ -99,7 +99,8 @@
 
 	/** {@inheritDoc} */
 	@Override
-	public @Nullable Action getVerifyAction(AnyLongObjectId id) {
+	@Nullable
+	public Action getVerifyAction(AnyLongObjectId id) {
 		return null;
 	}
 
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/s3/SignerV4.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/s3/SignerV4.java
index b21c94e..7b76cec 100644
--- a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/s3/SignerV4.java
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/s3/SignerV4.java
@@ -411,7 +411,8 @@
 		String stringToSign = stringToSign(SCHEME, ALGORITHM, dateTimeStamp,
 				scope, canonicalRequest);
 
-		byte[] signature = (SCHEME + bucketConfig.getSecretKey()).getBytes();
+		byte[] signature = (SCHEME + bucketConfig.getSecretKey())
+				.getBytes(UTF_8);
 		signature = sign(dateStamp, signature);
 		signature = sign(bucketConfig.getRegion(), signature);
 		signature = sign(S3, signature);
diff --git a/org.eclipse.jgit.lfs.test/.classpath b/org.eclipse.jgit.lfs.test/.classpath
index e43ae76..e79b7c7 100644
--- a/org.eclipse.jgit.lfs.test/.classpath
+++ b/org.eclipse.jgit.lfs.test/.classpath
@@ -2,7 +2,15 @@
 <classpath>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
 	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
-	<classpathentry kind="src" path="src"/>
-	<classpathentry kind="src" path="tst"/>
+	<classpathentry kind="src" path="src">
+		<attributes>
+			<attribute name="test" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="src" path="tst">
+		<attributes>
+			<attribute name="test" value="true"/>
+		</attributes>
+	</classpathentry>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/org.eclipse.jgit.lfs.test/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.lfs.test/.settings/org.eclipse.jdt.core.prefs
index 794592d..2ca78ff 100644
--- a/org.eclipse.jgit.lfs.test/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.lfs.test/.settings/org.eclipse.jdt.core.prefs
@@ -91,7 +91,7 @@
 org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
 org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
 org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning
-org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error
 org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
diff --git a/org.eclipse.jgit.lfs.test/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.lfs.test/.settings/org.eclipse.mylyn.team.ui.prefs
index ce7a0f0..984263d 100644
--- a/org.eclipse.jgit.lfs.test/.settings/org.eclipse.mylyn.team.ui.prefs
+++ b/org.eclipse.jgit.lfs.test/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -1,2 +1,2 @@
-commit.comment.template=${task.description} \n\nBug\: ${task.key}
+commit.comment.template=${task.description}\n\nBug\: ${task.key}
 eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF
index 8e03dd8..2863967 100644
--- a/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF
@@ -3,23 +3,23 @@
 Bundle-Name: %plugin_name
 Automatic-Module-Name: org.eclipse.jgit.lfs.test
 Bundle-SymbolicName: org.eclipse.jgit.lfs.test
-Bundle-Version: 5.1.9.qualifier
+Bundle-Version: 5.2.3.qualifier
 Bundle-Vendor: %provider_name
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Import-Package: org.eclipse.jgit.internal.storage.dfs;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.junit;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lfs;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lfs.errors;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lfs.lib;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lib;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.revwalk;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.treewalk;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.treewalk.filter;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.util;version="[5.1.9,5.2.0)",
+Import-Package: org.eclipse.jgit.internal.storage.dfs;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.junit;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lfs;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lfs.errors;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lfs.lib;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lib;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.revwalk;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.treewalk;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.treewalk.filter;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.util;version="[5.2.3,5.3.0)",
  org.hamcrest.core;version="[1.1.0,2.0.0)",
  org.junit;version="[4.12,5.0.0)",
  org.junit.runner;version="[4.12,5.0.0)",
  org.junit.runners;version="[4.12,5.0.0)"
-Export-Package: org.eclipse.jgit.lfs.test;version="5.1.9";x-friends:="org.eclipse.jgit.lfs.server.test"
+Export-Package: org.eclipse.jgit.lfs.test;version="5.2.3";x-friends:="org.eclipse.jgit.lfs.server.test"
 
diff --git a/org.eclipse.jgit.lfs.test/pom.xml b/org.eclipse.jgit.lfs.test/pom.xml
index c7734f2..37d11f9 100644
--- a/org.eclipse.jgit.lfs.test/pom.xml
+++ b/org.eclipse.jgit.lfs.test/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>5.1.9-SNAPSHOT</version>
+    <version>5.2.3-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.lfs.test</artifactId>
@@ -111,7 +111,7 @@
       <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
         <configuration>
-          <argLine>@{argLine} -Djava.io.tmpdir=${project.build.directory}  -Xmx512m</argLine>
+          <argLine>-Djava.io.tmpdir=${project.build.directory}  -Xmx300m</argLine>
         </configuration>
       </plugin>
     </plugins>
diff --git a/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LFSPointerTest.java b/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LFSPointerTest.java
index a1283dd..146a25e 100644
--- a/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LFSPointerTest.java
+++ b/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LFSPointerTest.java
@@ -43,7 +43,7 @@
 
 package org.eclipse.jgit.lfs.lib;
 
-import static org.eclipse.jgit.lib.Constants.CHARACTER_ENCODING;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 
 import java.io.ByteArrayOutputStream;
@@ -66,7 +66,7 @@
 			assertEquals(
 					"version https://git-lfs.github.com/spec/v1\noid sha256:"
 							+ s + "\nsize 4\n",
-					baos.toString(CHARACTER_ENCODING));
+					baos.toString(UTF_8.name()));
 		}
 	}
 }
diff --git a/org.eclipse.jgit.lfs/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.lfs/.settings/org.eclipse.jdt.core.prefs
index 89394ec..525ac67 100644
--- a/org.eclipse.jgit.lfs/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.lfs/.settings/org.eclipse.jdt.core.prefs
@@ -91,7 +91,7 @@
 org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
 org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
 org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning
-org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error
 org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
diff --git a/org.eclipse.jgit.lfs/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.lfs/.settings/org.eclipse.mylyn.team.ui.prefs
index ce7a0f0..984263d 100644
--- a/org.eclipse.jgit.lfs/.settings/org.eclipse.mylyn.team.ui.prefs
+++ b/org.eclipse.jgit.lfs/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -1,2 +1,2 @@
-commit.comment.template=${task.description} \n\nBug\: ${task.key}
+commit.comment.template=${task.description}\n\nBug\: ${task.key}
 eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF
index f731c54..209b2b2 100644
--- a/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF
@@ -3,33 +3,33 @@
 Bundle-Name: %plugin_name
 Automatic-Module-Name: org.eclipse.jgit.lfs
 Bundle-SymbolicName: org.eclipse.jgit.lfs
-Bundle-Version: 5.1.9.qualifier
+Bundle-Version: 5.2.3.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
-Export-Package: org.eclipse.jgit.lfs;version="5.1.9",
- org.eclipse.jgit.lfs.errors;version="5.1.9",
- org.eclipse.jgit.lfs.internal;version="5.1.9";x-friends:="org.eclipse.jgit.lfs.test,org.eclipse.jgit.lfs.server.fs,org.eclipse.jgit.lfs.server",
- org.eclipse.jgit.lfs.lib;version="5.1.9"
+Export-Package: org.eclipse.jgit.lfs;version="5.2.3",
+ org.eclipse.jgit.lfs.errors;version="5.2.3",
+ org.eclipse.jgit.lfs.internal;version="5.2.3";x-friends:="org.eclipse.jgit.lfs.test,org.eclipse.jgit.lfs.server.fs,org.eclipse.jgit.lfs.server",
+ org.eclipse.jgit.lfs.lib;version="5.2.3"
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Import-Package: com.google.gson;version="[2.8.2,3.0.0)",
  com.google.gson.stream;version="[2.8.2,3.0.0)",
  org.apache.http.impl.client;version="[4.2.6,5.0.0)",
  org.apache.http.impl.conn;version="[4.2.6,5.0.0)",
- org.eclipse.jgit.annotations;version="[5.1.9,5.2.0)";resolution:=optional,
- org.eclipse.jgit.api.errors;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.attributes;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.diff;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.errors;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.hooks;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lib;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.nls;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.revwalk;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.storage.file;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.storage.pack;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.transport;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.transport.http;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.treewalk;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.treewalk.filter;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.util;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.util.io;version="[5.1.9,5.2.0)"
+ org.eclipse.jgit.annotations;version="[5.2.3,5.3.0)";resolution:=optional,
+ org.eclipse.jgit.api.errors;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.attributes;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.diff;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.errors;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.hooks;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lib;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.nls;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.revwalk;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.storage.file;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.storage.pack;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.transport;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.transport.http;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.treewalk;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.treewalk.filter;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.util;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.util.io;version="[5.2.3,5.3.0)"
diff --git a/org.eclipse.jgit.lfs/pom.xml b/org.eclipse.jgit.lfs/pom.xml
index 311b6ce..f7cad33 100644
--- a/org.eclipse.jgit.lfs/pom.xml
+++ b/org.eclipse.jgit.lfs/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>5.1.9-SNAPSHOT</version>
+    <version>5.2.3-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.lfs</artifactId>
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/BuiltinLFS.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/BuiltinLFS.java
index 415caa9..56e3a12 100644
--- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/BuiltinLFS.java
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/BuiltinLFS.java
@@ -99,7 +99,8 @@
 	}
 
 	@Override
-	public @Nullable PrePushHook getPrePushHook(Repository repo,
+	@Nullable
+	public PrePushHook getPrePushHook(Repository repo,
 			PrintStream outputStream) {
 		if (isEnabled(repo)) {
 			return new LfsPrePushHook(repo, outputStream);
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/SmudgeFilter.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/SmudgeFilter.java
index fac87c1..7bacf49 100644
--- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/SmudgeFilter.java
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/SmudgeFilter.java
@@ -222,7 +222,10 @@
 									Integer.valueOf(responseCode)));
 				}
 				Path path = lfs.getMediaFile(ptr.getOid());
-				path.getParent().toFile().mkdirs();
+				Path parent = path.getParent();
+				if (parent != null) {
+					parent.toFile().mkdirs();
+				}
 				try (InputStream contentIn = contentServerConn
 						.getInputStream()) {
 					long bytesCopied = Files.copy(contentIn, path);
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/AtomicObjectOutputStream.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/AtomicObjectOutputStream.java
index 0762ac5..317d68a 100644
--- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/AtomicObjectOutputStream.java
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/AtomicObjectOutputStream.java
@@ -106,7 +106,8 @@
 	 *         stream. May return {@code null} if called before closing this
 	 *         stream.
 	 */
-	public @Nullable AnyLongObjectId getId() {
+	@Nullable
+	public AnyLongObjectId getId() {
 		return id;
 	}
 
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 955eca0..feb8b4a 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
@@ -227,7 +227,8 @@
 	 * @throws IOException
 	 *             in case of any error.
 	 */
-	public static @NonNull HttpConnection getLfsContentConnection(
+	@NonNull
+	public static HttpConnection getLfsContentConnection(
 			Repository repo, Protocol.Action action, String method)
 			throws IOException {
 		URL contentUrl = new URL(action.href);
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/.settings/org.eclipse.mylyn.team.ui.prefs
index 0cba949..2fca432 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/.settings/org.eclipse.mylyn.team.ui.prefs
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -1,3 +1,3 @@
 #Tue Jul 19 20:11:28 CEST 2011
-commit.comment.template=${task.description} \n\nBug\: ${task.key}
+commit.comment.template=${task.description}\n\nBug\: ${task.key}
 eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml
index 2a25885..ad4da8c 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit"
       label="%featureName"
-      version="5.1.9.qualifier"
+      version="5.2.3.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml
index cfbdee7..576756b 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>5.1.9-SNAPSHOT</version>
+    <version>5.2.3-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/.settings/org.eclipse.mylyn.team.ui.prefs
index 0cba949..2fca432 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/.settings/org.eclipse.mylyn.team.ui.prefs
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -1,3 +1,3 @@
 #Tue Jul 19 20:11:28 CEST 2011
-commit.comment.template=${task.description} \n\nBug\: ${task.key}
+commit.comment.template=${task.description}\n\nBug\: ${task.key}
 eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml
index d58fc2a..e721ddc 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.http.apache"
       label="%featureName"
-      version="5.1.9.qualifier"
+      version="5.2.3.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml
index 81ffd3a..c356b34 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>5.1.9-SNAPSHOT</version>
+    <version>5.2.3-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/.settings/org.eclipse.mylyn.team.ui.prefs
index 0cba949..2fca432 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/.settings/org.eclipse.mylyn.team.ui.prefs
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -1,3 +1,3 @@
 #Tue Jul 19 20:11:28 CEST 2011
-commit.comment.template=${task.description} \n\nBug\: ${task.key}
+commit.comment.template=${task.description}\n\nBug\: ${task.key}
 eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml
index 3edf069..0abc1ac 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.junit"
       label="%featureName"
-      version="5.1.9.qualifier"
+      version="5.2.3.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
@@ -41,6 +41,13 @@
          unpack="false"/>
 
    <plugin
+         id="org.eclipse.jgit.junit.ssh"
+         download-size="0"
+         install-size="0"
+         version="0.0.0"
+         unpack="false"/>
+
+   <plugin
          id="org.eclipse.jgit.http.server"
          download-size="0"
          install-size="0"
@@ -54,4 +61,18 @@
          version="0.0.0"
          unpack="false"/>
 
+   <plugin
+         id="org.apache.sshd.core"
+         download-size="0"
+         install-size="0"
+         version="0.0.0"
+         unpack="false"/>
+
+   <plugin
+         id="org.apache.sshd.sftp"
+         download-size="0"
+         install-size="0"
+         version="0.0.0"
+         unpack="false"/>
+
 </feature>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml
index 783af2d..da11f32 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>5.1.9-SNAPSHOT</version>
+    <version>5.2.3-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
@@ -72,6 +72,11 @@
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit.junit.ssh</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.http.server</artifactId>
       <version>${project.version}</version>
     </dependency>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/.settings/org.eclipse.mylyn.team.ui.prefs
index 0cba949..2fca432 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/.settings/org.eclipse.mylyn.team.ui.prefs
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -1,3 +1,3 @@
 #Tue Jul 19 20:11:28 CEST 2011
-commit.comment.template=${task.description} \n\nBug\: ${task.key}
+commit.comment.template=${task.description}\n\nBug\: ${task.key}
 eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml
index bfb95c3..d287505 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.lfs"
       label="%featureName"
-      version="5.1.9.qualifier"
+      version="5.2.3.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/pom.xml
index 57ad031..7957600 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>5.1.9-SNAPSHOT</version>
+    <version>5.2.3-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/.settings/org.eclipse.mylyn.team.ui.prefs
index 0cba949..2fca432 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/.settings/org.eclipse.mylyn.team.ui.prefs
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -1,3 +1,3 @@
 #Tue Jul 19 20:11:28 CEST 2011
-commit.comment.template=${task.description} \n\nBug\: ${task.key}
+commit.comment.template=${task.description}\n\nBug\: ${task.key}
 eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml
index b5dddaf..c74326e 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.pgm"
       label="%featureName"
-      version="5.1.9.qualifier"
+      version="5.2.3.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
@@ -31,8 +31,9 @@
          version="0.0.0"/>
 
    <requires>
-      <import feature="org.eclipse.jgit" version="5.1.9" match="equivalent"/>
-      <import feature="org.eclipse.jgit.lfs" version="5.1.9" match="equivalent"/>
+      <import feature="org.eclipse.jgit" version="5.2.3" match="equivalent"/>
+      <import feature="org.eclipse.jgit.lfs" version="5.2.3" match="equivalent"/>
+      <import feature="org.eclipse.jgit.ssh.apache" version="5.2.3" match="equivalent"/>
    </requires>
 
    <plugin
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml
index 80424c1..e45d79d 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>5.1.9-SNAPSHOT</version>
+    <version>5.2.3-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/.settings/org.eclipse.mylyn.team.ui.prefs
index 0cba949..2fca432 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/.settings/org.eclipse.mylyn.team.ui.prefs
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -1,3 +1,3 @@
 #Tue Jul 19 20:11:28 CEST 2011
-commit.comment.template=${task.description} \n\nBug\: ${task.key}
+commit.comment.template=${task.description}\n\nBug\: ${task.key}
 eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml
index 902a904..2179699 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.pgm.source"
       label="%featureName"
-      version="5.1.9.qualifier"
+      version="5.2.3.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml
index 2af5eb8..221f031 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>5.1.9-SNAPSHOT</version>
+    <version>5.2.3-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/.settings/org.eclipse.mylyn.team.ui.prefs
index 0cba949..2fca432 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/.settings/org.eclipse.mylyn.team.ui.prefs
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -1,3 +1,3 @@
 #Tue Jul 19 20:11:28 CEST 2011
-commit.comment.template=${task.description} \n\nBug\: ${task.key}
+commit.comment.template=${task.description}\n\nBug\: ${task.key}
 eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/category.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/category.xml
index 0149029..995bd21 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/category.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/category.xml
@@ -9,12 +9,18 @@
    <feature url="features/org.eclipse.jgit.pgm_0.0.0.qualifier.jar" id="org.eclipse.jgit.pgm" version="0.0.0" patch="true">
       <category name="JGit"/>
    </feature>
+   <feature url="features/org.eclipse.jgit.ssh.apache_0.0.0.qualifier.jar" id="org.eclipse.jgit.ssh.apache" version="0.0.0" patch="true">
+      <category name="JGit"/>
+   </feature>
    <feature url="features/org.eclipse.jgit.source_0.0.0.qualifier.jar" id="org.eclipse.jgit.source" version="0.0.0" patch="true">
       <category name="JGit"/>
    </feature>
    <feature url="features/org.eclipse.jgit.pgm.source_0.0.0.qualifier.jar" id="org.eclipse.jgit.pgm.source" version="0.0.0" patch="true">
       <category name="JGit"/>
    </feature>
+   <feature url="features/org.eclipse.jgit.ssh.apache.source_0.0.0.qualifier.jar" id="org.eclipse.jgit.ssh.apache.source" version="0.0.0" patch="true">
+      <category name="JGit"/>
+   </feature>
    <feature url="features/org.eclipse.jgit.junit_0.0.0.qualifier.jar" id="org.eclipse.jgit.junit" version="0.0.0" patch="true">
       <category name="JGit"/>
    </feature>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml
index fb1a556..e258eb2 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>5.1.9-SNAPSHOT</version>
+    <version>5.2.3-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.repository</artifactId>
@@ -96,8 +96,18 @@
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit.junit.ssh</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.http.server</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit.ssh.apache</artifactId>
+      <version>${project.version}</version>
+    </dependency>
   </dependencies>
 </project>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/.settings/org.eclipse.mylyn.team.ui.prefs
index 0cba949..2fca432 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/.settings/org.eclipse.mylyn.team.ui.prefs
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -1,3 +1,3 @@
 #Tue Jul 19 20:11:28 CEST 2011
-commit.comment.template=${task.description} \n\nBug\: ${task.key}
+commit.comment.template=${task.description}\n\nBug\: ${task.key}
 eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml
index f9ae9d0..cee8946 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.source"
       label="%featureName"
-      version="5.1.9.qualifier"
+      version="5.2.3.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml
index 4621e1b..60b2c5e 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>5.1.9-SNAPSHOT</version>
+    <version>5.2.3-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/.gitignore b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/.gitignore
new file mode 100644
index 0000000..2f7896d
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/.gitignore
@@ -0,0 +1 @@
+target/
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/.project b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/.project
new file mode 100644
index 0000000..77876e1
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.eclipse.jgit.ssh.apache.feature</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.pde.FeatureBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.FeatureNature</nature>
+	</natures>
+</projectDescription>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/.settings/org.eclipse.core.resources.prefs b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..14bdc2c
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,3 @@
+#Fri Jun 18 23:33:45 CEST 2010
+eclipse.preferences.version=1
+encoding/<project>=UTF-8
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/.settings/org.eclipse.core.runtime.prefs b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/.settings/org.eclipse.core.runtime.prefs
new file mode 100644
index 0000000..898252b
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/.settings/org.eclipse.core.runtime.prefs
@@ -0,0 +1,3 @@
+#Fri Jun 18 23:33:45 CEST 2010
+eclipse.preferences.version=1
+line.separator=\n
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/.settings/org.eclipse.mylyn.tasks.ui.prefs b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/.settings/org.eclipse.mylyn.tasks.ui.prefs
new file mode 100644
index 0000000..823c0f5
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/.settings/org.eclipse.mylyn.tasks.ui.prefs
@@ -0,0 +1,4 @@
+#Tue Jul 19 20:11:28 CEST 2011
+eclipse.preferences.version=1
+project.repository.kind=bugzilla
+project.repository.url=https\://bugs.eclipse.org/bugs
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/.settings/org.eclipse.mylyn.team.ui.prefs
new file mode 100644
index 0000000..2fca432
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -0,0 +1,3 @@
+#Tue Jul 19 20:11:28 CEST 2011
+commit.comment.template=${task.description}\n\nBug\: ${task.key}
+eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/build.properties b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/build.properties
new file mode 100644
index 0000000..b4a8dde
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/build.properties
@@ -0,0 +1,4 @@
+bin.includes = feature.xml,\
+               edl-v10.html,\
+               feature.properties,\
+               license.html
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/edl-v10.html b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/edl-v10.html
new file mode 100644
index 0000000..1826b47
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/edl-v10.html
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
+<title>Eclipse Distribution License - Version 1.0</title>
+<style type="text/css">
+  body {
+    size: 8.5in 11.0in;
+    margin: 0.25in 0.5in 0.25in 0.5in;
+    tab-interval: 0.5in;
+    }
+  p {
+    margin-left: auto;
+    margin-top:  0.5em;
+    margin-bottom: 0.5em;
+    }
+  p.list {
+    margin-left: 0.5in;
+    margin-top:  0.05em;
+    margin-bottom: 0.05em;
+    }
+  </style>
+
+</head>
+
+<body lang="EN-US">
+
+<p><b>Eclipse Distribution License - v 1.0</b></p>
+
+<p>Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors. </p>
+
+<p>All rights reserved.</p>
+<p>Redistribution and use in source and binary forms, with or without modification,
+    are permitted provided that the following conditions are met:
+<ul><li>Redistributions of source code must retain the above copyright notice,
+    this list of conditions and the following disclaimer.</li>
+<li>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.</li>
+<li>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.</li></ul>
+</p>
+<p>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.</p>
+
+</body>
+
+</html>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/feature.properties b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/feature.properties
new file mode 100644
index 0000000..2b08612
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/feature.properties
@@ -0,0 +1,177 @@
+###############################################################################
+# Copyright (c) 2000, 2010 IBM Corporation and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+#
+###############################################################################
+
+featureName=Java implementation of Git - ssh support using Apache MINA sshd
+providerName=Eclipse JGit
+
+updateSiteName=Eclipse JGit Update Site
+
+# description property - text of the "Feature Description"
+description=\
+Ssh support using Apache MINA sshd.\n
+################ end of description property ##################################
+
+# "copyright" property - text of the "Feature Update Copyright"
+copyright=\
+Copyright (c) 2018 Thomas Wolf and others.\n\
+All rights reserved. This program and the accompanying materials\n\
+are made available under the terms of the Eclipse Distribution License v1.0\n\
+which accompanies this distribution, and is available at\n\
+http://www.eclipse.org/org/documents/edl-v10.html\n
+################ end of copyright property ####################################
+
+# "licenseURL" property - URL of the "Feature License"
+# do not translate value - just change to point to a locale-specific HTML page
+licenseURL=license.html
+
+# "license" property - text of the "Feature Update License"
+# should be plain text version of license agreement pointed to be "licenseURL"
+license=\
+Eclipse Foundation Software User Agreement\n\
+\n\
+November 22, 2017\n\
+\n\
+Usage Of Content\n\
+\n\
+THE ECLIPSE FOUNDATION MAKES AVAILABLE SOFTWARE, DOCUMENTATION, INFORMATION\n\
+AND/OR OTHER MATERIALS FOR OPEN SOURCE PROJECTS (COLLECTIVELY "CONTENT"). USE OF\n\
+THE CONTENT IS GOVERNED BY THE TERMS AND CONDITIONS OF THIS AGREEMENT AND/OR THE\n\
+TERMS AND CONDITIONS OF LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED\n\
+BELOW. BY USING THE CONTENT, YOU AGREE THAT YOUR USE OF THE CONTENT IS GOVERNED\n\
+BY THIS AGREEMENT AND/OR THE TERMS AND CONDITIONS OF ANY APPLICABLE LICENSE\n\
+AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW. IF YOU DO NOT AGREE TO THE\n\
+TERMS AND CONDITIONS OF THIS AGREEMENT AND THE TERMS AND CONDITIONS OF ANY\n\
+APPLICABLE LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW, THEN YOU\n\
+MAY NOT USE THE CONTENT.\n\
+\n\
+Applicable Licenses\n\
+\n\
+Unless otherwise indicated, all Content made available by the Eclipse Foundation\n\
+is provided to you under the terms and conditions of the Eclipse Public License\n\
+Version 2.0 ("EPL"). A copy of the EPL is provided with this Content and is also\n\
+available at http://www.eclipse.org/legal/epl-2.0. For purposes of the EPL,\n\
+"Program" will mean the Content.\n\
+\n\
+Content includes, but is not limited to, source code, object code, documentation\n\
+and other files maintained in the Eclipse Foundation source code repository\n\
+("Repository") in software modules ("Modules") and made available as\n\
+downloadable archives ("Downloads").\n\
+\n\
+-   Content may be structured and packaged into modules to facilitate\n\
+    delivering, extending, and upgrading the Content. Typical modules may\n\
+    include plug-ins ("Plug-ins"), plug-in fragments ("Fragments"), and\n\
+    features ("Features").\n\
+-   Each Plug-in or Fragment may be packaged as a sub-directory or JAR\n\
+    (Java™ ARchive) in a directory named "plugins".\n\
+-   A Feature is a bundle of one or more Plug-ins and/or Fragments and\n\
+    associated material. Each Feature may be packaged as a sub-directory in a\n\
+    directory named "features". Within a Feature, files named "feature.xml" may\n\
+    contain a list of the names and version numbers of the Plug-ins and/or\n\
+    Fragments associated with that Feature.\n\
+-   Features may also include other Features ("Included Features"). Within a\n\
+    Feature, files named "feature.xml" may contain a list of the names and\n\
+    version numbers of Included Features.\n\
+\n\
+The terms and conditions governing Plug-ins and Fragments should be contained in\n\
+files named "about.html" ("Abouts"). The terms and conditions governing Features\n\
+and Included Features should be contained in files named "license.html"\n\
+("Feature Licenses"). Abouts and Feature Licenses may be located in any\n\
+directory of a Download or Module including, but not limited to the following\n\
+locations:\n\
+\n\
+-   The top-level (root) directory\n\
+-   Plug-in and Fragment directories\n\
+-   Inside Plug-ins and Fragments packaged as JARs\n\
+-   Sub-directories of the directory named "src" of certain Plug-ins\n\
+-   Feature directories\n\
+\n\
+Note: if a Feature made available by the Eclipse Foundation is installed using\n\
+the Provisioning Technology (as defined below), you must agree to a license\n\
+("Feature Update License") during the installation process. If the Feature\n\
+contains Included Features, the Feature Update License should either provide you\n\
+with the terms and conditions governing the Included Features or inform you\n\
+where you can locate them. Feature Update Licenses may be found in the "license"\n\
+property of files named "feature.properties" found within a Feature. Such\n\
+Abouts, Feature Licenses, and Feature Update Licenses contain the terms and\n\
+conditions (or references to such terms and conditions) that govern your use of\n\
+the associated Content in that directory.\n\
+\n\
+THE ABOUTS, FEATURE LICENSES, AND FEATURE UPDATE LICENSES MAY REFER TO THE EPL\n\
+OR OTHER LICENSE AGREEMENTS, NOTICES OR TERMS AND CONDITIONS. SOME OF THESE\n\
+OTHER LICENSE AGREEMENTS MAY INCLUDE (BUT ARE NOT LIMITED TO):\n\
+\n\
+-   Eclipse Public License Version 1.0 (available at\n\
+    http://www.eclipse.org/legal/epl-v10.html)\n\
+-   Eclipse Distribution License Version 1.0 (available at\n\
+    http://www.eclipse.org/licenses/edl-v1.0.html)\n\
+-   Common Public License Version 1.0 (available at\n\
+    http://www.eclipse.org/legal/cpl-v10.html)\n\
+-   Apache Software License 1.1 (available at\n\
+    http://www.apache.org/licenses/LICENSE)\n\
+-   Apache Software License 2.0 (available at\n\
+    http://www.apache.org/licenses/LICENSE-2.0)\n\
+-   Mozilla Public License Version 1.1 (available at\n\
+    http://www.mozilla.org/MPL/MPL-1.1.html)\n\
+\n\
+IT IS YOUR OBLIGATION TO READ AND ACCEPT ALL SUCH TERMS AND CONDITIONS PRIOR TO\n\
+USE OF THE CONTENT. If no About, Feature License, or Feature Update License is\n\
+provided, please contact the Eclipse Foundation to determine what terms and\n\
+conditions govern that particular Content.\n\
+\n\
+Use of Provisioning Technology\n\
+\n\
+The Eclipse Foundation makes available provisioning software, examples of which\n\
+include, but are not limited to, p2 and the Eclipse Update Manager\n\
+("Provisioning Technology") for the purpose of allowing users to install\n\
+software, documentation, information and/or other materials (collectively\n\
+"Installable Software"). This capability is provided with the intent of allowing\n\
+such users to install, extend and update Eclipse-based products. Information\n\
+about packaging Installable Software is available at\n\
+http://eclipse.org/equinox/p2/repository_packaging.html ("Specification").\n\
+\n\
+You may use Provisioning Technology to allow other parties to install\n\
+Installable Software. You shall be responsible for enabling the applicable\n\
+license agreements relating to the Installable Software to be presented to, and\n\
+accepted by, the users of the Provisioning Technology in accordance with the\n\
+Specification. By using Provisioning Technology in such a manner and making it\n\
+available in accordance with the Specification, you further acknowledge your\n\
+agreement to, and the acquisition of all necessary rights to permit the\n\
+following:\n\
+\n\
+1.  A series of actions may occur ("Provisioning Process") in which a user may\n\
+    execute the Provisioning Technology on a machine ("Target Machine") with the\n\
+    intent of installing, extending or updating the functionality of an\n\
+    Eclipse-based product.\n\
+2.  During the Provisioning Process, the Provisioning Technology may cause third\n\
+    party Installable Software or a portion thereof to be accessed and copied to\n\
+    the Target Machine.\n\
+3.  Pursuant to the Specification, you will provide to the user the terms and\n\
+    conditions that govern the use of the Installable Software ("Installable\n\
+    Software Agreement") and such Installable Software Agreement shall be\n\
+    accessed from the Target Machine in accordance with the Specification. Such\n\
+    Installable Software Agreement must inform the user of the terms and\n\
+    conditions that govern the Installable Software and must solicit acceptance\n\
+    by the end user in the manner prescribed in such Installable\n\
+    Software Agreement. Upon such indication of agreement by the user, the\n\
+    provisioning Technology will complete installation of the\n\
+    Installable Software.\n\
+\n\
+Cryptography\n\
+\n\
+Content may contain encryption software. The country in which you are currently\n\
+may have restrictions on the import, possession, and use, and/or re-export to\n\
+another country, of encryption software. BEFORE using any encryption software,\n\
+please check the country's laws, regulations and policies concerning the import,\n\
+possession, or use, and re-export of encryption software, to see if this is\n\
+permitted.\n\
+\n\
+Java and all Java-based trademarks are trademarks of Oracle Corporation in the\n\
+United States, other countries, or both.\n
+########### end of license property ##########################################
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/feature.xml
new file mode 100644
index 0000000..2ce712c
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/feature.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<feature
+      id="org.eclipse.jgit.ssh.apache"
+      label="%featureName"
+      version="5.2.3.qualifier"
+      provider-name="%providerName">
+
+   <description url="http://www.eclipse.org/jgit/">
+      %description
+   </description>
+
+   <copyright>
+      %copyright
+   </copyright>
+
+   <license url="%licenseURL">
+      %license
+   </license>
+
+   <url>
+      <update label="%updateSiteName" url="http://download.eclipse.org/egit/updates"/>
+      <discovery label="%updateSiteName" url="http://download.eclipse.org/egit/updates"/>
+   </url>
+
+   <requires>
+      <import plugin="org.eclipse.jgit"/>
+   </requires>
+
+   <plugin
+         id="org.eclipse.jgit.ssh.apache"
+         download-size="0"
+         install-size="0"
+         version="0.0.0"
+         unpack="false"/>
+
+   <plugin
+         id="org.apache.sshd.core"
+         download-size="0"
+         install-size="0"
+         version="0.0.0"
+         unpack="false"/>
+
+   <plugin
+         id="org.apache.sshd.sftp"
+         download-size="0"
+         install-size="0"
+         version="0.0.0"
+         unpack="false"/>
+
+   <plugin
+         id="net.i2p.crypto.eddsa"
+         download-size="0"
+         install-size="0"
+         version="0.0.0"
+         unpack="false"/>
+
+</feature>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/license.html b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/license.html
new file mode 100644
index 0000000..008b801
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/license.html
@@ -0,0 +1,189 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
+<title>Eclipse Foundation Software User Agreement</title>
+</head>
+
+<body lang="EN-US">
+	<h2>Eclipse Foundation Software User Agreement</h2>
+	<p>November 22, 2017</p>
+
+	<h3>Usage Of Content</h3>
+
+	<p>THE ECLIPSE FOUNDATION MAKES AVAILABLE SOFTWARE, DOCUMENTATION,
+		INFORMATION AND/OR OTHER MATERIALS FOR OPEN SOURCE PROJECTS
+		(COLLECTIVELY &quot;CONTENT&quot;). USE OF THE CONTENT IS GOVERNED BY
+		THE TERMS AND CONDITIONS OF THIS AGREEMENT AND/OR THE TERMS AND
+		CONDITIONS OF LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED
+		BELOW. BY USING THE CONTENT, YOU AGREE THAT YOUR USE OF THE CONTENT IS
+		GOVERNED BY THIS AGREEMENT AND/OR THE TERMS AND CONDITIONS OF ANY
+		APPLICABLE LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED
+		BELOW. IF YOU DO NOT AGREE TO THE TERMS AND CONDITIONS OF THIS
+		AGREEMENT AND THE TERMS AND CONDITIONS OF ANY APPLICABLE LICENSE
+		AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW, THEN YOU MAY NOT
+		USE THE CONTENT.</p>
+
+	<h3>Applicable Licenses</h3>
+
+	<p>
+		Unless otherwise indicated, all Content made available by the Eclipse
+		Foundation is provided to you under the terms and conditions of the
+		Eclipse Public License Version 2.0 (&quot;EPL&quot;). A copy of the
+		EPL is provided with this Content and is also available at <a
+			href="http://www.eclipse.org/legal/epl-2.0">http://www.eclipse.org/legal/epl-2.0</a>.
+		For purposes of the EPL, &quot;Program&quot; will mean the Content.
+	</p>
+
+	<p>Content includes, but is not limited to, source code, object
+		code, documentation and other files maintained in the Eclipse
+		Foundation source code repository (&quot;Repository&quot;) in software
+		modules (&quot;Modules&quot;) and made available as downloadable
+		archives (&quot;Downloads&quot;).</p>
+
+	<ul>
+		<li>Content may be structured and packaged into modules to
+			facilitate delivering, extending, and upgrading the Content. Typical
+			modules may include plug-ins (&quot;Plug-ins&quot;), plug-in
+			fragments (&quot;Fragments&quot;), and features
+			(&quot;Features&quot;).</li>
+		<li>Each Plug-in or Fragment may be packaged as a sub-directory
+			or JAR (Java&trade; ARchive) in a directory named
+			&quot;plugins&quot;.</li>
+		<li>A Feature is a bundle of one or more Plug-ins and/or
+			Fragments and associated material. Each Feature may be packaged as a
+			sub-directory in a directory named &quot;features&quot;. Within a
+			Feature, files named &quot;feature.xml&quot; may contain a list of
+			the names and version numbers of the Plug-ins and/or Fragments
+			associated with that Feature.</li>
+		<li>Features may also include other Features (&quot;Included
+			Features&quot;). Within a Feature, files named
+			&quot;feature.xml&quot; may contain a list of the names and version
+			numbers of Included Features.</li>
+	</ul>
+
+	<p>The terms and conditions governing Plug-ins and Fragments should
+		be contained in files named &quot;about.html&quot;
+		(&quot;Abouts&quot;). The terms and conditions governing Features and
+		Included Features should be contained in files named
+		&quot;license.html&quot; (&quot;Feature Licenses&quot;). Abouts and
+		Feature Licenses may be located in any directory of a Download or
+		Module including, but not limited to the following locations:</p>
+
+	<ul>
+		<li>The top-level (root) directory</li>
+		<li>Plug-in and Fragment directories</li>
+		<li>Inside Plug-ins and Fragments packaged as JARs</li>
+		<li>Sub-directories of the directory named &quot;src&quot; of
+			certain Plug-ins</li>
+		<li>Feature directories</li>
+	</ul>
+
+	<p>Note: if a Feature made available by the Eclipse Foundation is
+		installed using the Provisioning Technology (as defined below), you
+		must agree to a license (&quot;Feature Update License&quot;) during
+		the installation process. If the Feature contains Included Features,
+		the Feature Update License should either provide you with the terms
+		and conditions governing the Included Features or inform you where you
+		can locate them. Feature Update Licenses may be found in the
+		&quot;license&quot; property of files named
+		&quot;feature.properties&quot; found within a Feature. Such Abouts,
+		Feature Licenses, and Feature Update Licenses contain the terms and
+		conditions (or references to such terms and conditions) that govern
+		your use of the associated Content in that directory.</p>
+
+	<p>THE ABOUTS, FEATURE LICENSES, AND FEATURE UPDATE LICENSES MAY
+		REFER TO THE EPL OR OTHER LICENSE AGREEMENTS, NOTICES OR TERMS AND
+		CONDITIONS. SOME OF THESE OTHER LICENSE AGREEMENTS MAY INCLUDE (BUT
+		ARE NOT LIMITED TO):</p>
+
+	<ul>
+		<li>Eclipse Public License Version 1.0 (available at <a
+			href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>)
+		</li>
+		<li>Eclipse Distribution License Version 1.0 (available at <a
+			href="http://www.eclipse.org/licenses/edl-v10.html">http://www.eclipse.org/licenses/edl-v1.0.html</a>)
+		</li>
+		<li>Common Public License Version 1.0 (available at <a
+			href="http://www.eclipse.org/legal/cpl-v10.html">http://www.eclipse.org/legal/cpl-v10.html</a>)
+		</li>
+		<li>Apache Software License 1.1 (available at <a
+			href="http://www.apache.org/licenses/LICENSE">http://www.apache.org/licenses/LICENSE</a>)
+		</li>
+		<li>Apache Software License 2.0 (available at <a
+			href="http://www.apache.org/licenses/LICENSE-2.0">http://www.apache.org/licenses/LICENSE-2.0</a>)
+		</li>
+		<li>Mozilla Public License Version 1.1 (available at <a
+			href="http://www.mozilla.org/MPL/MPL-1.1.html">http://www.mozilla.org/MPL/MPL-1.1.html</a>)
+		</li>
+	</ul>
+
+	<p>IT IS YOUR OBLIGATION TO READ AND ACCEPT ALL SUCH TERMS AND
+		CONDITIONS PRIOR TO USE OF THE CONTENT. If no About, Feature License,
+		or Feature Update License is provided, please contact the Eclipse
+		Foundation to determine what terms and conditions govern that
+		particular Content.</p>
+
+
+	<h3>Use of Provisioning Technology</h3>
+
+	<p>
+		The Eclipse Foundation makes available provisioning software, examples
+		of which include, but are not limited to, p2 and the Eclipse Update
+		Manager (&quot;Provisioning Technology&quot;) for the purpose of
+		allowing users to install software, documentation, information and/or
+		other materials (collectively &quot;Installable Software&quot;). This
+		capability is provided with the intent of allowing such users to
+		install, extend and update Eclipse-based products. Information about
+		packaging Installable Software is available at <a
+			href="http://eclipse.org/equinox/p2/repository_packaging.html">http://eclipse.org/equinox/p2/repository_packaging.html</a>
+		(&quot;Specification&quot;).
+	</p>
+
+	<p>You may use Provisioning Technology to allow other parties to
+		install Installable Software. You shall be responsible for enabling
+		the applicable license agreements relating to the Installable Software
+		to be presented to, and accepted by, the users of the Provisioning
+		Technology in accordance with the Specification. By using Provisioning
+		Technology in such a manner and making it available in accordance with
+		the Specification, you further acknowledge your agreement to, and the
+		acquisition of all necessary rights to permit the following:</p>
+
+	<ol>
+		<li>A series of actions may occur (&quot;Provisioning
+			Process&quot;) in which a user may execute the Provisioning
+			Technology on a machine (&quot;Target Machine&quot;) with the intent
+			of installing, extending or updating the functionality of an
+			Eclipse-based product.</li>
+		<li>During the Provisioning Process, the Provisioning Technology
+			may cause third party Installable Software or a portion thereof to be
+			accessed and copied to the Target Machine.</li>
+		<li>Pursuant to the Specification, you will provide to the user
+			the terms and conditions that govern the use of the Installable
+			Software (&quot;Installable Software Agreement&quot;) and such
+			Installable Software Agreement shall be accessed from the Target
+			Machine in accordance with the Specification. Such Installable
+			Software Agreement must inform the user of the terms and conditions
+			that govern the Installable Software and must solicit acceptance by
+			the end user in the manner prescribed in such Installable Software
+			Agreement. Upon such indication of agreement by the user, the
+			provisioning Technology will complete installation of the Installable
+			Software.</li>
+	</ol>
+
+	<h3>Cryptography</h3>
+
+	<p>Content may contain encryption software. The country in which
+		you are currently may have restrictions on the import, possession, and
+		use, and/or re-export to another country, of encryption software.
+		BEFORE using any encryption software, please check the country's laws,
+		regulations and policies concerning the import, possession, or use,
+		and re-export of encryption software, to see if this is permitted.</p>
+
+	<p>
+		<small>Java and all Java-based trademarks are trademarks of
+			Oracle Corporation in the United States, other countries, or both.</small>
+	</p>
+</body>
+</html>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/pom.xml
new file mode 100644
index 0000000..869fb17
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/pom.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Copyright (C) 201x84, Thomas Wolf <thomas.wolf@paranor.ch>
+   and other copyright owners as documented in the project's IP log.
+
+   This program and the accompanying materials are made available
+   under the terms of the Eclipse Distribution License v1.0 which
+   accompanies this distribution, is reproduced below, and is
+   available at http://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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.eclipse.jgit</groupId>
+    <artifactId>jgit.tycho.parent</artifactId>
+    <version>5.2.3-SNAPSHOT</version>
+  </parent>
+
+  <groupId>org.eclipse.jgit.feature</groupId>
+  <artifactId>org.eclipse.jgit.ssh.apache</artifactId>
+  <packaging>eclipse-feature</packaging>
+
+  <name>JGit - Ssh support using Apache MINA sshd</name>
+  <dependencies>
+
+    <dependency>
+      <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit.ssh.apache</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+  </dependencies>
+
+</project>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/.gitignore b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/.gitignore
new file mode 100644
index 0000000..eb5a316
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/.gitignore
@@ -0,0 +1 @@
+target
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/.project b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/.project
new file mode 100644
index 0000000..5562084
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.eclipse.jgit.ssh.apache.source.feature</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.pde.FeatureBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.FeatureNature</nature>
+	</natures>
+</projectDescription>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/.settings/org.eclipse.core.resources.prefs b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..6f96ce8
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,3 @@
+#Fri Jun 18 23:33:45 CEST 2010

+eclipse.preferences.version=1

+encoding/<project>=UTF-8

diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/.settings/org.eclipse.core.runtime.prefs b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/.settings/org.eclipse.core.runtime.prefs
new file mode 100644
index 0000000..1a32dba
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/.settings/org.eclipse.core.runtime.prefs
@@ -0,0 +1,3 @@
+#Fri Jun 18 23:33:45 CEST 2010

+eclipse.preferences.version=1

+line.separator=\n

diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/.settings/org.eclipse.mylyn.tasks.ui.prefs b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/.settings/org.eclipse.mylyn.tasks.ui.prefs
new file mode 100644
index 0000000..823c0f5
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/.settings/org.eclipse.mylyn.tasks.ui.prefs
@@ -0,0 +1,4 @@
+#Tue Jul 19 20:11:28 CEST 2011
+eclipse.preferences.version=1
+project.repository.kind=bugzilla
+project.repository.url=https\://bugs.eclipse.org/bugs
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/.settings/org.eclipse.mylyn.team.ui.prefs
new file mode 100644
index 0000000..2fca432
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -0,0 +1,3 @@
+#Tue Jul 19 20:11:28 CEST 2011
+commit.comment.template=${task.description}\n\nBug\: ${task.key}
+eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/build.properties b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/build.properties
new file mode 100644
index 0000000..b4a8dde
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/build.properties
@@ -0,0 +1,4 @@
+bin.includes = feature.xml,\
+               edl-v10.html,\
+               feature.properties,\
+               license.html
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/edl-v10.html b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/edl-v10.html
new file mode 100644
index 0000000..8caddac
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/edl-v10.html
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
+<title>Eclipse Distribution License - Version 1.0</title>
+<style type="text/css">
+	body {
+		size: 8.5in 11.0in;
+		margin: 0.25in 0.5in 0.25in 0.5in;
+		tab-interval: 0.5in;
+	}
+	p {
+		margin-left: auto;
+		margin-top:  0.5em;
+		margin-bottom: 0.5em;
+	}
+	p.list {
+		margin-left: 0.5in;
+		margin-top:  0.05em;
+		margin-bottom: 0.05em;
+	}
+</style>
+
+</head>
+
+<body lang="EN-US">
+
+<p><b>Eclipse Distribution License - v 1.0</b></p>
+
+<p>Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors. </p>
+
+<p>All rights reserved.</p>
+<p>Redistribution and use in source and binary forms, with or without modification,
+	are permitted provided that the following conditions are met:
+<ul><li>Redistributions of source code must retain the above copyright notice,
+	this list of conditions and the following disclaimer. </li>
+<li>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. </li>
+<li>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. </li></ul>
+</p>
+<p>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.</p>
+
+</body>
+
+</html>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/feature.properties b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/feature.properties
new file mode 100644
index 0000000..9935431
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/feature.properties
@@ -0,0 +1,178 @@
+###############################################################################
+# Copyright (c) 2000, 2010 IBM Corporation and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+#
+###############################################################################
+
+featureName=Java implementation of Git - ssh support using Apache MINA sshd - Source Code
+providerName=Eclipse JGit
+
+updateSiteName=Eclipse JGit Update Site
+
+# description property - text of the "Feature Description"
+description=\
+Do not install in your IDE: this feature is meant to provision Target Platforms.\n\
+Source code for the JGit ssh support using Apache MINA sshd.\n
+################ end of description property ##################################
+
+# "copyright" property - text of the "Feature Update Copyright"
+copyright=\
+Copyright (c) 2018 Thomas Wolf and others\n\
+All rights reserved. This program and the accompanying materials\n\
+are made available under the terms of the Eclipse Distribution License v1.0\n\
+which accompanies this distribution, and is available at\n\
+http://www.eclipse.org/org/documents/edl-v10.html\n
+################ end of copyright property ####################################
+
+# "licenseURL" property - URL of the "Feature License"
+# do not translate value - just change to point to a locale-specific HTML page
+licenseURL=license.html
+
+# "license" property - text of the "Feature Update License"
+# should be plain text version of license agreement pointed to be "licenseURL"
+license=\
+Eclipse Foundation Software User Agreement\n\
+\n\
+November 22, 2017\n\
+\n\
+Usage Of Content\n\
+\n\
+THE ECLIPSE FOUNDATION MAKES AVAILABLE SOFTWARE, DOCUMENTATION, INFORMATION\n\
+AND/OR OTHER MATERIALS FOR OPEN SOURCE PROJECTS (COLLECTIVELY "CONTENT"). USE OF\n\
+THE CONTENT IS GOVERNED BY THE TERMS AND CONDITIONS OF THIS AGREEMENT AND/OR THE\n\
+TERMS AND CONDITIONS OF LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED\n\
+BELOW. BY USING THE CONTENT, YOU AGREE THAT YOUR USE OF THE CONTENT IS GOVERNED\n\
+BY THIS AGREEMENT AND/OR THE TERMS AND CONDITIONS OF ANY APPLICABLE LICENSE\n\
+AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW. IF YOU DO NOT AGREE TO THE\n\
+TERMS AND CONDITIONS OF THIS AGREEMENT AND THE TERMS AND CONDITIONS OF ANY\n\
+APPLICABLE LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW, THEN YOU\n\
+MAY NOT USE THE CONTENT.\n\
+\n\
+Applicable Licenses\n\
+\n\
+Unless otherwise indicated, all Content made available by the Eclipse Foundation\n\
+is provided to you under the terms and conditions of the Eclipse Public License\n\
+Version 2.0 ("EPL"). A copy of the EPL is provided with this Content and is also\n\
+available at http://www.eclipse.org/legal/epl-2.0. For purposes of the EPL,\n\
+"Program" will mean the Content.\n\
+\n\
+Content includes, but is not limited to, source code, object code, documentation\n\
+and other files maintained in the Eclipse Foundation source code repository\n\
+("Repository") in software modules ("Modules") and made available as\n\
+downloadable archives ("Downloads").\n\
+\n\
+-   Content may be structured and packaged into modules to facilitate\n\
+    delivering, extending, and upgrading the Content. Typical modules may\n\
+    include plug-ins ("Plug-ins"), plug-in fragments ("Fragments"), and\n\
+    features ("Features").\n\
+-   Each Plug-in or Fragment may be packaged as a sub-directory or JAR\n\
+    (Java™ ARchive) in a directory named "plugins".\n\
+-   A Feature is a bundle of one or more Plug-ins and/or Fragments and\n\
+    associated material. Each Feature may be packaged as a sub-directory in a\n\
+    directory named "features". Within a Feature, files named "feature.xml" may\n\
+    contain a list of the names and version numbers of the Plug-ins and/or\n\
+    Fragments associated with that Feature.\n\
+-   Features may also include other Features ("Included Features"). Within a\n\
+    Feature, files named "feature.xml" may contain a list of the names and\n\
+    version numbers of Included Features.\n\
+\n\
+The terms and conditions governing Plug-ins and Fragments should be contained in\n\
+files named "about.html" ("Abouts"). The terms and conditions governing Features\n\
+and Included Features should be contained in files named "license.html"\n\
+("Feature Licenses"). Abouts and Feature Licenses may be located in any\n\
+directory of a Download or Module including, but not limited to the following\n\
+locations:\n\
+\n\
+-   The top-level (root) directory\n\
+-   Plug-in and Fragment directories\n\
+-   Inside Plug-ins and Fragments packaged as JARs\n\
+-   Sub-directories of the directory named "src" of certain Plug-ins\n\
+-   Feature directories\n\
+\n\
+Note: if a Feature made available by the Eclipse Foundation is installed using\n\
+the Provisioning Technology (as defined below), you must agree to a license\n\
+("Feature Update License") during the installation process. If the Feature\n\
+contains Included Features, the Feature Update License should either provide you\n\
+with the terms and conditions governing the Included Features or inform you\n\
+where you can locate them. Feature Update Licenses may be found in the "license"\n\
+property of files named "feature.properties" found within a Feature. Such\n\
+Abouts, Feature Licenses, and Feature Update Licenses contain the terms and\n\
+conditions (or references to such terms and conditions) that govern your use of\n\
+the associated Content in that directory.\n\
+\n\
+THE ABOUTS, FEATURE LICENSES, AND FEATURE UPDATE LICENSES MAY REFER TO THE EPL\n\
+OR OTHER LICENSE AGREEMENTS, NOTICES OR TERMS AND CONDITIONS. SOME OF THESE\n\
+OTHER LICENSE AGREEMENTS MAY INCLUDE (BUT ARE NOT LIMITED TO):\n\
+\n\
+-   Eclipse Public License Version 1.0 (available at\n\
+    http://www.eclipse.org/legal/epl-v10.html)\n\
+-   Eclipse Distribution License Version 1.0 (available at\n\
+    http://www.eclipse.org/licenses/edl-v1.0.html)\n\
+-   Common Public License Version 1.0 (available at\n\
+    http://www.eclipse.org/legal/cpl-v10.html)\n\
+-   Apache Software License 1.1 (available at\n\
+    http://www.apache.org/licenses/LICENSE)\n\
+-   Apache Software License 2.0 (available at\n\
+    http://www.apache.org/licenses/LICENSE-2.0)\n\
+-   Mozilla Public License Version 1.1 (available at\n\
+    http://www.mozilla.org/MPL/MPL-1.1.html)\n\
+\n\
+IT IS YOUR OBLIGATION TO READ AND ACCEPT ALL SUCH TERMS AND CONDITIONS PRIOR TO\n\
+USE OF THE CONTENT. If no About, Feature License, or Feature Update License is\n\
+provided, please contact the Eclipse Foundation to determine what terms and\n\
+conditions govern that particular Content.\n\
+\n\
+Use of Provisioning Technology\n\
+\n\
+The Eclipse Foundation makes available provisioning software, examples of which\n\
+include, but are not limited to, p2 and the Eclipse Update Manager\n\
+("Provisioning Technology") for the purpose of allowing users to install\n\
+software, documentation, information and/or other materials (collectively\n\
+"Installable Software"). This capability is provided with the intent of allowing\n\
+such users to install, extend and update Eclipse-based products. Information\n\
+about packaging Installable Software is available at\n\
+http://eclipse.org/equinox/p2/repository_packaging.html ("Specification").\n\
+\n\
+You may use Provisioning Technology to allow other parties to install\n\
+Installable Software. You shall be responsible for enabling the applicable\n\
+license agreements relating to the Installable Software to be presented to, and\n\
+accepted by, the users of the Provisioning Technology in accordance with the\n\
+Specification. By using Provisioning Technology in such a manner and making it\n\
+available in accordance with the Specification, you further acknowledge your\n\
+agreement to, and the acquisition of all necessary rights to permit the\n\
+following:\n\
+\n\
+1.  A series of actions may occur ("Provisioning Process") in which a user may\n\
+    execute the Provisioning Technology on a machine ("Target Machine") with the\n\
+    intent of installing, extending or updating the functionality of an\n\
+    Eclipse-based product.\n\
+2.  During the Provisioning Process, the Provisioning Technology may cause third\n\
+    party Installable Software or a portion thereof to be accessed and copied to\n\
+    the Target Machine.\n\
+3.  Pursuant to the Specification, you will provide to the user the terms and\n\
+    conditions that govern the use of the Installable Software ("Installable\n\
+    Software Agreement") and such Installable Software Agreement shall be\n\
+    accessed from the Target Machine in accordance with the Specification. Such\n\
+    Installable Software Agreement must inform the user of the terms and\n\
+    conditions that govern the Installable Software and must solicit acceptance\n\
+    by the end user in the manner prescribed in such Installable\n\
+    Software Agreement. Upon such indication of agreement by the user, the\n\
+    provisioning Technology will complete installation of the\n\
+    Installable Software.\n\
+\n\
+Cryptography\n\
+\n\
+Content may contain encryption software. The country in which you are currently\n\
+may have restrictions on the import, possession, and use, and/or re-export to\n\
+another country, of encryption software. BEFORE using any encryption software,\n\
+please check the country's laws, regulations and policies concerning the import,\n\
+possession, or use, and re-export of encryption software, to see if this is\n\
+permitted.\n\
+\n\
+Java and all Java-based trademarks are trademarks of Oracle Corporation in the\n\
+United States, other countries, or both.\n
+########### end of license property ##########################################
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/feature.xml
new file mode 100644
index 0000000..0877744
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/feature.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<feature
+      id="org.eclipse.jgit.ssh.apache.source"
+      label="%featureName"
+      version="5.2.3.qualifier"
+      provider-name="%providerName">
+
+   <description url="http://www.eclipse.org/jgit/">
+     %description
+   </description>
+
+   <copyright>
+     %copyright
+   </copyright>
+
+   <license url="%licenseURL">
+     %license
+   </license>
+
+   <url>
+      <update label="%updateSiteName" url="http://download.eclipse.org/egit/updates"/>
+      <discovery label="%updateSiteName" url="http://download.eclipse.org/egit/updates"/>
+   </url>
+
+   <plugin
+         id="org.eclipse.jgit.ssh.apache.source"
+         download-size="0"
+         install-size="0"
+         version="0.0.0"
+         unpack="false"/>
+</feature>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/license.html b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/license.html
new file mode 100644
index 0000000..008b801
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/license.html
@@ -0,0 +1,189 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
+<title>Eclipse Foundation Software User Agreement</title>
+</head>
+
+<body lang="EN-US">
+	<h2>Eclipse Foundation Software User Agreement</h2>
+	<p>November 22, 2017</p>
+
+	<h3>Usage Of Content</h3>
+
+	<p>THE ECLIPSE FOUNDATION MAKES AVAILABLE SOFTWARE, DOCUMENTATION,
+		INFORMATION AND/OR OTHER MATERIALS FOR OPEN SOURCE PROJECTS
+		(COLLECTIVELY &quot;CONTENT&quot;). USE OF THE CONTENT IS GOVERNED BY
+		THE TERMS AND CONDITIONS OF THIS AGREEMENT AND/OR THE TERMS AND
+		CONDITIONS OF LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED
+		BELOW. BY USING THE CONTENT, YOU AGREE THAT YOUR USE OF THE CONTENT IS
+		GOVERNED BY THIS AGREEMENT AND/OR THE TERMS AND CONDITIONS OF ANY
+		APPLICABLE LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED
+		BELOW. IF YOU DO NOT AGREE TO THE TERMS AND CONDITIONS OF THIS
+		AGREEMENT AND THE TERMS AND CONDITIONS OF ANY APPLICABLE LICENSE
+		AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW, THEN YOU MAY NOT
+		USE THE CONTENT.</p>
+
+	<h3>Applicable Licenses</h3>
+
+	<p>
+		Unless otherwise indicated, all Content made available by the Eclipse
+		Foundation is provided to you under the terms and conditions of the
+		Eclipse Public License Version 2.0 (&quot;EPL&quot;). A copy of the
+		EPL is provided with this Content and is also available at <a
+			href="http://www.eclipse.org/legal/epl-2.0">http://www.eclipse.org/legal/epl-2.0</a>.
+		For purposes of the EPL, &quot;Program&quot; will mean the Content.
+	</p>
+
+	<p>Content includes, but is not limited to, source code, object
+		code, documentation and other files maintained in the Eclipse
+		Foundation source code repository (&quot;Repository&quot;) in software
+		modules (&quot;Modules&quot;) and made available as downloadable
+		archives (&quot;Downloads&quot;).</p>
+
+	<ul>
+		<li>Content may be structured and packaged into modules to
+			facilitate delivering, extending, and upgrading the Content. Typical
+			modules may include plug-ins (&quot;Plug-ins&quot;), plug-in
+			fragments (&quot;Fragments&quot;), and features
+			(&quot;Features&quot;).</li>
+		<li>Each Plug-in or Fragment may be packaged as a sub-directory
+			or JAR (Java&trade; ARchive) in a directory named
+			&quot;plugins&quot;.</li>
+		<li>A Feature is a bundle of one or more Plug-ins and/or
+			Fragments and associated material. Each Feature may be packaged as a
+			sub-directory in a directory named &quot;features&quot;. Within a
+			Feature, files named &quot;feature.xml&quot; may contain a list of
+			the names and version numbers of the Plug-ins and/or Fragments
+			associated with that Feature.</li>
+		<li>Features may also include other Features (&quot;Included
+			Features&quot;). Within a Feature, files named
+			&quot;feature.xml&quot; may contain a list of the names and version
+			numbers of Included Features.</li>
+	</ul>
+
+	<p>The terms and conditions governing Plug-ins and Fragments should
+		be contained in files named &quot;about.html&quot;
+		(&quot;Abouts&quot;). The terms and conditions governing Features and
+		Included Features should be contained in files named
+		&quot;license.html&quot; (&quot;Feature Licenses&quot;). Abouts and
+		Feature Licenses may be located in any directory of a Download or
+		Module including, but not limited to the following locations:</p>
+
+	<ul>
+		<li>The top-level (root) directory</li>
+		<li>Plug-in and Fragment directories</li>
+		<li>Inside Plug-ins and Fragments packaged as JARs</li>
+		<li>Sub-directories of the directory named &quot;src&quot; of
+			certain Plug-ins</li>
+		<li>Feature directories</li>
+	</ul>
+
+	<p>Note: if a Feature made available by the Eclipse Foundation is
+		installed using the Provisioning Technology (as defined below), you
+		must agree to a license (&quot;Feature Update License&quot;) during
+		the installation process. If the Feature contains Included Features,
+		the Feature Update License should either provide you with the terms
+		and conditions governing the Included Features or inform you where you
+		can locate them. Feature Update Licenses may be found in the
+		&quot;license&quot; property of files named
+		&quot;feature.properties&quot; found within a Feature. Such Abouts,
+		Feature Licenses, and Feature Update Licenses contain the terms and
+		conditions (or references to such terms and conditions) that govern
+		your use of the associated Content in that directory.</p>
+
+	<p>THE ABOUTS, FEATURE LICENSES, AND FEATURE UPDATE LICENSES MAY
+		REFER TO THE EPL OR OTHER LICENSE AGREEMENTS, NOTICES OR TERMS AND
+		CONDITIONS. SOME OF THESE OTHER LICENSE AGREEMENTS MAY INCLUDE (BUT
+		ARE NOT LIMITED TO):</p>
+
+	<ul>
+		<li>Eclipse Public License Version 1.0 (available at <a
+			href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>)
+		</li>
+		<li>Eclipse Distribution License Version 1.0 (available at <a
+			href="http://www.eclipse.org/licenses/edl-v10.html">http://www.eclipse.org/licenses/edl-v1.0.html</a>)
+		</li>
+		<li>Common Public License Version 1.0 (available at <a
+			href="http://www.eclipse.org/legal/cpl-v10.html">http://www.eclipse.org/legal/cpl-v10.html</a>)
+		</li>
+		<li>Apache Software License 1.1 (available at <a
+			href="http://www.apache.org/licenses/LICENSE">http://www.apache.org/licenses/LICENSE</a>)
+		</li>
+		<li>Apache Software License 2.0 (available at <a
+			href="http://www.apache.org/licenses/LICENSE-2.0">http://www.apache.org/licenses/LICENSE-2.0</a>)
+		</li>
+		<li>Mozilla Public License Version 1.1 (available at <a
+			href="http://www.mozilla.org/MPL/MPL-1.1.html">http://www.mozilla.org/MPL/MPL-1.1.html</a>)
+		</li>
+	</ul>
+
+	<p>IT IS YOUR OBLIGATION TO READ AND ACCEPT ALL SUCH TERMS AND
+		CONDITIONS PRIOR TO USE OF THE CONTENT. If no About, Feature License,
+		or Feature Update License is provided, please contact the Eclipse
+		Foundation to determine what terms and conditions govern that
+		particular Content.</p>
+
+
+	<h3>Use of Provisioning Technology</h3>
+
+	<p>
+		The Eclipse Foundation makes available provisioning software, examples
+		of which include, but are not limited to, p2 and the Eclipse Update
+		Manager (&quot;Provisioning Technology&quot;) for the purpose of
+		allowing users to install software, documentation, information and/or
+		other materials (collectively &quot;Installable Software&quot;). This
+		capability is provided with the intent of allowing such users to
+		install, extend and update Eclipse-based products. Information about
+		packaging Installable Software is available at <a
+			href="http://eclipse.org/equinox/p2/repository_packaging.html">http://eclipse.org/equinox/p2/repository_packaging.html</a>
+		(&quot;Specification&quot;).
+	</p>
+
+	<p>You may use Provisioning Technology to allow other parties to
+		install Installable Software. You shall be responsible for enabling
+		the applicable license agreements relating to the Installable Software
+		to be presented to, and accepted by, the users of the Provisioning
+		Technology in accordance with the Specification. By using Provisioning
+		Technology in such a manner and making it available in accordance with
+		the Specification, you further acknowledge your agreement to, and the
+		acquisition of all necessary rights to permit the following:</p>
+
+	<ol>
+		<li>A series of actions may occur (&quot;Provisioning
+			Process&quot;) in which a user may execute the Provisioning
+			Technology on a machine (&quot;Target Machine&quot;) with the intent
+			of installing, extending or updating the functionality of an
+			Eclipse-based product.</li>
+		<li>During the Provisioning Process, the Provisioning Technology
+			may cause third party Installable Software or a portion thereof to be
+			accessed and copied to the Target Machine.</li>
+		<li>Pursuant to the Specification, you will provide to the user
+			the terms and conditions that govern the use of the Installable
+			Software (&quot;Installable Software Agreement&quot;) and such
+			Installable Software Agreement shall be accessed from the Target
+			Machine in accordance with the Specification. Such Installable
+			Software Agreement must inform the user of the terms and conditions
+			that govern the Installable Software and must solicit acceptance by
+			the end user in the manner prescribed in such Installable Software
+			Agreement. Upon such indication of agreement by the user, the
+			provisioning Technology will complete installation of the Installable
+			Software.</li>
+	</ol>
+
+	<h3>Cryptography</h3>
+
+	<p>Content may contain encryption software. The country in which
+		you are currently may have restrictions on the import, possession, and
+		use, and/or re-export to another country, of encryption software.
+		BEFORE using any encryption software, please check the country's laws,
+		regulations and policies concerning the import, possession, or use,
+		and re-export of encryption software, to see if this is permitted.</p>
+
+	<p>
+		<small>Java and all Java-based trademarks are trademarks of
+			Oracle Corporation in the United States, other countries, or both.</small>
+	</p>
+</body>
+</html>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/pom.xml
new file mode 100644
index 0000000..6fd255a
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/pom.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Copyright (C) 2018 Thomas Wolf <thomas.wolf@paranor.ch>
+   and other copyright owners as documented in the project's IP log.
+
+   This program and the accompanying materials are made available
+   under the terms of the Eclipse Distribution License v1.0 which
+   accompanies this distribution, is reproduced below, and is
+   available at http://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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.eclipse.jgit</groupId>
+    <artifactId>jgit.tycho.parent</artifactId>
+    <version>5.2.3-SNAPSHOT</version>
+  </parent>
+
+  <groupId>org.eclipse.jgit.feature</groupId>
+  <artifactId>org.eclipse.jgit.ssh.apache.source</artifactId>
+  <packaging>eclipse-feature</packaging>
+
+  <name>JGit Apache MINA ssh Source Feature</name>
+
+</project>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/META-INF/MANIFEST.MF b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/META-INF/MANIFEST.MF
index d678701..b869d8d 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/META-INF/MANIFEST.MF
@@ -2,4 +2,4 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: JGit Target Platform Bundle
 Bundle-SymbolicName: org.eclipse.jgit.target
-Bundle-Version: 5.1.9.qualifier
+Bundle-Version: 5.2.3.qualifier
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9-staging.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10-staging.target
similarity index 76%
rename from org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9-staging.target
rename to org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10-staging.target
index bdabdff..98a2a6b 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9-staging.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10-staging.target
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
-<!-- generated with https://github.com/mbarbero/fr.obeo.releng.targetplatform -->
-<target name="jgit-4.9-staging" sequenceNumber="1536522948">
+<!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
+<target name="jgit-4.9-staging" sequenceNumber="1544018574">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.jetty.client" version="9.4.11.v20180605"/>
@@ -23,12 +23,12 @@
       <repository id="jetty-9.4.11" location="http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.11.v20180605"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="org.apache.ant" version="1.9.6.v201510161327"/>
-      <unit id="org.apache.ant.source" version="1.9.6.v201510161327"/>
+      <unit id="org.apache.ant" version="1.10.5.v20180808-0324"/>
+      <unit id="org.apache.ant.source" version="1.10.5.v20180808-0324"/>
       <unit id="org.apache.commons.codec" version="1.10.0.v20180409-1845"/>
       <unit id="org.apache.commons.codec.source" version="1.10.0.v20180409-1845"/>
-      <unit id="org.apache.commons.compress" version="1.15.0.v20180119-1613"/>
-      <unit id="org.apache.commons.compress.source" version="1.15.0.v20180119-1613"/>
+      <unit id="org.apache.commons.compress" version="1.18.0.v20181121-2221"/>
+      <unit id="org.apache.commons.compress.source" version="1.18.0.v20181121-2221"/>
       <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.5.v20180409-1525"/>
@@ -46,10 +46,14 @@
       <unit id="org.hamcrest.library.source" version="1.3.0.v20180524-2246"/>
       <unit id="javaewah" version="1.1.6.v20160919-1400"/>
       <unit id="javaewah.source" version="1.1.6.v20160919-1400"/>
-      <unit id="org.objenesis" version="1.0.0.v201505121915"/>
-      <unit id="org.objenesis.source" version="1.0.0.v201505121915"/>
-      <unit id="org.mockito" version="1.8.4.v201303031500"/>
-      <unit id="org.mockito.source" version="1.8.4.v201303031500"/>
+      <unit id="org.objenesis" version="2.6.0.v20180420-1519"/>
+      <unit id="org.objenesis.source" version="2.6.0.v20180420-1519"/>
+      <unit id="org.mockito" version="2.13.0.v20180426-1843"/>
+      <unit id="org.mockito.source" version="2.13.0.v20180426-1843"/>
+      <unit id="net.bytebuddy.byte-buddy" version="1.7.9.v20180420-1519"/>
+      <unit id="net.bytebuddy.byte-buddy.source" version="1.7.9.v20180420-1519"/>
+      <unit id="net.bytebuddy.byte-buddy-agent" version="1.7.9.v20180420-1519"/>
+      <unit id="net.bytebuddy.byte-buddy-agent.source" version="1.7.9.v20180420-1519"/>
       <unit id="com.google.gson" version="2.8.2.v20180104-1110"/>
       <unit id="com.google.gson.source" version="2.8.2.v20180104-1110"/>
       <unit id="com.jcraft.jsch" version="0.1.54.v20170116-1932"/>
@@ -66,11 +70,17 @@
       <unit id="org.slf4j.impl.log4j12.source" version="1.7.2.v20131105-2200"/>
       <unit id="com.jcraft.jzlib" version="1.1.1.v201205102305"/>
       <unit id="com.jcraft.jzlib.source" version="1.1.1.v201205102305"/>
-      <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20180905201904/repository"/>
+      <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.sshd.core" version="2.0.0.v20181102-1323"/>
+      <unit id="org.apache.sshd.core.source" version="2.0.0.v20181102-1323"/>
+      <unit id="org.apache.sshd.sftp" version="2.0.0.v20181102-1323"/>
+      <unit id="org.apache.sshd.sftp.source" version="2.0.0.v20181102-1323"/>
+      <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20181128170323/repository"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.osgi" version="0.0.0"/>
-      <repository location="http://download.eclipse.org/staging/2018-09/"/>
+      <repository location="http://download.eclipse.org/staging/2018-12/"/>
     </location>
   </locations>
 </target>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10-staging.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10-staging.tpd
new file mode 100644
index 0000000..07594ea
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10-staging.tpd
@@ -0,0 +1,8 @@
+target "jgit-4.9-staging" with source configurePhase
+
+include "projects/jetty-9.4.11.tpd"
+include "orbit/R20181128170323-2018-12.tpd"
+
+location "http://download.eclipse.org/staging/2018-12/" {
+	org.eclipse.osgi lazy
+}
\ No newline at end of file
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target
index 28ebdbf..10ff3be 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
-<!-- generated with https://github.com/mbarbero/fr.obeo.releng.targetplatform -->
-<target name="jgit-4.5" sequenceNumber="1536523000">
+<!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
+<target name="jgit-4.5" sequenceNumber="1544018556">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.jetty.client" version="9.4.11.v20180605"/>
@@ -23,12 +23,12 @@
       <repository id="jetty-9.4.11" location="http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.11.v20180605"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="org.apache.ant" version="1.9.6.v201510161327"/>
-      <unit id="org.apache.ant.source" version="1.9.6.v201510161327"/>
+      <unit id="org.apache.ant" version="1.10.5.v20180808-0324"/>
+      <unit id="org.apache.ant.source" version="1.10.5.v20180808-0324"/>
       <unit id="org.apache.commons.codec" version="1.10.0.v20180409-1845"/>
       <unit id="org.apache.commons.codec.source" version="1.10.0.v20180409-1845"/>
-      <unit id="org.apache.commons.compress" version="1.15.0.v20180119-1613"/>
-      <unit id="org.apache.commons.compress.source" version="1.15.0.v20180119-1613"/>
+      <unit id="org.apache.commons.compress" version="1.18.0.v20181121-2221"/>
+      <unit id="org.apache.commons.compress.source" version="1.18.0.v20181121-2221"/>
       <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.5.v20180409-1525"/>
@@ -46,10 +46,14 @@
       <unit id="org.hamcrest.library.source" version="1.3.0.v20180524-2246"/>
       <unit id="javaewah" version="1.1.6.v20160919-1400"/>
       <unit id="javaewah.source" version="1.1.6.v20160919-1400"/>
-      <unit id="org.objenesis" version="1.0.0.v201505121915"/>
-      <unit id="org.objenesis.source" version="1.0.0.v201505121915"/>
-      <unit id="org.mockito" version="1.8.4.v201303031500"/>
-      <unit id="org.mockito.source" version="1.8.4.v201303031500"/>
+      <unit id="org.objenesis" version="2.6.0.v20180420-1519"/>
+      <unit id="org.objenesis.source" version="2.6.0.v20180420-1519"/>
+      <unit id="org.mockito" version="2.13.0.v20180426-1843"/>
+      <unit id="org.mockito.source" version="2.13.0.v20180426-1843"/>
+      <unit id="net.bytebuddy.byte-buddy" version="1.7.9.v20180420-1519"/>
+      <unit id="net.bytebuddy.byte-buddy.source" version="1.7.9.v20180420-1519"/>
+      <unit id="net.bytebuddy.byte-buddy-agent" version="1.7.9.v20180420-1519"/>
+      <unit id="net.bytebuddy.byte-buddy-agent.source" version="1.7.9.v20180420-1519"/>
       <unit id="com.google.gson" version="2.8.2.v20180104-1110"/>
       <unit id="com.google.gson.source" version="2.8.2.v20180104-1110"/>
       <unit id="com.jcraft.jsch" version="0.1.54.v20170116-1932"/>
@@ -66,7 +70,13 @@
       <unit id="org.slf4j.impl.log4j12.source" version="1.7.2.v20131105-2200"/>
       <unit id="com.jcraft.jzlib" version="1.1.1.v201205102305"/>
       <unit id="com.jcraft.jzlib.source" version="1.1.1.v201205102305"/>
-      <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20180905201904/repository"/>
+      <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.sshd.core" version="2.0.0.v20181102-1323"/>
+      <unit id="org.apache.sshd.core.source" version="2.0.0.v20181102-1323"/>
+      <unit id="org.apache.sshd.sftp" version="2.0.0.v20181102-1323"/>
+      <unit id="org.apache.sshd.sftp.source" version="2.0.0.v20181102-1323"/>
+      <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20181128170323/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.5.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.tpd
index 8e65c98..f099b34 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.tpd
@@ -1,7 +1,7 @@
 target "jgit-4.5" with source configurePhase
 
 include "projects/jetty-9.4.11.tpd"
-include "orbit/R20180905201904-2018-09.tpd"
+include "orbit/R20181128170323-2018-12.tpd"
 
 location "http://download.eclipse.org/releases/mars/" {
 	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 8490e55..11fd7fd 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,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
-<!-- generated with https://github.com/mbarbero/fr.obeo.releng.targetplatform -->
-<target name="jgit-4.6" sequenceNumber="1536522989">
+<!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
+<target name="jgit-4.6" sequenceNumber="1544018561">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.jetty.client" version="9.4.11.v20180605"/>
@@ -23,12 +23,12 @@
       <repository id="jetty-9.4.11" location="http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.11.v20180605"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="org.apache.ant" version="1.9.6.v201510161327"/>
-      <unit id="org.apache.ant.source" version="1.9.6.v201510161327"/>
+      <unit id="org.apache.ant" version="1.10.5.v20180808-0324"/>
+      <unit id="org.apache.ant.source" version="1.10.5.v20180808-0324"/>
       <unit id="org.apache.commons.codec" version="1.10.0.v20180409-1845"/>
       <unit id="org.apache.commons.codec.source" version="1.10.0.v20180409-1845"/>
-      <unit id="org.apache.commons.compress" version="1.15.0.v20180119-1613"/>
-      <unit id="org.apache.commons.compress.source" version="1.15.0.v20180119-1613"/>
+      <unit id="org.apache.commons.compress" version="1.18.0.v20181121-2221"/>
+      <unit id="org.apache.commons.compress.source" version="1.18.0.v20181121-2221"/>
       <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.5.v20180409-1525"/>
@@ -46,10 +46,14 @@
       <unit id="org.hamcrest.library.source" version="1.3.0.v20180524-2246"/>
       <unit id="javaewah" version="1.1.6.v20160919-1400"/>
       <unit id="javaewah.source" version="1.1.6.v20160919-1400"/>
-      <unit id="org.objenesis" version="1.0.0.v201505121915"/>
-      <unit id="org.objenesis.source" version="1.0.0.v201505121915"/>
-      <unit id="org.mockito" version="1.8.4.v201303031500"/>
-      <unit id="org.mockito.source" version="1.8.4.v201303031500"/>
+      <unit id="org.objenesis" version="2.6.0.v20180420-1519"/>
+      <unit id="org.objenesis.source" version="2.6.0.v20180420-1519"/>
+      <unit id="org.mockito" version="2.13.0.v20180426-1843"/>
+      <unit id="org.mockito.source" version="2.13.0.v20180426-1843"/>
+      <unit id="net.bytebuddy.byte-buddy" version="1.7.9.v20180420-1519"/>
+      <unit id="net.bytebuddy.byte-buddy.source" version="1.7.9.v20180420-1519"/>
+      <unit id="net.bytebuddy.byte-buddy-agent" version="1.7.9.v20180420-1519"/>
+      <unit id="net.bytebuddy.byte-buddy-agent.source" version="1.7.9.v20180420-1519"/>
       <unit id="com.google.gson" version="2.8.2.v20180104-1110"/>
       <unit id="com.google.gson.source" version="2.8.2.v20180104-1110"/>
       <unit id="com.jcraft.jsch" version="0.1.54.v20170116-1932"/>
@@ -66,7 +70,13 @@
       <unit id="org.slf4j.impl.log4j12.source" version="1.7.2.v20131105-2200"/>
       <unit id="com.jcraft.jzlib" version="1.1.1.v201205102305"/>
       <unit id="com.jcraft.jzlib.source" version="1.1.1.v201205102305"/>
-      <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20180905201904/repository"/>
+      <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.sshd.core" version="2.0.0.v20181102-1323"/>
+      <unit id="org.apache.sshd.core.source" version="2.0.0.v20181102-1323"/>
+      <unit id="org.apache.sshd.sftp" version="2.0.0.v20181102-1323"/>
+      <unit id="org.apache.sshd.sftp.source" version="2.0.0.v20181102-1323"/>
+      <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20181128170323/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 44c846e..39f166c 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.11.tpd"
-include "orbit/R20180905201904-2018-09.tpd"
+include "orbit/R20181128170323-2018-12.tpd"
 
 location "http://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 a6651b8..061439c 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,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
-<!-- generated with https://github.com/mbarbero/fr.obeo.releng.targetplatform -->
-<target name="jgit-4.7" sequenceNumber="1536522970">
+<!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
+<target name="jgit-4.7" sequenceNumber="1544018548">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.jetty.client" version="9.4.11.v20180605"/>
@@ -23,12 +23,12 @@
       <repository id="jetty-9.4.11" location="http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.11.v20180605"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="org.apache.ant" version="1.9.6.v201510161327"/>
-      <unit id="org.apache.ant.source" version="1.9.6.v201510161327"/>
+      <unit id="org.apache.ant" version="1.10.5.v20180808-0324"/>
+      <unit id="org.apache.ant.source" version="1.10.5.v20180808-0324"/>
       <unit id="org.apache.commons.codec" version="1.10.0.v20180409-1845"/>
       <unit id="org.apache.commons.codec.source" version="1.10.0.v20180409-1845"/>
-      <unit id="org.apache.commons.compress" version="1.15.0.v20180119-1613"/>
-      <unit id="org.apache.commons.compress.source" version="1.15.0.v20180119-1613"/>
+      <unit id="org.apache.commons.compress" version="1.18.0.v20181121-2221"/>
+      <unit id="org.apache.commons.compress.source" version="1.18.0.v20181121-2221"/>
       <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.5.v20180409-1525"/>
@@ -46,10 +46,14 @@
       <unit id="org.hamcrest.library.source" version="1.3.0.v20180524-2246"/>
       <unit id="javaewah" version="1.1.6.v20160919-1400"/>
       <unit id="javaewah.source" version="1.1.6.v20160919-1400"/>
-      <unit id="org.objenesis" version="1.0.0.v201505121915"/>
-      <unit id="org.objenesis.source" version="1.0.0.v201505121915"/>
-      <unit id="org.mockito" version="1.8.4.v201303031500"/>
-      <unit id="org.mockito.source" version="1.8.4.v201303031500"/>
+      <unit id="org.objenesis" version="2.6.0.v20180420-1519"/>
+      <unit id="org.objenesis.source" version="2.6.0.v20180420-1519"/>
+      <unit id="org.mockito" version="2.13.0.v20180426-1843"/>
+      <unit id="org.mockito.source" version="2.13.0.v20180426-1843"/>
+      <unit id="net.bytebuddy.byte-buddy" version="1.7.9.v20180420-1519"/>
+      <unit id="net.bytebuddy.byte-buddy.source" version="1.7.9.v20180420-1519"/>
+      <unit id="net.bytebuddy.byte-buddy-agent" version="1.7.9.v20180420-1519"/>
+      <unit id="net.bytebuddy.byte-buddy-agent.source" version="1.7.9.v20180420-1519"/>
       <unit id="com.google.gson" version="2.8.2.v20180104-1110"/>
       <unit id="com.google.gson.source" version="2.8.2.v20180104-1110"/>
       <unit id="com.jcraft.jsch" version="0.1.54.v20170116-1932"/>
@@ -66,7 +70,13 @@
       <unit id="org.slf4j.impl.log4j12.source" version="1.7.2.v20131105-2200"/>
       <unit id="com.jcraft.jzlib" version="1.1.1.v201205102305"/>
       <unit id="com.jcraft.jzlib.source" version="1.1.1.v201205102305"/>
-      <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20180905201904/repository"/>
+      <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.sshd.core" version="2.0.0.v20181102-1323"/>
+      <unit id="org.apache.sshd.core.source" version="2.0.0.v20181102-1323"/>
+      <unit id="org.apache.sshd.sftp" version="2.0.0.v20181102-1323"/>
+      <unit id="org.apache.sshd.sftp.source" version="2.0.0.v20181102-1323"/>
+      <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20181128170323/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 b63955f..b1f0568 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.11.tpd"
-include "orbit/R20180905201904-2018-09.tpd"
+include "orbit/R20181128170323-2018-12.tpd"
 
 location "http://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 7adb7da..da66fb9 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,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
-<!-- generated with https://github.com/mbarbero/fr.obeo.releng.targetplatform -->
-<target name="jgit-4.8" sequenceNumber="1536522959">
+<!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
+<target name="jgit-4.8" sequenceNumber="1544018536">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.jetty.client" version="9.4.11.v20180605"/>
@@ -23,12 +23,12 @@
       <repository id="jetty-9.4.11" location="http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.11.v20180605"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="org.apache.ant" version="1.9.6.v201510161327"/>
-      <unit id="org.apache.ant.source" version="1.9.6.v201510161327"/>
+      <unit id="org.apache.ant" version="1.10.5.v20180808-0324"/>
+      <unit id="org.apache.ant.source" version="1.10.5.v20180808-0324"/>
       <unit id="org.apache.commons.codec" version="1.10.0.v20180409-1845"/>
       <unit id="org.apache.commons.codec.source" version="1.10.0.v20180409-1845"/>
-      <unit id="org.apache.commons.compress" version="1.15.0.v20180119-1613"/>
-      <unit id="org.apache.commons.compress.source" version="1.15.0.v20180119-1613"/>
+      <unit id="org.apache.commons.compress" version="1.18.0.v20181121-2221"/>
+      <unit id="org.apache.commons.compress.source" version="1.18.0.v20181121-2221"/>
       <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.5.v20180409-1525"/>
@@ -46,10 +46,14 @@
       <unit id="org.hamcrest.library.source" version="1.3.0.v20180524-2246"/>
       <unit id="javaewah" version="1.1.6.v20160919-1400"/>
       <unit id="javaewah.source" version="1.1.6.v20160919-1400"/>
-      <unit id="org.objenesis" version="1.0.0.v201505121915"/>
-      <unit id="org.objenesis.source" version="1.0.0.v201505121915"/>
-      <unit id="org.mockito" version="1.8.4.v201303031500"/>
-      <unit id="org.mockito.source" version="1.8.4.v201303031500"/>
+      <unit id="org.objenesis" version="2.6.0.v20180420-1519"/>
+      <unit id="org.objenesis.source" version="2.6.0.v20180420-1519"/>
+      <unit id="org.mockito" version="2.13.0.v20180426-1843"/>
+      <unit id="org.mockito.source" version="2.13.0.v20180426-1843"/>
+      <unit id="net.bytebuddy.byte-buddy" version="1.7.9.v20180420-1519"/>
+      <unit id="net.bytebuddy.byte-buddy.source" version="1.7.9.v20180420-1519"/>
+      <unit id="net.bytebuddy.byte-buddy-agent" version="1.7.9.v20180420-1519"/>
+      <unit id="net.bytebuddy.byte-buddy-agent.source" version="1.7.9.v20180420-1519"/>
       <unit id="com.google.gson" version="2.8.2.v20180104-1110"/>
       <unit id="com.google.gson.source" version="2.8.2.v20180104-1110"/>
       <unit id="com.jcraft.jsch" version="0.1.54.v20170116-1932"/>
@@ -66,7 +70,13 @@
       <unit id="org.slf4j.impl.log4j12.source" version="1.7.2.v20131105-2200"/>
       <unit id="com.jcraft.jzlib" version="1.1.1.v201205102305"/>
       <unit id="com.jcraft.jzlib.source" version="1.1.1.v201205102305"/>
-      <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20180905201904/repository"/>
+      <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.sshd.core" version="2.0.0.v20181102-1323"/>
+      <unit id="org.apache.sshd.core.source" version="2.0.0.v20181102-1323"/>
+      <unit id="org.apache.sshd.sftp" version="2.0.0.v20181102-1323"/>
+      <unit id="org.apache.sshd.sftp.source" version="2.0.0.v20181102-1323"/>
+      <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20181128170323/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 4e41ad9..959abae 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.11.tpd"
-include "orbit/R20180905201904-2018-09.tpd"
+include "orbit/R20181128170323-2018-12.tpd"
 
 location "http://download.eclipse.org/releases/photon/" {
 	org.eclipse.osgi lazy
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9-staging.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9-staging.tpd
deleted file mode 100644
index 6f963c7..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9-staging.tpd
+++ /dev/null
@@ -1,8 +0,0 @@
-target "jgit-4.9-staging" with source configurePhase
-
-include "projects/jetty-9.4.11.tpd"
-include "orbit/R20180905201904-2018-09.tpd"
-
-location "http://download.eclipse.org/staging/2018-09/" {
-	org.eclipse.osgi lazy
-}
\ No newline at end of file
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9-staging.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target
similarity index 74%
copy from org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9-staging.target
copy to org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target
index bdabdff..025bb1f 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9-staging.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
-<!-- generated with https://github.com/mbarbero/fr.obeo.releng.targetplatform -->
-<target name="jgit-4.9-staging" sequenceNumber="1536522948">
+<!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
+<target name="jgit-4.9" sequenceNumber="1544017335">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.jetty.client" version="9.4.11.v20180605"/>
@@ -23,12 +23,12 @@
       <repository id="jetty-9.4.11" location="http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.11.v20180605"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="org.apache.ant" version="1.9.6.v201510161327"/>
-      <unit id="org.apache.ant.source" version="1.9.6.v201510161327"/>
+      <unit id="org.apache.ant" version="1.10.5.v20180808-0324"/>
+      <unit id="org.apache.ant.source" version="1.10.5.v20180808-0324"/>
       <unit id="org.apache.commons.codec" version="1.10.0.v20180409-1845"/>
       <unit id="org.apache.commons.codec.source" version="1.10.0.v20180409-1845"/>
-      <unit id="org.apache.commons.compress" version="1.15.0.v20180119-1613"/>
-      <unit id="org.apache.commons.compress.source" version="1.15.0.v20180119-1613"/>
+      <unit id="org.apache.commons.compress" version="1.18.0.v20181121-2221"/>
+      <unit id="org.apache.commons.compress.source" version="1.18.0.v20181121-2221"/>
       <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.5.v20180409-1525"/>
@@ -46,10 +46,14 @@
       <unit id="org.hamcrest.library.source" version="1.3.0.v20180524-2246"/>
       <unit id="javaewah" version="1.1.6.v20160919-1400"/>
       <unit id="javaewah.source" version="1.1.6.v20160919-1400"/>
-      <unit id="org.objenesis" version="1.0.0.v201505121915"/>
-      <unit id="org.objenesis.source" version="1.0.0.v201505121915"/>
-      <unit id="org.mockito" version="1.8.4.v201303031500"/>
-      <unit id="org.mockito.source" version="1.8.4.v201303031500"/>
+      <unit id="org.objenesis" version="2.6.0.v20180420-1519"/>
+      <unit id="org.objenesis.source" version="2.6.0.v20180420-1519"/>
+      <unit id="org.mockito" version="2.13.0.v20180426-1843"/>
+      <unit id="org.mockito.source" version="2.13.0.v20180426-1843"/>
+      <unit id="net.bytebuddy.byte-buddy" version="1.7.9.v20180420-1519"/>
+      <unit id="net.bytebuddy.byte-buddy.source" version="1.7.9.v20180420-1519"/>
+      <unit id="net.bytebuddy.byte-buddy-agent" version="1.7.9.v20180420-1519"/>
+      <unit id="net.bytebuddy.byte-buddy-agent.source" version="1.7.9.v20180420-1519"/>
       <unit id="com.google.gson" version="2.8.2.v20180104-1110"/>
       <unit id="com.google.gson.source" version="2.8.2.v20180104-1110"/>
       <unit id="com.jcraft.jsch" version="0.1.54.v20170116-1932"/>
@@ -66,11 +70,17 @@
       <unit id="org.slf4j.impl.log4j12.source" version="1.7.2.v20131105-2200"/>
       <unit id="com.jcraft.jzlib" version="1.1.1.v201205102305"/>
       <unit id="com.jcraft.jzlib.source" version="1.1.1.v201205102305"/>
-      <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20180905201904/repository"/>
+      <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.sshd.core" version="2.0.0.v20181102-1323"/>
+      <unit id="org.apache.sshd.core.source" version="2.0.0.v20181102-1323"/>
+      <unit id="org.apache.sshd.sftp" version="2.0.0.v20181102-1323"/>
+      <unit id="org.apache.sshd.sftp.source" version="2.0.0.v20181102-1323"/>
+      <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20181128170323/repository"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.osgi" version="0.0.0"/>
-      <repository location="http://download.eclipse.org/staging/2018-09/"/>
+      <repository location="http://download.eclipse.org/releases/2018-09/"/>
     </location>
   </locations>
 </target>
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
new file mode 100644
index 0000000..0b6277d
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.tpd
@@ -0,0 +1,8 @@
+target "jgit-4.9" with source configurePhase
+
+include "projects/jetty-9.4.11.tpd"
+include "orbit/R20181128170323-2018-12.tpd"
+
+location "http://download.eclipse.org/releases/2018-09/" {
+	org.eclipse.osgi lazy
+}
\ No newline at end of file
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20181128170323-2018-12.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20181128170323-2018-12.tpd
new file mode 100644
index 0000000..d5a257d
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20181128170323-2018-12.tpd
@@ -0,0 +1,58 @@
+target "R20181128170323-2018-12" with source configurePhase
+// see http://download.eclipse.org/tools/orbit/downloads/
+
+location "http://download.eclipse.org/tools/orbit/downloads/drops/R20181128170323/repository" {
+	org.apache.ant [1.10.5.v20180808-0324,1.10.5.v20180808-0324]
+	org.apache.ant.source [1.10.5.v20180808-0324,1.10.5.v20180808-0324]
+	org.apache.commons.codec [1.10.0.v20180409-1845,1.10.0.v20180409-1845]
+	org.apache.commons.codec.source [1.10.0.v20180409-1845,1.10.0.v20180409-1845]
+	org.apache.commons.compress [1.18.0.v20181121-2221,1.18.0.v20181121-2221]
+	org.apache.commons.compress.source [1.18.0.v20181121-2221,1.18.0.v20181121-2221]
+	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.5.v20180409-1525,4.5.5.v20180409-1525]
+	org.apache.httpcomponents.httpclient.source [4.5.5.v20180409-1525,4.5.5.v20180409-1525]
+	org.apache.httpcomponents.httpcore [4.4.9.v20180409-1525,4.4.9.v20180409-1525]
+	org.apache.httpcomponents.httpcore.source [4.4.9.v20180409-1525,4.4.9.v20180409-1525]
+	org.apache.log4j [1.2.15.v201012070815,1.2.15.v201012070815]
+	org.apache.log4j.source [1.2.15.v201012070815,1.2.15.v201012070815]
+	org.kohsuke.args4j [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
+	org.kohsuke.args4j.source [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
+	org.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]
+	org.hamcrest.library [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
+	org.hamcrest.library.source [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
+	javaewah [1.1.6.v20160919-1400,1.1.6.v20160919-1400]
+	javaewah.source [1.1.6.v20160919-1400,1.1.6.v20160919-1400]
+	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.mockito [2.13.0.v20180426-1843,2.13.0.v20180426-1843]
+	org.mockito.source [2.13.0.v20180426-1843,2.13.0.v20180426-1843]
+	net.bytebuddy.byte-buddy [1.7.9.v20180420-1519,1.7.9.v20180420-1519]
+	net.bytebuddy.byte-buddy.source [1.7.9.v20180420-1519,1.7.9.v20180420-1519]
+	net.bytebuddy.byte-buddy-agent [1.7.9.v20180420-1519,1.7.9.v20180420-1519]
+	net.bytebuddy.byte-buddy-agent.source [1.7.9.v20180420-1519,1.7.9.v20180420-1519]
+	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.54.v20170116-1932,0.1.54.v20170116-1932]
+	com.jcraft.jsch.source [0.1.54.v20170116-1932,0.1.54.v20170116-1932]
+	org.junit [4.12.0.v201504281640,4.12.0.v201504281640]
+	org.junit.source [4.12.0.v201504281640,4.12.0.v201504281640]
+	javax.servlet [3.1.0.v201410161800,3.1.0.v201410161800]
+	javax.servlet.source [3.1.0.v201410161800,3.1.0.v201410161800]
+	org.tukaani.xz [1.6.0.v20170629-1752,1.6.0.v20170629-1752]
+	org.tukaani.xz.source [1.6.0.v20170629-1752,1.6.0.v20170629-1752]
+	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]
+	com.jcraft.jzlib [1.1.1.v201205102305,1.1.1.v201205102305]
+	com.jcraft.jzlib.source [1.1.1.v201205102305,1.1.1.v201205102305]
+	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.sshd.core [2.0.0.v20181102-1323,2.0.0.v20181102-1323]
+	org.apache.sshd.core.source [2.0.0.v20181102-1323,2.0.0.v20181102-1323]
+	org.apache.sshd.sftp [2.0.0.v20181102-1323,2.0.0.v20181102-1323]
+	org.apache.sshd.sftp.source [2.0.0.v20181102-1323,2.0.0.v20181102-1323]
+}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml
index f25796f..dba70d3 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml
@@ -49,7 +49,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>5.1.9-SNAPSHOT</version>
+    <version>5.2.3-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.target</artifactId>
@@ -82,4 +82,4 @@
       </plugin>
     </plugins>
   </build>
-</project>
\ No newline at end of file
+</project>
diff --git a/org.eclipse.jgit.packaging/pom.xml b/org.eclipse.jgit.packaging/pom.xml
index d763cb0..bc069c6 100644
--- a/org.eclipse.jgit.packaging/pom.xml
+++ b/org.eclipse.jgit.packaging/pom.xml
@@ -53,7 +53,7 @@
 
   <groupId>org.eclipse.jgit</groupId>
   <artifactId>jgit.tycho.parent</artifactId>
-  <version>5.1.9-SNAPSHOT</version>
+  <version>5.2.3-SNAPSHOT</version>
   <packaging>pom</packaging>
 
   <name>JGit Tycho Parent</name>
@@ -79,10 +79,12 @@
     <module>org.eclipse.jgit.target</module>
     <module>org.eclipse.jgit.feature</module>
     <module>org.eclipse.jgit.http.apache.feature</module>
+    <module>org.eclipse.jgit.ssh.apache.feature</module>
     <module>org.eclipse.jgit.lfs.feature</module>
     <module>org.eclipse.jgit.pgm.feature</module>
     <module>org.eclipse.jgit.source.feature</module>
     <module>org.eclipse.jgit.pgm.source.feature</module>
+    <module>org.eclipse.jgit.ssh.apache.source.feature</module>
     <module>org.eclipse.jgit.junit.feature</module>
     <module>org.eclipse.jgit.repository</module>
   </modules>
@@ -115,6 +117,12 @@
       <version>${project.version}</version>
       <classifier>sources</classifier>
     </dependency>
+    <dependency>
+      <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit.ssh.apache</artifactId>
+      <version>${project.version}</version>
+      <classifier>sources</classifier>
+    </dependency>
   </dependencies>
 
   <build>
diff --git a/org.eclipse.jgit.pgm.test/.classpath b/org.eclipse.jgit.pgm.test/.classpath
index b26f4c4..1334739 100644
--- a/org.eclipse.jgit.pgm.test/.classpath
+++ b/org.eclipse.jgit.pgm.test/.classpath
@@ -1,7 +1,15 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-	<classpathentry kind="src" path="tst"/>
-	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="src" path="tst">
+		<attributes>
+			<attribute name="test" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="src" path="src">
+		<attributes>
+			<attribute name="test" value="true"/>
+		</attributes>
+	</classpathentry>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
 	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
 	<classpathentry kind="output" path="bin"/>
diff --git a/org.eclipse.jgit.pgm.test/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.pgm.test/.settings/org.eclipse.jdt.core.prefs
index 794592d..2ca78ff 100644
--- a/org.eclipse.jgit.pgm.test/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.pgm.test/.settings/org.eclipse.jdt.core.prefs
@@ -91,7 +91,7 @@
 org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
 org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
 org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning
-org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error
 org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
diff --git a/org.eclipse.jgit.pgm.test/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.pgm.test/.settings/org.eclipse.mylyn.team.ui.prefs
index 0cba949..2fca432 100644
--- a/org.eclipse.jgit.pgm.test/.settings/org.eclipse.mylyn.team.ui.prefs
+++ b/org.eclipse.jgit.pgm.test/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -1,3 +1,3 @@
 #Tue Jul 19 20:11:28 CEST 2011
-commit.comment.template=${task.description} \n\nBug\: ${task.key}
+commit.comment.template=${task.description}\n\nBug\: ${task.key}
 eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF
index 97a7ba2..90398a9 100644
--- a/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF
@@ -3,28 +3,28 @@
 Bundle-Name: %plugin_name
 Automatic-Module-Name: org.eclipse.jgit.pgm.test
 Bundle-SymbolicName: org.eclipse.jgit.pgm.test
-Bundle-Version: 5.1.9.qualifier
+Bundle-Version: 5.2.3.qualifier
 Bundle-Vendor: %provider_name
 Bundle-Localization: plugin
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Import-Package: org.eclipse.jgit.api;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.api.errors;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.diff;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.dircache;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.internal.storage.file;version="5.1.9",
- org.eclipse.jgit.junit;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lib;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.merge;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.pgm;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.pgm.internal;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.pgm.opt;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.revwalk;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.storage.file;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.transport;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.treewalk;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.util;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.util.io;version="[5.1.9,5.2.0)",
+Import-Package: org.eclipse.jgit.api;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.api.errors;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.diff;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.dircache;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="5.2.3",
+ org.eclipse.jgit.junit;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lib;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.merge;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.pgm;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.pgm.internal;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.pgm.opt;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.revwalk;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.storage.file;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.transport;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.treewalk;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.util;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.util.io;version="[5.2.3,5.3.0)",
  org.hamcrest.core;bundle-version="[1.1.0,2.0.0)",
  org.junit;version="[4.12,5.0.0)",
  org.junit.rules;version="[4.12,5.0.0)",
diff --git a/org.eclipse.jgit.pgm.test/pom.xml b/org.eclipse.jgit.pgm.test/pom.xml
index d5641be..ced7168 100644
--- a/org.eclipse.jgit.pgm.test/pom.xml
+++ b/org.eclipse.jgit.pgm.test/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>5.1.9-SNAPSHOT</version>
+    <version>5.2.3-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.pgm.test</artifactId>
@@ -109,7 +109,7 @@
       <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
         <configuration>
-          <argLine>@{argLine} -Xmx512m -Djava.io.tmpdir=${project.build.directory}</argLine>
+          <argLine>-Djava.io.tmpdir=${project.build.directory}</argLine>
         </configuration>
       </plugin>
     </plugins>
diff --git a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java
index 81875f1..9ad22dd 100644
--- a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java
+++ b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java
@@ -42,11 +42,13 @@
  */
 package org.eclipse.jgit.pgm;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertNull;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
+import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -153,7 +155,8 @@
 
 	@Override
 	PrintWriter createErrorWriter() {
-		return new PrintWriter(result.err);
+		return new PrintWriter(new OutputStreamWriter(
+				result.err, UTF_8));
 	}
 
 	@Override
@@ -249,19 +252,19 @@
 		}
 
 		public String outString() {
-			return out.toString();
+			return new String(out.toByteArray(), UTF_8);
 		}
 
 		public List<String> outLines() {
-			return IO.readLines(out.toString());
+			return IO.readLines(outString());
 		}
 
 		public String errString() {
-			return err.toString();
+			return new String(err.toByteArray(), UTF_8);
 		}
 
 		public List<String> errLines() {
-			return IO.readLines(err.toString());
+			return IO.readLines(errString());
 		}
 	}
 
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ProxyConfigTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ProxyConfigTest.java
index 40a223d..42530f3 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ProxyConfigTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ProxyConfigTest.java
@@ -37,6 +37,7 @@
  */
 package org.eclipse.jgit.pgm;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 
 import java.io.ByteArrayOutputStream;
@@ -47,7 +48,6 @@
 import java.util.List;
 import java.util.Map;
 
-import org.eclipse.jgit.lib.Constants;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -204,7 +204,7 @@
 			while ((length = inputStream.read(buffer)) != -1) {
 				result.write(buffer, 0, length);
 			}
-			return result.toString(Constants.CHARACTER_ENCODING);
+			return result.toString(UTF_8.name());
 		}
 	}
 }
diff --git a/org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.core.prefs
index 13c32a6..ef6f5e7 100644
--- a/org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.core.prefs
@@ -91,7 +91,7 @@
 org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
 org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
 org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning
-org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error
 org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
diff --git a/org.eclipse.jgit.pgm/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.pgm/.settings/org.eclipse.mylyn.team.ui.prefs
index 0cba949..2fca432 100644
--- a/org.eclipse.jgit.pgm/.settings/org.eclipse.mylyn.team.ui.prefs
+++ b/org.eclipse.jgit.pgm/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -1,3 +1,3 @@
 #Tue Jul 19 20:11:28 CEST 2011
-commit.comment.template=${task.description} \n\nBug\: ${task.key}
+commit.comment.template=${task.description}\n\nBug\: ${task.key}
 eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.pgm/BUILD b/org.eclipse.jgit.pgm/BUILD
index ddc84be..1a115dd 100644
--- a/org.eclipse.jgit.pgm/BUILD
+++ b/org.eclipse.jgit.pgm/BUILD
@@ -7,6 +7,7 @@
     deps = [
         ":services",
         "//lib:args4j",
+        "//lib:commons-logging",
         "//lib:httpclient",
         "//lib:httpcore",
         "//lib:jetty-http",
@@ -21,6 +22,7 @@
         "//org.eclipse.jgit:jgit",
         "//org.eclipse.jgit.lfs:jgit-lfs",
         "//org.eclipse.jgit.lfs.server:jgit-lfs-server",
+        "//org.eclipse.jgit.ssh.apache:ssh-apache",
         "//org.eclipse.jgit.ui:ui",
     ],
 )
diff --git a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
index ce4d6ef..e10a772 100644
--- a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
 Bundle-Name: %plugin_name
 Automatic-Module-Name: org.eclipse.jgit.pgm
 Bundle-SymbolicName: org.eclipse.jgit.pgm
-Bundle-Version: 5.1.9.qualifier
+Bundle-Version: 5.2.3.qualifier
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
 Bundle-Localization: plugin
@@ -28,49 +28,50 @@
  org.eclipse.jetty.util.log;version="[9.4.5,10.0.0)",
  org.eclipse.jetty.util.security;version="[9.4.5,10.0.0)",
  org.eclipse.jetty.util.thread;version="[9.4.5,10.0.0)",
- org.eclipse.jgit.api;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.api.errors;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.archive;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.awtui;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.blame;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.diff;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.dircache;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.errors;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.gitrepo;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.internal.ketch;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.internal.storage.io;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.internal.storage.pack;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.internal.storage.reftable;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.internal.storage.reftree;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lfs;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lfs.lib;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lfs.server;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lfs.server.fs;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lfs.server.s3;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lib;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.merge;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.nls;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.notes;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.revplot;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.revwalk;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.revwalk.filter;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.storage.file;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.storage.pack;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.transport;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.transport.http.apache;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.transport.resolver;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.treewalk;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.treewalk.filter;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.util;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.util.io;version="[5.1.9,5.2.0)",
+ org.eclipse.jgit.api;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.api.errors;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.archive;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.awtui;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.blame;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.diff;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.dircache;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.errors;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.gitrepo;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal.ketch;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal.storage.io;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal.storage.reftable;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal.storage.reftree;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lfs;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lfs.lib;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lfs.server;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lfs.server.fs;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lfs.server.s3;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lib;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.merge;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.nls;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.notes;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.revplot;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.revwalk;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.revwalk.filter;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.storage.file;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.storage.pack;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.transport;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.transport.http.apache;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.transport.resolver;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.transport.sshd;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.treewalk;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.treewalk.filter;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.util;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.util.io;version="[5.2.3,5.3.0)",
  org.kohsuke.args4j;version="[2.33.0,3.0.0)",
  org.kohsuke.args4j.spi;version="[2.33.0,3.0.0)"
-Export-Package: org.eclipse.jgit.console;version="5.1.9";
+Export-Package: org.eclipse.jgit.console;version="5.2.3";
   uses:="org.eclipse.jgit.transport,
    org.eclipse.jgit.util",
- org.eclipse.jgit.pgm;version="5.1.9";
+ org.eclipse.jgit.pgm;version="5.2.3";
   uses:="org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.pgm.opt,
@@ -81,11 +82,11 @@
    org.eclipse.jgit.treewalk,
    javax.swing,
    org.eclipse.jgit.transport",
- org.eclipse.jgit.pgm.debug;version="5.1.9";
+ org.eclipse.jgit.pgm.debug;version="5.2.3";
   uses:="org.eclipse.jgit.util.io,
    org.eclipse.jgit.pgm",
- org.eclipse.jgit.pgm.internal;version="5.1.9";x-friends:="org.eclipse.jgit.pgm.test,org.eclipse.jgit.test",
- org.eclipse.jgit.pgm.opt;version="5.1.9";
+ org.eclipse.jgit.pgm.internal;version="5.2.3";x-friends:="org.eclipse.jgit.pgm.test,org.eclipse.jgit.test",
+ org.eclipse.jgit.pgm.opt;version="5.2.3";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
    org.kohsuke.args4j.spi,
diff --git a/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF
index 2c4fba8..9831e13 100644
--- a/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.pgm - Sources
 Bundle-SymbolicName: org.eclipse.jgit.pgm.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 5.1.9.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.pgm;version="5.1.9.qualifier";roots="."
+Bundle-Version: 5.2.3.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.pgm;version="5.2.3.qualifier";roots="."
diff --git a/org.eclipse.jgit.pgm/pom.xml b/org.eclipse.jgit.pgm/pom.xml
index 98f579a..81ef1b3 100644
--- a/org.eclipse.jgit.pgm/pom.xml
+++ b/org.eclipse.jgit.pgm/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>5.1.9-SNAPSHOT</version>
+    <version>5.2.3-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.pgm</artifactId>
@@ -101,6 +101,12 @@
     </dependency>
 
     <dependency>
+      <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit.ssh.apache</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <dependency>
       <groupId>org.apache.httpcomponents</groupId>
       <artifactId>httpclient</artifactId>
     </dependency>
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 8ff9c6b..d7d895a 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
@@ -402,6 +402,7 @@
 usage_showTimeInMilliseconds=Show mtime in milliseconds
 usage_squash=Squash commits as if a real merge happened, but do not make a commit or move the HEAD.
 usage_srcPrefix=show the source prefix instead of "a/"
+usage_sshDriver=Selects the built-in ssh library to use, JSch or Apache MINA sshd.
 usage_symbolicVersionForTheProject=Symbolic version for the project
 usage_tags=fetch all tags
 usage_notags=do not fetch tags
@@ -411,7 +412,7 @@
 usage_updateRef=reference to update
 usage_updateRemoteRefsFromAnotherRepository=Update remote refs from another repository
 usage_useNameInsteadOfOriginToTrackUpstream=use <name> instead of 'origin' to track upstream
-usage_checkoutBranchAfterClone=checkout named branch instead of remotes's HEAD
+usage_checkoutBranchAfterClone=checkout named branch instead of remote's HEAD
 usage_viewCommitHistory=View commit history
-usage_orphan=Create a new orphan branch. The first commit made on this new branch will have no parents amd it will be the root of a new history totally disconnected from other branches and commits.
+usage_orphan=Create a new orphan branch. The first commit made on this new branch will have no parents and it will be the root of a new history totally disconnected from other branches and commits.
 usernameFor=Username for {0}:
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java
index 7e5b545..c4b4018 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java
@@ -44,6 +44,9 @@
 
 package org.eclipse.jgit.pgm;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_SECTION_I18N;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_LOG_OUTPUT_ENCODING;
 import static org.eclipse.jgit.lib.Constants.R_HEADS;
 import static org.eclipse.jgit.lib.Constants.R_REMOTES;
 import static org.eclipse.jgit.lib.Constants.R_TAGS;
@@ -56,14 +59,20 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
+import java.nio.charset.Charset;
 import java.text.MessageFormat;
 import java.util.ResourceBundle;
 
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.pgm.internal.CLIText;
+import org.eclipse.jgit.pgm.internal.SshDriver;
 import org.eclipse.jgit.pgm.opt.CmdLineParser;
 import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.SshSessionFactory;
+import org.eclipse.jgit.transport.sshd.DefaultProxyDataFactory;
+import org.eclipse.jgit.transport.sshd.JGitKeyCache;
+import org.eclipse.jgit.transport.sshd.SshdSessionFactory;
 import org.eclipse.jgit.util.io.ThrowingPrintWriter;
 import org.kohsuke.args4j.CmdLineException;
 import org.kohsuke.args4j.Option;
@@ -85,6 +94,9 @@
 	@Option(name = "--help", usage = "usage_displayThisHelpText", aliases = { "-h" })
 	private boolean help;
 
+	@Option(name = "--ssh", usage = "usage_sshDriver")
+	private SshDriver sshDriver = SshDriver.JSCH;
+
 	/**
 	 * Input stream, typically this is standard input.
 	 *
@@ -168,6 +180,30 @@
 	}
 
 	/**
+	 * Get the log output encoding specified in the repository's
+	 * {@code i18n.logOutputEncoding} configuration.
+	 *
+	 * @param repository
+	 *            the repository.
+	 * @return Charset corresponding to {@code i18n.logOutputEncoding}, or
+	 *         {@code UTF_8}.
+	 */
+	private Charset getLogOutputEncodingCharset(Repository repository) {
+		if (repository != null) {
+			String logOutputEncoding = repository.getConfig().getString(
+					CONFIG_SECTION_I18N, null, CONFIG_KEY_LOG_OUTPUT_ENCODING);
+			if (logOutputEncoding != null) {
+				try {
+					return Charset.forName(logOutputEncoding);
+				} catch (IllegalArgumentException e) {
+					throw die(CLIText.get().cannotCreateOutputStream);
+				}
+			}
+		}
+		return UTF_8;
+	}
+
+	/**
 	 * Initialize the command to work with a repository.
 	 *
 	 * @param repository
@@ -177,32 +213,18 @@
 	 *            {@code repository} is null.
 	 */
 	protected void init(Repository repository, String gitDir) {
-		try {
-			final String outputEncoding = repository != null ? repository
-					.getConfig().getString("i18n", null, "logOutputEncoding") : null; //$NON-NLS-1$ //$NON-NLS-2$
-			if (ins == null)
-				ins = new FileInputStream(FileDescriptor.in);
-			if (outs == null)
-				outs = new FileOutputStream(FileDescriptor.out);
-			if (errs == null)
-				errs = new FileOutputStream(FileDescriptor.err);
-			BufferedWriter outbufw;
-			if (outputEncoding != null)
-				outbufw = new BufferedWriter(new OutputStreamWriter(outs,
-						outputEncoding));
-			else
-				outbufw = new BufferedWriter(new OutputStreamWriter(outs));
-			outw = new ThrowingPrintWriter(outbufw);
-			BufferedWriter errbufw;
-			if (outputEncoding != null)
-				errbufw = new BufferedWriter(new OutputStreamWriter(errs,
-						outputEncoding));
-			else
-				errbufw = new BufferedWriter(new OutputStreamWriter(errs));
-			errw = new ThrowingPrintWriter(errbufw);
-		} catch (IOException e) {
-			throw die(CLIText.get().cannotCreateOutputStream);
-		}
+		Charset charset = getLogOutputEncodingCharset(repository);
+
+		if (ins == null)
+			ins = new FileInputStream(FileDescriptor.in);
+		if (outs == null)
+			outs = new FileOutputStream(FileDescriptor.out);
+		if (errs == null)
+			errs = new FileOutputStream(FileDescriptor.err);
+		outw = new ThrowingPrintWriter(new BufferedWriter(
+				new OutputStreamWriter(outs, charset)));
+		errw = new ThrowingPrintWriter(new BufferedWriter(
+				new OutputStreamWriter(errs, charset)));
 
 		if (repository != null && repository.getDirectory() != null) {
 			db = repository;
@@ -225,6 +247,20 @@
 	 */
 	public final void execute(String[] args) throws Exception {
 		parseArguments(args);
+		switch (sshDriver) {
+		case APACHE: {
+			SshdSessionFactory factory = new SshdSessionFactory(
+					new JGitKeyCache(), new DefaultProxyDataFactory());
+			Runtime.getRuntime()
+					.addShutdownHook(new Thread(() -> factory.close()));
+			SshSessionFactory.setInstance(factory);
+			break;
+		}
+		case JSCH:
+		default:
+			SshSessionFactory.setInstance(null);
+			break;
+		}
 		run();
 	}
 
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java
index bb51b50..300a01d 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java
@@ -474,7 +474,7 @@
 	}
 
 	/** Utility to help us identify unique lines in a file. */
-	private class Line {
+	private static class Line {
 		private final RawText txt;
 
 		private final int pos;
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/SshDriver.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/SshDriver.java
new file mode 100644
index 0000000..7d0423b
--- /dev/null
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/SshDriver.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.pgm.internal;
+
+/**
+ * Simple enumeration for the available built-in ssh clients.
+ */
+public enum SshDriver {
+
+	/** Default client: use JSch. */
+	JSCH,
+
+	/** Use the Apache MINA sshd client from org.eclipse.jgit.ssh.apache. */
+	APACHE;
+
+}
diff --git a/org.eclipse.jgit.ssh.apache.test/.classpath b/org.eclipse.jgit.ssh.apache.test/.classpath
new file mode 100644
index 0000000..f08af0a
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/.classpath
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+	<classpathentry kind="src" path="tst">
+		<attributes>
+			<attribute name="test" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/org.eclipse.jgit.ssh.apache.test/.gitignore b/org.eclipse.jgit.ssh.apache.test/.gitignore
new file mode 100644
index 0000000..934e0e0
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/.gitignore
@@ -0,0 +1,2 @@
+/bin
+/target
diff --git a/org.eclipse.jgit.ssh.apache.test/.project b/org.eclipse.jgit.ssh.apache.test/.project
new file mode 100644
index 0000000..0aafb73
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.eclipse.jgit.ssh.apache.test</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.ManifestBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.SchemaBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/org.eclipse.jgit.ssh.apache.test/.settings/org.eclipse.core.resources.prefs b/org.eclipse.jgit.ssh.apache.test/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..f77db3b
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,3 @@
+#Sat Dec 20 21:21:24 CET 2008
+eclipse.preferences.version=1
+encoding/<project>=UTF-8
diff --git a/org.eclipse.jgit.ssh.apache.test/.settings/org.eclipse.core.runtime.prefs b/org.eclipse.jgit.ssh.apache.test/.settings/org.eclipse.core.runtime.prefs
new file mode 100644
index 0000000..9f733ee
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/.settings/org.eclipse.core.runtime.prefs
@@ -0,0 +1,3 @@
+#Mon Mar 24 18:55:56 EDT 2008
+eclipse.preferences.version=1
+line.separator=\n
diff --git a/org.eclipse.jgit.ssh.apache.test/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.ssh.apache.test/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..794592d
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,399 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
+org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
+org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
+org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
+org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.doc.comment.support=enabled
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.autoboxing=warning
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=error
+org.eclipse.jdt.core.compiler.problem.deadCode=error
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=warning
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=warning
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
+org.eclipse.jdt.core.compiler.problem.fieldHiding=warning
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=error
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=error
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=enabled
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=error
+org.eclipse.jdt.core.compiler.problem.invalidJavadoc=error
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=private
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=error
+org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=error
+org.eclipse.jdt.core.compiler.problem.missingJavadocComments=ignore
+org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag
+org.eclipse.jdt.core.compiler.problem.missingJavadocTags=error
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=error
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=error
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
+org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
+org.eclipse.jdt.core.compiler.problem.nullReference=error
+org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=error
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning
+org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore
+org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning
+org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning
+org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=error
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=error
+org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=error
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedImport=error
+org.eclipse.jdt.core.compiler.problem.unusedLabel=error
+org.eclipse.jdt.core.compiler.problem.unusedLocal=error
+org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning
+org.eclipse.jdt.core.compiler.problem.unusedParameter=warning
+org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=error
+org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=error
+org.eclipse.jdt.core.compiler.source=1.8
+org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_assignment=0
+org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
+org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
+org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
+org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
+org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
+org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_after_package=1
+org.eclipse.jdt.core.formatter.blank_lines_before_field=1
+org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
+org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
+org.eclipse.jdt.core.formatter.blank_lines_before_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
+org.eclipse.jdt.core.formatter.blank_lines_before_package=0
+org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
+org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
+org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
+org.eclipse.jdt.core.formatter.comment.format_block_comments=true
+org.eclipse.jdt.core.formatter.comment.format_comments=true
+org.eclipse.jdt.core.formatter.comment.format_header=false
+org.eclipse.jdt.core.formatter.comment.format_html=true
+org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
+org.eclipse.jdt.core.formatter.comment.format_line_comments=true
+org.eclipse.jdt.core.formatter.comment.format_source_code=true
+org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
+org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
+org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
+org.eclipse.jdt.core.formatter.comment.line_length=80
+org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
+org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
+org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
+org.eclipse.jdt.core.formatter.compact_else_if=true
+org.eclipse.jdt.core.formatter.continuation_indentation=2
+org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
+org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
+org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
+org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
+org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
+org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_empty_lines=false
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
+org.eclipse.jdt.core.formatter.indentation.size=4
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
+org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
+org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.join_lines_in_comments=true
+org.eclipse.jdt.core.formatter.join_wrapped_lines=true
+org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.lineSplit=80
+org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
+org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
+org.eclipse.jdt.core.formatter.tabulation.char=tab
+org.eclipse.jdt.core.formatter.tabulation.size=4
+org.eclipse.jdt.core.formatter.use_on_off_tags=true
+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
+org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
+org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
diff --git a/org.eclipse.jgit.ssh.apache.test/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.jgit.ssh.apache.test/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 0000000..fef3713
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,66 @@
+eclipse.preferences.version=1
+editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+formatter_profile=_JGit Format
+formatter_settings_version=12
+org.eclipse.jdt.ui.ignorelowercasenames=true
+org.eclipse.jdt.ui.importorder=java;javax;org;com;
+org.eclipse.jdt.ui.ondemandthreshold=99
+org.eclipse.jdt.ui.staticondemandthreshold=99
+org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8"?><templates/>
+sp_cleanup.add_default_serial_version_id=true
+sp_cleanup.add_generated_serial_version_id=false
+sp_cleanup.add_missing_annotations=true
+sp_cleanup.add_missing_deprecated_annotations=true
+sp_cleanup.add_missing_methods=false
+sp_cleanup.add_missing_nls_tags=false
+sp_cleanup.add_missing_override_annotations=true
+sp_cleanup.add_missing_override_annotations_interface_methods=true
+sp_cleanup.add_serial_version_id=false
+sp_cleanup.always_use_blocks=true
+sp_cleanup.always_use_parentheses_in_expressions=false
+sp_cleanup.always_use_this_for_non_static_field_access=false
+sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_functional_interfaces=false
+sp_cleanup.convert_to_enhanced_for_loop=false
+sp_cleanup.correct_indentation=false
+sp_cleanup.format_source_code=true
+sp_cleanup.format_source_code_changes_only=true
+sp_cleanup.insert_inferred_type_arguments=false
+sp_cleanup.make_local_variable_final=false
+sp_cleanup.make_parameters_final=false
+sp_cleanup.make_private_fields_final=true
+sp_cleanup.make_type_abstract_if_missing_method=false
+sp_cleanup.make_variable_declarations_final=false
+sp_cleanup.never_use_blocks=false
+sp_cleanup.never_use_parentheses_in_expressions=true
+sp_cleanup.on_save_use_additional_actions=true
+sp_cleanup.organize_imports=false
+sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_type_arguments=true
+sp_cleanup.remove_trailing_whitespaces=true
+sp_cleanup.remove_trailing_whitespaces_all=true
+sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=true
+sp_cleanup.remove_unused_imports=false
+sp_cleanup.remove_unused_local_variables=false
+sp_cleanup.remove_unused_private_fields=true
+sp_cleanup.remove_unused_private_members=false
+sp_cleanup.remove_unused_private_methods=true
+sp_cleanup.remove_unused_private_types=true
+sp_cleanup.sort_members=false
+sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
+sp_cleanup.use_blocks=false
+sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=false
+sp_cleanup.use_parentheses_in_expressions=false
+sp_cleanup.use_this_for_non_static_field_access=false
+sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+sp_cleanup.use_this_for_non_static_method_access=false
+sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/org.eclipse.jgit.ssh.apache.test/.settings/org.eclipse.mylyn.tasks.ui.prefs b/org.eclipse.jgit.ssh.apache.test/.settings/org.eclipse.mylyn.tasks.ui.prefs
new file mode 100644
index 0000000..823c0f5
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/.settings/org.eclipse.mylyn.tasks.ui.prefs
@@ -0,0 +1,4 @@
+#Tue Jul 19 20:11:28 CEST 2011
+eclipse.preferences.version=1
+project.repository.kind=bugzilla
+project.repository.url=https\://bugs.eclipse.org/bugs
diff --git a/org.eclipse.jgit.ssh.apache.test/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.ssh.apache.test/.settings/org.eclipse.mylyn.team.ui.prefs
new file mode 100644
index 0000000..0cba949
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -0,0 +1,3 @@
+#Tue Jul 19 20:11:28 CEST 2011
+commit.comment.template=${task.description} \n\nBug\: ${task.key}
+eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.ssh.apache.test/.settings/org.eclipse.pde.api.tools.prefs b/org.eclipse.jgit.ssh.apache.test/.settings/org.eclipse.pde.api.tools.prefs
new file mode 100644
index 0000000..c0030de
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/.settings/org.eclipse.pde.api.tools.prefs
@@ -0,0 +1,104 @@
+ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error
+ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error
+ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
+ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error
+ANNOTATION_ELEMENT_TYPE_REMOVED_METHOD=Error
+ANNOTATION_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
+API_COMPONENT_ELEMENT_TYPE_REMOVED_API_TYPE=Error
+API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error
+API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error
+API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error
+API_USE_SCAN_FIELD_SEVERITY=Error
+API_USE_SCAN_METHOD_SEVERITY=Error
+API_USE_SCAN_TYPE_SEVERITY=Error
+CLASS_ELEMENT_TYPE_ADDED_FIELD=Error
+CLASS_ELEMENT_TYPE_ADDED_METHOD=Error
+CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
+CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
+CLASS_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error
+CLASS_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error
+CLASS_ELEMENT_TYPE_CHANGED_NON_ABSTRACT_TO_ABSTRACT=Error
+CLASS_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error
+CLASS_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
+CLASS_ELEMENT_TYPE_REMOVED_CONSTRUCTOR=Error
+CLASS_ELEMENT_TYPE_REMOVED_FIELD=Error
+CLASS_ELEMENT_TYPE_REMOVED_METHOD=Error
+CLASS_ELEMENT_TYPE_REMOVED_SUPERCLASS=Error
+CLASS_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
+CLASS_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+CONSTRUCTOR_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
+CONSTRUCTOR_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error
+CONSTRUCTOR_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error
+CONSTRUCTOR_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+ENUM_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error
+ENUM_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
+ENUM_ELEMENT_TYPE_REMOVED_ENUM_CONSTANT=Error
+ENUM_ELEMENT_TYPE_REMOVED_FIELD=Error
+ENUM_ELEMENT_TYPE_REMOVED_METHOD=Error
+ENUM_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
+FIELD_ELEMENT_TYPE_ADDED_VALUE=Error
+FIELD_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error
+FIELD_ELEMENT_TYPE_CHANGED_FINAL_TO_NON_FINAL_STATIC_CONSTANT=Error
+FIELD_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error
+FIELD_ELEMENT_TYPE_CHANGED_NON_STATIC_TO_STATIC=Error
+FIELD_ELEMENT_TYPE_CHANGED_STATIC_TO_NON_STATIC=Error
+FIELD_ELEMENT_TYPE_CHANGED_TYPE=Error
+FIELD_ELEMENT_TYPE_CHANGED_VALUE=Error
+FIELD_ELEMENT_TYPE_REMOVED_TYPE_ARGUMENT=Error
+FIELD_ELEMENT_TYPE_REMOVED_VALUE=Error
+ILLEGAL_EXTEND=Warning
+ILLEGAL_IMPLEMENT=Warning
+ILLEGAL_INSTANTIATE=Warning
+ILLEGAL_OVERRIDE=Warning
+ILLEGAL_REFERENCE=Warning
+INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error
+INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error
+INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error
+INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
+INTERFACE_ELEMENT_TYPE_ADDED_SUPER_INTERFACE_WITH_METHODS=Error
+INTERFACE_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
+INTERFACE_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error
+INTERFACE_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
+INTERFACE_ELEMENT_TYPE_REMOVED_FIELD=Error
+INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error
+INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
+INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+INVALID_ANNOTATION=Ignore
+INVALID_JAVADOC_TAG=Ignore
+INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error
+LEAK_EXTEND=Warning
+LEAK_FIELD_DECL=Warning
+LEAK_IMPLEMENT=Warning
+LEAK_METHOD_PARAM=Warning
+LEAK_METHOD_RETURN_TYPE=Warning
+METHOD_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
+METHOD_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
+METHOD_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error
+METHOD_ELEMENT_TYPE_CHANGED_NON_ABSTRACT_TO_ABSTRACT=Error
+METHOD_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error
+METHOD_ELEMENT_TYPE_CHANGED_NON_STATIC_TO_STATIC=Error
+METHOD_ELEMENT_TYPE_CHANGED_STATIC_TO_NON_STATIC=Error
+METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error
+METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error
+METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+MISSING_EE_DESCRIPTIONS=Warning
+TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error
+TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error
+TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error
+TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_INTERFACE_BOUND=Error
+TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_CLASS_BOUND=Error
+TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error
+UNUSED_PROBLEM_FILTERS=Warning
+automatically_removed_unused_problem_filters=false
+changed_execution_env=Error
+eclipse.preferences.version=1
+incompatible_api_component_version=Error
+incompatible_api_component_version_include_major_without_breaking_change=Disabled
+incompatible_api_component_version_include_minor_without_api_change=Disabled
+incompatible_api_component_version_report_major_without_breaking_change=Warning
+incompatible_api_component_version_report_minor_without_api_change=Ignore
+invalid_since_tag_version=Error
+malformed_since_tag=Error
+missing_since_tag=Error
+report_api_breakage_when_major_version_incremented=Disabled
+report_resolution_errors_api_component=Warning
diff --git a/org.eclipse.jgit.ssh.apache.test/.settings/org.eclipse.pde.core.prefs b/org.eclipse.jgit.ssh.apache.test/.settings/org.eclipse.pde.core.prefs
new file mode 100644
index 0000000..82793f2
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/.settings/org.eclipse.pde.core.prefs
@@ -0,0 +1,3 @@
+#Thu Jan 14 14:34:32 CST 2010
+eclipse.preferences.version=1
+resolve.requirebundle=false
diff --git a/org.eclipse.jgit.ssh.apache.test/BUILD b/org.eclipse.jgit.ssh.apache.test/BUILD
new file mode 100644
index 0000000..a13cf0b
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/BUILD
@@ -0,0 +1,19 @@
+load(
+    "@com_googlesource_gerrit_bazlets//tools:junit.bzl",
+    "junit_tests",
+)
+
+junit_tests(
+    name = "sshd_apache",
+    srcs = glob(["tst/**/*.java"]),
+    tags = ["sshd"],
+    deps = [
+        "//lib:eddsa",
+        "//lib:junit",
+        "//lib:sshd-core",
+        "//lib:sshd-sftp",
+        "//org.eclipse.jgit:jgit",
+        "//org.eclipse.jgit.ssh.apache:ssh-apache",
+        "//org.eclipse.jgit.test:sshd-helpers",
+    ],
+)
diff --git a/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..5456949
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF
@@ -0,0 +1,19 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %Bundle-Name
+Automatic-Module-Name: org.eclipse.jgit.ssh.apache.test
+Bundle-SymbolicName: org.eclipse.jgit.ssh.apache.test
+Bundle-Version: 5.2.3.qualifier
+Bundle-Vendor: %Provider-Name
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Import-Package: org.eclipse.jgit.internal.transport.sshd.proxy;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.junit;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.junit.ssh;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lib;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.transport;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.transport.ssh;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.transport.sshd;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.util;version="[5.2.3,5.3.0)",
+ org.junit;version="[4.12,5.0.0)",
+ org.junit.experimental.theories;version="[4.12,5.0.0)",
+ org.junit.runner;version="[4.12,5.0.0)"
diff --git a/org.eclipse.jgit.ssh.apache.test/about.html b/org.eclipse.jgit.ssh.apache.test/about.html
new file mode 100644
index 0000000..f971af1
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/about.html
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
+<title>Eclipse Distribution License - Version 1.0</title>
+<style type="text/css">
+  body {
+    size: 8.5in 11.0in;
+    margin: 0.25in 0.5in 0.25in 0.5in;
+    tab-interval: 0.5in;
+    }
+  p {  	
+    margin-left: auto;
+    margin-top:  0.5em;
+    margin-bottom: 0.5em;
+    }
+  p.list {
+  	margin-left: 0.5in;
+    margin-top:  0.05em;
+    margin-bottom: 0.05em;
+    }
+  .ubc-name {
+    margin-left: 0.5in;
+    white-space: pre;
+  }
+  </style>
+
+</head>
+
+<body lang="EN-US">
+
+<p><b>Eclipse Distribution License - v 1.0</b></p>
+
+<p>Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors. </p>
+
+<p>All rights reserved.</p>
+<p>Redistribution and use in source and binary forms, with or without modification, 
+	are permitted provided that the following conditions are met:
+<ul><li>Redistributions of source code must retain the above copyright notice, 
+	this list of conditions and the following disclaimer. </li>
+<li>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. </li>
+<li>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. </li></ul>
+</p>
+<p>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.</p>
+
+<hr>
+<p><b>SHA-1 UbcCheck - MIT</b></p>
+
+<p>Copyright (c) 2017:</p>
+<div class="ubc-name">
+Marc Stevens
+Cryptology Group
+Centrum Wiskunde & Informatica
+P.O. Box 94079, 1090 GB Amsterdam, Netherlands
+marc@marc-stevens.nl
+</div>
+<div class="ubc-name">
+Dan Shumow
+Microsoft Research
+danshu@microsoft.com
+</div>
+<p>Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+</p>
+<ul><li>The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.</li></ul>
+<p>THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.</p>
+
+</body>
+
+</html>
diff --git a/org.eclipse.jgit.ssh.apache.test/build.properties b/org.eclipse.jgit.ssh.apache.test/build.properties
new file mode 100644
index 0000000..9ffa0ca
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/build.properties
@@ -0,0 +1,5 @@
+source.. = tst/
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               plugin.properties
diff --git a/org.eclipse.jgit.ssh.apache.test/plugin.properties b/org.eclipse.jgit.ssh.apache.test/plugin.properties
new file mode 100644
index 0000000..67c296d
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/plugin.properties
@@ -0,0 +1,2 @@
+plugin_name=JGit Tests for SSH with Apache MINA sshd
+provider_name=Eclipse JGit
diff --git a/org.eclipse.jgit.ssh.apache.test/pom.xml b/org.eclipse.jgit.ssh.apache.test/pom.xml
new file mode 100644
index 0000000..a03f4ec
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/pom.xml
@@ -0,0 +1,145 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+   and other copyright owners as documented in the project's IP log.
+
+   This program and the accompanying materials are made available
+   under the terms of the Eclipse Distribution License v1.0 which
+   accompanies this distribution, is reproduced below, and is
+   available at http://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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.eclipse.jgit</groupId>
+    <artifactId>org.eclipse.jgit-parent</artifactId>
+    <version>5.2.3-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>org.eclipse.jgit.ssh.apache.test</artifactId>
+  <name>JGit - Apache sshd SSH Tests</name>
+
+  <description>
+    JUnit tests for the JGit SSH support based on Apache MINA sshd.
+  </description>
+
+  <properties>
+    <maven.javadoc.skip>true</maven.javadoc.skip>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit.junit</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit.ssh.apache</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit.test</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+  </dependencies>
+
+  <profiles>
+    <!-- Profile provides a property which enables long running tests. -->
+    <profile>
+      <id>test.long</id>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-surefire-plugin</artifactId>
+            <configuration>
+              <argLine>-Djgit.test.long=true</argLine>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+
+  <build>
+    <sourceDirectory>src/</sourceDirectory>
+    <testSourceDirectory>tst/</testSourceDirectory>
+
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <executions>
+          <execution>
+            <goals>
+              <goal>test-jar</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <argLine>-Xmx1024m -Dfile.encoding=UTF-8 -Djava.io.tmpdir=${project.build.directory}</argLine>
+          <includes>
+            <include>**/*Test.java</include>
+            <include>**/*Tests.java</include>
+          </includes>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/transport/sshd/proxy/HttpParserTest.java b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/transport/sshd/proxy/HttpParserTest.java
new file mode 100644
index 0000000..b8e8549
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/transport/sshd/proxy/HttpParserTest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd.proxy;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Test;
+
+public class HttpParserTest {
+
+	private static final String STATUS_LINE = "HTTP/1.1. 407 Authentication required";
+
+	@Test
+	public void testEmpty() throws Exception {
+		String[] lines = { STATUS_LINE };
+		List<AuthenticationChallenge> challenges = HttpParser
+				.getAuthenticationHeaders(Arrays.asList(lines),
+						"WWW-Authenticate:");
+		assertTrue("No challenges expected", challenges.isEmpty());
+	}
+
+	@Test
+	public void testRFC7235Example() throws Exception {
+		// The example from RFC 7235, sec. 4.1, slightly modified ("kind"
+		// argument with whitespace around '=')
+		String[] lines = { STATUS_LINE,
+				"WWW-Authenticate: Newauth realm=\"apps\", type=1  , kind = \t2 ",
+				"   \t  title=\"Login to \\\"apps\\\"\", Basic realm=\"simple\"" };
+		List<AuthenticationChallenge> challenges = HttpParser
+				.getAuthenticationHeaders(Arrays.asList(lines),
+						"WWW-Authenticate:");
+		assertEquals("Unexpected number of challenges", 2, challenges.size());
+		assertNull("No token expected", challenges.get(0).getToken());
+		assertNull("No token expected", challenges.get(1).getToken());
+		assertEquals("Unexpected mechanism", "Newauth",
+				challenges.get(0).getMechanism());
+		assertEquals("Unexpected mechanism", "Basic",
+				challenges.get(1).getMechanism());
+		Map<String, String> expectedArguments = new LinkedHashMap<>();
+		expectedArguments.put("realm", "apps");
+		expectedArguments.put("type", "1");
+		expectedArguments.put("kind", "2");
+		expectedArguments.put("title", "Login to \"apps\"");
+		assertEquals("Unexpected arguments", expectedArguments,
+				challenges.get(0).getArguments());
+		expectedArguments.clear();
+		expectedArguments.put("realm", "simple");
+		assertEquals("Unexpected arguments", expectedArguments,
+				challenges.get(1).getArguments());
+	}
+
+	@Test
+	public void testMultipleHeaders() {
+		String[] lines = { STATUS_LINE,
+				"Server: Apache",
+				"WWW-Authenticate: Newauth realm=\"apps\", type=1  , kind = \t2 ",
+				"   \t  title=\"Login to \\\"apps\\\"\", Basic realm=\"simple\"",
+				"Content-Type: text/plain",
+				"WWW-Authenticate: Other 0123456789===  , YetAnother, ",
+				"WWW-Authenticate: Negotiate   ",
+				"WWW-Authenticate: Negotiate a87421000492aa874209af8bc028" };
+		List<AuthenticationChallenge> challenges = HttpParser
+				.getAuthenticationHeaders(Arrays.asList(lines),
+						"WWW-Authenticate:");
+		assertEquals("Unexpected number of challenges", 6, challenges.size());
+		assertEquals("Mismatched challenge", "Other",
+				challenges.get(2).getMechanism());
+		assertEquals("Token expected", "0123456789===",
+				challenges.get(2).getToken());
+		assertEquals("Mismatched challenge", "YetAnother",
+				challenges.get(3).getMechanism());
+		assertNull("No token expected", challenges.get(3).getToken());
+		assertTrue("No arguments expected",
+				challenges.get(3).getArguments().isEmpty());
+		assertEquals("Mismatched challenge", "Negotiate",
+				challenges.get(4).getMechanism());
+		assertNull("No token expected", challenges.get(4).getToken());
+		assertEquals("Mismatched challenge", "Negotiate",
+				challenges.get(5).getMechanism());
+		assertEquals("Token expected", "a87421000492aa874209af8bc028",
+				challenges.get(5).getToken());
+	}
+
+	@Test
+	public void testStopOnEmptyLine() {
+		String[] lines = { STATUS_LINE, "Server: Apache",
+				"WWW-Authenticate: Newauth realm=\"apps\", type=1  , kind = \t2 ",
+				"   \t  title=\"Login to \\\"apps\\\"\", Basic realm=\"simple\"",
+				"Content-Type: text/plain",
+				"WWW-Authenticate: Other 0123456789===", "",
+				// Not headers anymore; this would be the body
+				"WWW-Authenticate: Negotiate   ",
+				"WWW-Authenticate: Negotiate a87421000492aa874209af8bc028" };
+		List<AuthenticationChallenge> challenges = HttpParser
+				.getAuthenticationHeaders(Arrays.asList(lines),
+						"WWW-Authenticate:");
+		assertEquals("Unexpected number of challenges", 3, challenges.size());
+	}
+}
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
new file mode 100644
index 0000000..ee58083
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/ApacheSshTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.transport.sshd;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.util.Arrays;
+
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.transport.SshSessionFactory;
+import org.eclipse.jgit.transport.ssh.SshTestBase;
+import org.eclipse.jgit.transport.sshd.SshdSessionFactory;
+import org.eclipse.jgit.util.FS;
+import org.junit.Test;
+import org.junit.experimental.theories.Theories;
+import org.junit.runner.RunWith;
+
+@RunWith(Theories.class)
+public class ApacheSshTest extends SshTestBase {
+
+	@Override
+	protected SshSessionFactory createSessionFactory() {
+		SshdSessionFactory result = new SshdSessionFactory(new JGitKeyCache(),
+				null);
+		// The home directory is mocked at this point!
+		result.setHomeDirectory(FS.DETECTED.userHome());
+		result.setSshDirectory(sshDir);
+		return result;
+	}
+
+	@Override
+	protected void installConfig(String... config) {
+		File configFile = new File(sshDir, Constants.CONFIG);
+		if (config != null) {
+			try {
+				Files.write(configFile.toPath(), Arrays.asList(config));
+			} catch (IOException e) {
+				throw new UncheckedIOException(e);
+			}
+		}
+	}
+
+	// Using an ed25519 (unencrypted) user key is tested in the super class in
+	// testSshKeys(). sshd 2.0.0 cannot yet read encrypted ed25519 keys.
+
+	@Test
+	public void testEd25519HostKey() throws Exception {
+		File newHostKey = new File(getTemporaryDirectory(), "newhostkey");
+		copyTestResource("id_ed25519", newHostKey);
+		server.addHostKey(newHostKey.toPath(), true);
+		File newHostKeyPub = new File(getTemporaryDirectory(),
+				"newhostkey.pub");
+		copyTestResource("id_ed25519.pub", newHostKeyPub);
+		createKnownHostsFile(knownHosts, "localhost", testPort, newHostKeyPub);
+		cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
+				"Host git", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"IdentityFile " + privateKey1.getAbsolutePath());
+	}
+
+}
diff --git a/org.eclipse.jgit.ssh.apache/.classpath b/org.eclipse.jgit.ssh.apache/.classpath
new file mode 100644
index 0000000..110168f
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/.classpath
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="src" path="resources"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/org.eclipse.jgit.ssh.apache/.fbprefs b/org.eclipse.jgit.ssh.apache/.fbprefs
new file mode 100644
index 0000000..81a0767
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/.fbprefs
@@ -0,0 +1,125 @@
+#FindBugs User Preferences
+#Mon May 04 16:24:13 PDT 2009
+detectorAppendingToAnObjectOutputStream=AppendingToAnObjectOutputStream|true
+detectorBadAppletConstructor=BadAppletConstructor|false
+detectorBadResultSetAccess=BadResultSetAccess|true
+detectorBadSyntaxForRegularExpression=BadSyntaxForRegularExpression|true
+detectorBadUseOfReturnValue=BadUseOfReturnValue|true
+detectorBadlyOverriddenAdapter=BadlyOverriddenAdapter|true
+detectorBooleanReturnNull=BooleanReturnNull|true
+detectorCallToUnsupportedMethod=CallToUnsupportedMethod|true
+detectorCheckImmutableAnnotation=CheckImmutableAnnotation|true
+detectorCheckTypeQualifiers=CheckTypeQualifiers|true
+detectorCloneIdiom=CloneIdiom|false
+detectorComparatorIdiom=ComparatorIdiom|true
+detectorConfusedInheritance=ConfusedInheritance|true
+detectorConfusionBetweenInheritedAndOuterMethod=ConfusionBetweenInheritedAndOuterMethod|true
+detectorCrossSiteScripting=CrossSiteScripting|true
+detectorDoInsideDoPrivileged=DoInsideDoPrivileged|true
+detectorDontCatchIllegalMonitorStateException=DontCatchIllegalMonitorStateException|true
+detectorDontUseEnum=DontUseEnum|true
+detectorDroppedException=DroppedException|true
+detectorDumbMethodInvocations=DumbMethodInvocations|true
+detectorDumbMethods=DumbMethods|true
+detectorDuplicateBranches=DuplicateBranches|true
+detectorEmptyZipFileEntry=EmptyZipFileEntry|true
+detectorEqualsOperandShouldHaveClassCompatibleWithThis=EqualsOperandShouldHaveClassCompatibleWithThis|true
+detectorFinalizerNullsFields=FinalizerNullsFields|true
+detectorFindBadCast2=FindBadCast2|true
+detectorFindBadForLoop=FindBadForLoop|true
+detectorFindCircularDependencies=FindCircularDependencies|false
+detectorFindDeadLocalStores=FindDeadLocalStores|true
+detectorFindDoubleCheck=FindDoubleCheck|true
+detectorFindEmptySynchronizedBlock=FindEmptySynchronizedBlock|true
+detectorFindFieldSelfAssignment=FindFieldSelfAssignment|true
+detectorFindFinalizeInvocations=FindFinalizeInvocations|true
+detectorFindFloatEquality=FindFloatEquality|true
+detectorFindHEmismatch=FindHEmismatch|true
+detectorFindInconsistentSync2=FindInconsistentSync2|true
+detectorFindJSR166LockMonitorenter=FindJSR166LockMonitorenter|true
+detectorFindLocalSelfAssignment2=FindLocalSelfAssignment2|true
+detectorFindMaskedFields=FindMaskedFields|true
+detectorFindMismatchedWaitOrNotify=FindMismatchedWaitOrNotify|true
+detectorFindNakedNotify=FindNakedNotify|true
+detectorFindNonSerializableStoreIntoSession=FindNonSerializableStoreIntoSession|true
+detectorFindNonSerializableValuePassedToWriteObject=FindNonSerializableValuePassedToWriteObject|true
+detectorFindNonShortCircuit=FindNonShortCircuit|true
+detectorFindNullDeref=FindNullDeref|true
+detectorFindNullDerefsInvolvingNonShortCircuitEvaluation=FindNullDerefsInvolvingNonShortCircuitEvaluation|true
+detectorFindOpenStream=FindOpenStream|true
+detectorFindPuzzlers=FindPuzzlers|true
+detectorFindRefComparison=FindRefComparison|true
+detectorFindReturnRef=FindReturnRef|true
+detectorFindRunInvocations=FindRunInvocations|true
+detectorFindSelfComparison=FindSelfComparison|true
+detectorFindSelfComparison2=FindSelfComparison2|true
+detectorFindSleepWithLockHeld=FindSleepWithLockHeld|true
+detectorFindSpinLoop=FindSpinLoop|true
+detectorFindSqlInjection=FindSqlInjection|true
+detectorFindTwoLockWait=FindTwoLockWait|true
+detectorFindUncalledPrivateMethods=FindUncalledPrivateMethods|true
+detectorFindUnconditionalWait=FindUnconditionalWait|true
+detectorFindUninitializedGet=FindUninitializedGet|true
+detectorFindUnrelatedTypesInGenericContainer=FindUnrelatedTypesInGenericContainer|true
+detectorFindUnreleasedLock=FindUnreleasedLock|true
+detectorFindUnsatisfiedObligation=FindUnsatisfiedObligation|true
+detectorFindUnsyncGet=FindUnsyncGet|true
+detectorFindUselessControlFlow=FindUselessControlFlow|true
+detectorFormatStringChecker=FormatStringChecker|true
+detectorHugeSharedStringConstants=HugeSharedStringConstants|true
+detectorIDivResultCastToDouble=IDivResultCastToDouble|true
+detectorIncompatMask=IncompatMask|true
+detectorInconsistentAnnotations=InconsistentAnnotations|true
+detectorInefficientMemberAccess=InefficientMemberAccess|false
+detectorInefficientToArray=InefficientToArray|true
+detectorInfiniteLoop=InfiniteLoop|true
+detectorInfiniteRecursiveLoop=InfiniteRecursiveLoop|true
+detectorInfiniteRecursiveLoop2=InfiniteRecursiveLoop2|false
+detectorInheritanceUnsafeGetResource=InheritanceUnsafeGetResource|true
+detectorInitializationChain=InitializationChain|true
+detectorInstantiateStaticClass=InstantiateStaticClass|true
+detectorInvalidJUnitTest=InvalidJUnitTest|true
+detectorIteratorIdioms=IteratorIdioms|true
+detectorLazyInit=LazyInit|true
+detectorLoadOfKnownNullValue=LoadOfKnownNullValue|true
+detectorMethodReturnCheck=MethodReturnCheck|true
+detectorMultithreadedInstanceAccess=MultithreadedInstanceAccess|true
+detectorMutableLock=MutableLock|true
+detectorMutableStaticFields=MutableStaticFields|true
+detectorNaming=Naming|true
+detectorNumberConstructor=NumberConstructor|true
+detectorOverridingEqualsNotSymmetrical=OverridingEqualsNotSymmetrical|true
+detectorPreferZeroLengthArrays=PreferZeroLengthArrays|true
+detectorPublicSemaphores=PublicSemaphores|false
+detectorQuestionableBooleanAssignment=QuestionableBooleanAssignment|true
+detectorReadReturnShouldBeChecked=ReadReturnShouldBeChecked|true
+detectorRedundantInterfaces=RedundantInterfaces|true
+detectorRepeatedConditionals=RepeatedConditionals|true
+detectorRuntimeExceptionCapture=RuntimeExceptionCapture|true
+detectorSerializableIdiom=SerializableIdiom|true
+detectorStartInConstructor=StartInConstructor|true
+detectorStaticCalendarDetector=StaticCalendarDetector|true
+detectorStringConcatenation=StringConcatenation|true
+detectorSuperfluousInstanceOf=SuperfluousInstanceOf|true
+detectorSuspiciousThreadInterrupted=SuspiciousThreadInterrupted|true
+detectorSwitchFallthrough=SwitchFallthrough|true
+detectorSynchronizeAndNullCheckField=SynchronizeAndNullCheckField|true
+detectorSynchronizeOnClassLiteralNotGetClass=SynchronizeOnClassLiteralNotGetClass|true
+detectorSynchronizingOnContentsOfFieldToProtectField=SynchronizingOnContentsOfFieldToProtectField|true
+detectorURLProblems=URLProblems|true
+detectorUncallableMethodOfAnonymousClass=UncallableMethodOfAnonymousClass|true
+detectorUnnecessaryMath=UnnecessaryMath|true
+detectorUnreadFields=UnreadFields|true
+detectorUseObjectEquals=UseObjectEquals|false
+detectorUselessSubclassMethod=UselessSubclassMethod|false
+detectorVarArgsProblems=VarArgsProblems|true
+detectorVolatileUsage=VolatileUsage|true
+detectorWaitInLoop=WaitInLoop|true
+detectorWrongMapIterator=WrongMapIterator|true
+detectorXMLFactoryBypass=XMLFactoryBypass|true
+detector_threshold=2
+effort=default
+excludefilter0=findBugs/FindBugsExcludeFilter.xml
+filter_settings=Medium|BAD_PRACTICE,CORRECTNESS,MT_CORRECTNESS,PERFORMANCE,STYLE|false
+filter_settings_neg=MALICIOUS_CODE,NOISE,I18N,SECURITY,EXPERIMENTAL|
+run_at_full_build=true
diff --git a/org.eclipse.jgit.ssh.apache/.gitignore b/org.eclipse.jgit.ssh.apache/.gitignore
new file mode 100644
index 0000000..934e0e0
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/.gitignore
@@ -0,0 +1,2 @@
+/bin
+/target
diff --git a/org.eclipse.jgit.ssh.apache/.project b/org.eclipse.jgit.ssh.apache/.project
new file mode 100644
index 0000000..a7bbd6b
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/.project
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.eclipse.jgit.ssh.apache</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.ManifestBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.SchemaBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.api.tools.apiAnalysisBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.eclipse.pde.api.tools.apiAnalysisNature</nature>
+	</natures>
+</projectDescription>
diff --git a/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.core.resources.prefs b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..66ac15c
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,3 @@
+#Mon Aug 11 16:46:12 PDT 2008
+eclipse.preferences.version=1
+encoding/<project>=UTF-8
diff --git a/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.core.runtime.prefs b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.core.runtime.prefs
new file mode 100644
index 0000000..006e07e
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.core.runtime.prefs
@@ -0,0 +1,3 @@
+#Mon Mar 24 18:55:50 EDT 2008
+eclipse.preferences.version=1
+line.separator=\n
diff --git a/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..13c32a6
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,399 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=enabled
+org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
+org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jgit.annotations.NonNull
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jgit.annotations.NonNullByDefault
+org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jgit.annotations.Nullable
+org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.doc.comment.support=enabled
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.autoboxing=warning
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=error
+org.eclipse.jdt.core.compiler.problem.deadCode=error
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=warning
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=warning
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
+org.eclipse.jdt.core.compiler.problem.fieldHiding=warning
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=error
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=error
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=enabled
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=error
+org.eclipse.jdt.core.compiler.problem.invalidJavadoc=error
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=private
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=error
+org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=error
+org.eclipse.jdt.core.compiler.problem.missingJavadocComments=error
+org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag
+org.eclipse.jdt.core.compiler.problem.missingJavadocTags=error
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=error
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=error
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=warning
+org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
+org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
+org.eclipse.jdt.core.compiler.problem.nullReference=error
+org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=ignore
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=error
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=error
+org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore
+org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning
+org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning
+org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=error
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=error
+org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedImport=error
+org.eclipse.jdt.core.compiler.problem.unusedLabel=error
+org.eclipse.jdt.core.compiler.problem.unusedLocal=error
+org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning
+org.eclipse.jdt.core.compiler.problem.unusedParameter=warning
+org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=error
+org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=error
+org.eclipse.jdt.core.compiler.source=1.8
+org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_assignment=0
+org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
+org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
+org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
+org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
+org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
+org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_after_package=1
+org.eclipse.jdt.core.formatter.blank_lines_before_field=1
+org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
+org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
+org.eclipse.jdt.core.formatter.blank_lines_before_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
+org.eclipse.jdt.core.formatter.blank_lines_before_package=0
+org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
+org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
+org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
+org.eclipse.jdt.core.formatter.comment.format_block_comments=true
+org.eclipse.jdt.core.formatter.comment.format_comments=true
+org.eclipse.jdt.core.formatter.comment.format_header=false
+org.eclipse.jdt.core.formatter.comment.format_html=true
+org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
+org.eclipse.jdt.core.formatter.comment.format_line_comments=true
+org.eclipse.jdt.core.formatter.comment.format_source_code=true
+org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
+org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
+org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
+org.eclipse.jdt.core.formatter.comment.line_length=80
+org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
+org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
+org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
+org.eclipse.jdt.core.formatter.compact_else_if=true
+org.eclipse.jdt.core.formatter.continuation_indentation=2
+org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
+org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
+org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
+org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
+org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
+org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_empty_lines=false
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
+org.eclipse.jdt.core.formatter.indentation.size=4
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
+org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
+org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.join_lines_in_comments=true
+org.eclipse.jdt.core.formatter.join_wrapped_lines=true
+org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.lineSplit=80
+org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
+org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
+org.eclipse.jdt.core.formatter.tabulation.char=tab
+org.eclipse.jdt.core.formatter.tabulation.size=4
+org.eclipse.jdt.core.formatter.use_on_off_tags=true
+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
+org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
+org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
diff --git a/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 0000000..fef3713
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,66 @@
+eclipse.preferences.version=1
+editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+formatter_profile=_JGit Format
+formatter_settings_version=12
+org.eclipse.jdt.ui.ignorelowercasenames=true
+org.eclipse.jdt.ui.importorder=java;javax;org;com;
+org.eclipse.jdt.ui.ondemandthreshold=99
+org.eclipse.jdt.ui.staticondemandthreshold=99
+org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8"?><templates/>
+sp_cleanup.add_default_serial_version_id=true
+sp_cleanup.add_generated_serial_version_id=false
+sp_cleanup.add_missing_annotations=true
+sp_cleanup.add_missing_deprecated_annotations=true
+sp_cleanup.add_missing_methods=false
+sp_cleanup.add_missing_nls_tags=false
+sp_cleanup.add_missing_override_annotations=true
+sp_cleanup.add_missing_override_annotations_interface_methods=true
+sp_cleanup.add_serial_version_id=false
+sp_cleanup.always_use_blocks=true
+sp_cleanup.always_use_parentheses_in_expressions=false
+sp_cleanup.always_use_this_for_non_static_field_access=false
+sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_functional_interfaces=false
+sp_cleanup.convert_to_enhanced_for_loop=false
+sp_cleanup.correct_indentation=false
+sp_cleanup.format_source_code=true
+sp_cleanup.format_source_code_changes_only=true
+sp_cleanup.insert_inferred_type_arguments=false
+sp_cleanup.make_local_variable_final=false
+sp_cleanup.make_parameters_final=false
+sp_cleanup.make_private_fields_final=true
+sp_cleanup.make_type_abstract_if_missing_method=false
+sp_cleanup.make_variable_declarations_final=false
+sp_cleanup.never_use_blocks=false
+sp_cleanup.never_use_parentheses_in_expressions=true
+sp_cleanup.on_save_use_additional_actions=true
+sp_cleanup.organize_imports=false
+sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_type_arguments=true
+sp_cleanup.remove_trailing_whitespaces=true
+sp_cleanup.remove_trailing_whitespaces_all=true
+sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=true
+sp_cleanup.remove_unused_imports=false
+sp_cleanup.remove_unused_local_variables=false
+sp_cleanup.remove_unused_private_fields=true
+sp_cleanup.remove_unused_private_members=false
+sp_cleanup.remove_unused_private_methods=true
+sp_cleanup.remove_unused_private_types=true
+sp_cleanup.sort_members=false
+sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
+sp_cleanup.use_blocks=false
+sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=false
+sp_cleanup.use_parentheses_in_expressions=false
+sp_cleanup.use_this_for_non_static_field_access=false
+sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+sp_cleanup.use_this_for_non_static_method_access=false
+sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.mylyn.tasks.ui.prefs b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.mylyn.tasks.ui.prefs
new file mode 100644
index 0000000..823c0f5
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.mylyn.tasks.ui.prefs
@@ -0,0 +1,4 @@
+#Tue Jul 19 20:11:28 CEST 2011
+eclipse.preferences.version=1
+project.repository.kind=bugzilla
+project.repository.url=https\://bugs.eclipse.org/bugs
diff --git a/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.mylyn.team.ui.prefs
new file mode 100644
index 0000000..0cba949
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -0,0 +1,3 @@
+#Tue Jul 19 20:11:28 CEST 2011
+commit.comment.template=${task.description} \n\nBug\: ${task.key}
+eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.pde.api.tools.prefs b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.pde.api.tools.prefs
new file mode 100644
index 0000000..c0030de
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.pde.api.tools.prefs
@@ -0,0 +1,104 @@
+ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error
+ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error
+ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
+ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error
+ANNOTATION_ELEMENT_TYPE_REMOVED_METHOD=Error
+ANNOTATION_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
+API_COMPONENT_ELEMENT_TYPE_REMOVED_API_TYPE=Error
+API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error
+API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error
+API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error
+API_USE_SCAN_FIELD_SEVERITY=Error
+API_USE_SCAN_METHOD_SEVERITY=Error
+API_USE_SCAN_TYPE_SEVERITY=Error
+CLASS_ELEMENT_TYPE_ADDED_FIELD=Error
+CLASS_ELEMENT_TYPE_ADDED_METHOD=Error
+CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
+CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
+CLASS_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error
+CLASS_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error
+CLASS_ELEMENT_TYPE_CHANGED_NON_ABSTRACT_TO_ABSTRACT=Error
+CLASS_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error
+CLASS_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
+CLASS_ELEMENT_TYPE_REMOVED_CONSTRUCTOR=Error
+CLASS_ELEMENT_TYPE_REMOVED_FIELD=Error
+CLASS_ELEMENT_TYPE_REMOVED_METHOD=Error
+CLASS_ELEMENT_TYPE_REMOVED_SUPERCLASS=Error
+CLASS_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
+CLASS_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+CONSTRUCTOR_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
+CONSTRUCTOR_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error
+CONSTRUCTOR_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error
+CONSTRUCTOR_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+ENUM_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error
+ENUM_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
+ENUM_ELEMENT_TYPE_REMOVED_ENUM_CONSTANT=Error
+ENUM_ELEMENT_TYPE_REMOVED_FIELD=Error
+ENUM_ELEMENT_TYPE_REMOVED_METHOD=Error
+ENUM_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
+FIELD_ELEMENT_TYPE_ADDED_VALUE=Error
+FIELD_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error
+FIELD_ELEMENT_TYPE_CHANGED_FINAL_TO_NON_FINAL_STATIC_CONSTANT=Error
+FIELD_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error
+FIELD_ELEMENT_TYPE_CHANGED_NON_STATIC_TO_STATIC=Error
+FIELD_ELEMENT_TYPE_CHANGED_STATIC_TO_NON_STATIC=Error
+FIELD_ELEMENT_TYPE_CHANGED_TYPE=Error
+FIELD_ELEMENT_TYPE_CHANGED_VALUE=Error
+FIELD_ELEMENT_TYPE_REMOVED_TYPE_ARGUMENT=Error
+FIELD_ELEMENT_TYPE_REMOVED_VALUE=Error
+ILLEGAL_EXTEND=Warning
+ILLEGAL_IMPLEMENT=Warning
+ILLEGAL_INSTANTIATE=Warning
+ILLEGAL_OVERRIDE=Warning
+ILLEGAL_REFERENCE=Warning
+INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error
+INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error
+INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error
+INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
+INTERFACE_ELEMENT_TYPE_ADDED_SUPER_INTERFACE_WITH_METHODS=Error
+INTERFACE_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
+INTERFACE_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error
+INTERFACE_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
+INTERFACE_ELEMENT_TYPE_REMOVED_FIELD=Error
+INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error
+INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
+INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+INVALID_ANNOTATION=Ignore
+INVALID_JAVADOC_TAG=Ignore
+INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error
+LEAK_EXTEND=Warning
+LEAK_FIELD_DECL=Warning
+LEAK_IMPLEMENT=Warning
+LEAK_METHOD_PARAM=Warning
+LEAK_METHOD_RETURN_TYPE=Warning
+METHOD_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
+METHOD_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
+METHOD_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error
+METHOD_ELEMENT_TYPE_CHANGED_NON_ABSTRACT_TO_ABSTRACT=Error
+METHOD_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error
+METHOD_ELEMENT_TYPE_CHANGED_NON_STATIC_TO_STATIC=Error
+METHOD_ELEMENT_TYPE_CHANGED_STATIC_TO_NON_STATIC=Error
+METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error
+METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error
+METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+MISSING_EE_DESCRIPTIONS=Warning
+TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error
+TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error
+TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error
+TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_INTERFACE_BOUND=Error
+TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_CLASS_BOUND=Error
+TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error
+UNUSED_PROBLEM_FILTERS=Warning
+automatically_removed_unused_problem_filters=false
+changed_execution_env=Error
+eclipse.preferences.version=1
+incompatible_api_component_version=Error
+incompatible_api_component_version_include_major_without_breaking_change=Disabled
+incompatible_api_component_version_include_minor_without_api_change=Disabled
+incompatible_api_component_version_report_major_without_breaking_change=Warning
+incompatible_api_component_version_report_minor_without_api_change=Ignore
+invalid_since_tag_version=Error
+malformed_since_tag=Error
+missing_since_tag=Error
+report_api_breakage_when_major_version_incremented=Disabled
+report_resolution_errors_api_component=Warning
diff --git a/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.pde.core.prefs b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.pde.core.prefs
new file mode 100644
index 0000000..82793f2
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/.settings/org.eclipse.pde.core.prefs
@@ -0,0 +1,3 @@
+#Thu Jan 14 14:34:32 CST 2010
+eclipse.preferences.version=1
+resolve.requirebundle=false
diff --git a/org.eclipse.jgit.ssh.apache/BUILD b/org.eclipse.jgit.ssh.apache/BUILD
new file mode 100644
index 0000000..a1a6c8e
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/BUILD
@@ -0,0 +1,19 @@
+package(default_visibility = ["//visibility:public"])
+
+SRCS = glob(["src/**/*.java"])
+
+RESOURCES = glob(["resources/**"])
+
+java_library(
+    name = "ssh-apache",
+    srcs = SRCS,
+    resource_strip_prefix = "org.eclipse.jgit.ssh.apache/resources",
+    resources = RESOURCES,
+    deps = [
+        "//lib:eddsa",
+        "//lib:slf4j-api",
+        "//lib:sshd-core",
+        "//lib:sshd-sftp",
+        "//org.eclipse.jgit:jgit",
+    ],
+)
diff --git a/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF b/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..29a12bb
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF
@@ -0,0 +1,83 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %Bundle-Name
+Automatic-Module-Name: org.eclipse.jgit.ssh.apache
+Bundle-SymbolicName: org.eclipse.jgit.ssh.apache
+Bundle-Vendor: %Provider-Name
+Bundle-ActivationPolicy: lazy
+Bundle-Version: 5.2.3.qualifier
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Export-Package: org.eclipse.jgit.internal.transport.sshd;version="5.2.3";x-internal:=true;
+  uses:="org.apache.sshd.client,
+   org.apache.sshd.client.auth,
+   org.apache.sshd.client.auth.keyboard,
+   org.apache.sshd.client.auth.pubkey,
+   org.apache.sshd.client.config.hosts,
+   org.apache.sshd.client.future,
+   org.apache.sshd.client.keyverifier,
+   org.apache.sshd.client.session,
+   org.apache.sshd.common.config.keys,
+   org.apache.sshd.common.io,
+   org.apache.sshd.common.keyprovider,
+   org.apache.sshd.common.signature,
+   org.apache.sshd.common.util.buffer,
+   org.eclipse.jgit.transport",
+ org.eclipse.jgit.internal.transport.sshd.auth;version="5.2.3";x-internal:=true,
+ org.eclipse.jgit.internal.transport.sshd.proxy;version="5.2.3";x-friends:="org.eclipse.jgit.ssh.apache.test",
+ org.eclipse.jgit.transport.sshd;version="5.2.3";
+  uses:="org.eclipse.jgit.transport,
+   org.apache.sshd.client.config.hosts,
+   org.apache.sshd.common.keyprovider,
+   org.eclipse.jgit.util,
+   org.apache.sshd.client.session,
+   org.apache.sshd.client.keyverifier"
+Import-Package: net.i2p.crypto.eddsa;version="[0.3.0,0.4.0)",
+ org.apache.sshd.agent;version="[2.0.0,2.1.0)",
+ org.apache.sshd.client;version="[2.0.0,2.1.0)",
+ org.apache.sshd.client.auth;version="[2.0.0,2.1.0)",
+ org.apache.sshd.client.auth.keyboard;version="[2.0.0,2.1.0)",
+ org.apache.sshd.client.auth.password;version="[2.0.0,2.1.0)",
+ org.apache.sshd.client.auth.pubkey;version="[2.0.0,2.1.0)",
+ org.apache.sshd.client.channel;version="[2.0.0,2.1.0)",
+ org.apache.sshd.client.config.hosts;version="[2.0.0,2.1.0)",
+ org.apache.sshd.client.config.keys;version="[2.0.0,2.1.0)",
+ org.apache.sshd.client.future;version="[2.0.0,2.1.0)",
+ org.apache.sshd.client.keyverifier;version="[2.0.0,2.1.0)",
+ org.apache.sshd.client.session;version="[2.0.0,2.1.0)",
+ org.apache.sshd.client.subsystem.sftp;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.auth;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.channel;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.compression;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.config.keys;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.config.keys.loader;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.digest;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.forward;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.future;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.helpers;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.io;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.kex;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.keyprovider;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.mac;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.random;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.session;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.session.helpers;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.signature;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.subsystem.sftp;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.util;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.util.buffer;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.util.closeable;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.util.io;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.util.logging;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.util.net;version="[2.0.0,2.1.0)",
+ org.apache.sshd.common.util.security;version="[2.0.0,2.1.0)",
+ org.apache.sshd.server.auth;version="[2.0.0,2.1.0)",
+ org.eclipse.jgit.annotations;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.errors;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.fnmatch;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal.transport.ssh;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.nls;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.transport;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.util;version="[5.2.3,5.3.0)",
+ org.slf4j;version="[1.7.0,2.0.0)"
diff --git a/org.eclipse.jgit.ssh.apache/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.ssh.apache/META-INF/SOURCE-MANIFEST.MF
new file mode 100644
index 0000000..a16ebd3
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/META-INF/SOURCE-MANIFEST.MF
@@ -0,0 +1,7 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: org.eclipse.jgit.ssh.apache - Sources
+Bundle-SymbolicName: org.eclipse.jgit.ssh.apache.source
+Bundle-Vendor: Eclipse.org - JGit
+Bundle-Version: 5.2.3.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.ssh.apache;version="5.2.3.qualifier";roots="."
diff --git a/org.eclipse.jgit.ssh.apache/about.html b/org.eclipse.jgit.ssh.apache/about.html
new file mode 100644
index 0000000..f971af1
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/about.html
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
+<title>Eclipse Distribution License - Version 1.0</title>
+<style type="text/css">
+  body {
+    size: 8.5in 11.0in;
+    margin: 0.25in 0.5in 0.25in 0.5in;
+    tab-interval: 0.5in;
+    }
+  p {  	
+    margin-left: auto;
+    margin-top:  0.5em;
+    margin-bottom: 0.5em;
+    }
+  p.list {
+  	margin-left: 0.5in;
+    margin-top:  0.05em;
+    margin-bottom: 0.05em;
+    }
+  .ubc-name {
+    margin-left: 0.5in;
+    white-space: pre;
+  }
+  </style>
+
+</head>
+
+<body lang="EN-US">
+
+<p><b>Eclipse Distribution License - v 1.0</b></p>
+
+<p>Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors. </p>
+
+<p>All rights reserved.</p>
+<p>Redistribution and use in source and binary forms, with or without modification, 
+	are permitted provided that the following conditions are met:
+<ul><li>Redistributions of source code must retain the above copyright notice, 
+	this list of conditions and the following disclaimer. </li>
+<li>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. </li>
+<li>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. </li></ul>
+</p>
+<p>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.</p>
+
+<hr>
+<p><b>SHA-1 UbcCheck - MIT</b></p>
+
+<p>Copyright (c) 2017:</p>
+<div class="ubc-name">
+Marc Stevens
+Cryptology Group
+Centrum Wiskunde & Informatica
+P.O. Box 94079, 1090 GB Amsterdam, Netherlands
+marc@marc-stevens.nl
+</div>
+<div class="ubc-name">
+Dan Shumow
+Microsoft Research
+danshu@microsoft.com
+</div>
+<p>Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+</p>
+<ul><li>The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.</li></ul>
+<p>THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.</p>
+
+</body>
+
+</html>
diff --git a/org.eclipse.jgit.ssh.apache/build.properties b/org.eclipse.jgit.ssh.apache/build.properties
new file mode 100644
index 0000000..8148271
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/build.properties
@@ -0,0 +1,7 @@
+source.. = src/,\
+           resources/
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               plugin.properties,\
+               about.html
diff --git a/org.eclipse.jgit.ssh.apache/plugin.properties b/org.eclipse.jgit.ssh.apache/plugin.properties
new file mode 100644
index 0000000..8f3540c
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/plugin.properties
@@ -0,0 +1,2 @@
+plugin_name=JGit SSH support based on Apache MINA sshd
+provider_name=Eclipse JGit
diff --git a/org.eclipse.jgit.ssh.apache/pom.xml b/org.eclipse.jgit.ssh.apache/pom.xml
new file mode 100644
index 0000000..2254133
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/pom.xml
@@ -0,0 +1,256 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+   and other copyright owners as documented in the project's IP log.
+
+   This program and the accompanying materials are made available
+   under the terms of the Eclipse Distribution License v1.0 which
+   accompanies this distribution, is reproduced below, and is
+   available at http://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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.eclipse.jgit</groupId>
+    <artifactId>org.eclipse.jgit-parent</artifactId>
+    <version>5.2.3-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>org.eclipse.jgit.ssh.apache</artifactId>
+  <name>JGit - Apache sshd-based SSH support</name>
+
+  <description>
+    SSH support for JGit based on Apache MINA sshd
+  </description>
+
+  <properties>
+    <translate-qualifier/>
+    <source-bundle-manifest>${project.build.directory}/META-INF/SOURCE-MANIFEST.MF</source-bundle-manifest>
+    <eddsa-version>0.3.0</eddsa-version>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.sshd</groupId>
+      <artifactId>sshd-core</artifactId>
+      <version>${apache-sshd-version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.sshd</groupId>
+      <artifactId>sshd-sftp</artifactId>
+      <version>${apache-sshd-version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>net.i2p.crypto</groupId>
+      <artifactId>eddsa</artifactId>
+      <version>${eddsa-version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <sourceDirectory>src/</sourceDirectory>
+
+    <resources>
+      <resource>
+        <directory>.</directory>
+        <includes>
+          <include>plugin.properties</include>
+          <include>about.html</include>
+        </includes>
+      </resource>
+      <resource>
+        <directory>resources/</directory>
+      </resource>
+    </resources>
+
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-antrun-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>translate-source-qualifier</id>
+            <phase>generate-resources</phase>
+            <configuration>
+              <target>
+                <copy file="META-INF/SOURCE-MANIFEST.MF" tofile="${source-bundle-manifest}" overwrite="true" />
+                <replace file="${source-bundle-manifest}">
+                  <replacefilter token=".qualifier" value=".${maven.build.timestamp}" />
+                </replace>
+              </target>
+            </configuration>
+            <goals>
+              <goal>run</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-source-plugin</artifactId>
+          <inherited>true</inherited>
+          <executions>
+            <execution>
+              <id>attach-sources</id>
+              <phase>process-classes</phase>
+              <goals>
+                <goal>jar</goal>
+              </goals>
+            <configuration>
+              <archive>
+                <manifestFile>${source-bundle-manifest}</manifestFile>
+              </archive>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <artifactId>maven-jar-plugin</artifactId>
+        <configuration>
+          <archive>
+            <manifestFile>${bundle-manifest}</manifestFile>
+          </archive>
+        </configuration>
+      </plugin>
+
+      <plugin>
+          <groupId>com.github.siom79.japicmp</groupId>
+          <artifactId>japicmp-maven-plugin</artifactId>
+          <version>${japicmp-version}</version>
+          <configuration>
+              <oldVersion>
+                  <dependency>
+                      <groupId>${project.groupId}</groupId>
+                      <artifactId>${project.artifactId}</artifactId>
+                      <version>${jgit-last-release-version}</version>
+                  </dependency>
+              </oldVersion>
+              <newVersion>
+                  <file>
+                      <path>${project.build.directory}/${project.artifactId}-${project.version}.jar</path>
+                  </file>
+              </newVersion>
+              <parameter>
+                  <onlyModified>true</onlyModified>
+                  <includes>
+                      <include>org.eclipse.jgit.*</include>
+                  </includes>
+                  <accessModifier>public</accessModifier>
+                  <breakBuildOnModifications>false</breakBuildOnModifications>
+                  <breakBuildOnBinaryIncompatibleModifications>false</breakBuildOnBinaryIncompatibleModifications>
+                  <onlyBinaryIncompatible>false</onlyBinaryIncompatible>
+                  <includeSynthetic>false</includeSynthetic>
+                  <ignoreMissingClasses>false</ignoreMissingClasses>
+                  <skipPomModules>true</skipPomModules>
+              </parameter>
+              <skip>true</skip><!-- TODO: Enable after the first release -->
+          </configuration>
+          <executions>
+            <execution>
+             <phase>verify</phase>
+             <goals>
+               <goal>cmp</goal>
+             </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+
+  <reporting>
+    <plugins>
+      <plugin>
+          <groupId>com.github.siom79.japicmp</groupId>
+          <artifactId>japicmp-maven-plugin</artifactId>
+          <version>${japicmp-version}</version>
+          <reportSets>
+              <reportSet>
+                  <reports>
+                      <report>cmp-report</report>
+                  </reports>
+              </reportSet>
+          </reportSets>
+          <configuration>
+              <oldVersion>
+                  <dependency>
+                      <groupId>${project.groupId}</groupId>
+                      <artifactId>${project.artifactId}</artifactId>
+                      <version>${jgit-last-release-version}</version>
+                  </dependency>
+              </oldVersion>
+              <newVersion>
+                  <file>
+                      <path>${project.build.directory}/${project.artifactId}-${project.version}.jar</path>
+                  </file>
+              </newVersion>
+              <parameter>
+                  <onlyModified>true</onlyModified>
+                  <includes>
+                      <include>org.eclipse.jgit.*</include>
+                  </includes>
+                  <accessModifier>public</accessModifier>
+                  <breakBuildOnModifications>false</breakBuildOnModifications>
+                  <breakBuildOnBinaryIncompatibleModifications>false</breakBuildOnBinaryIncompatibleModifications>
+                  <onlyBinaryIncompatible>false</onlyBinaryIncompatible>
+                  <includeSynthetic>false</includeSynthetic>
+                  <ignoreMissingClasses>false</ignoreMissingClasses>
+                  <skipPomModules>true</skipPomModules>
+              </parameter>
+              <skip>true</skip><!-- TODO: Enable after the first release -->
+          </configuration>
+      </plugin>
+    </plugins>
+  </reporting>
+</project>
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
new file mode 100644
index 0000000..aa4e4cc
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties
@@ -0,0 +1,77 @@
+authenticationCanceled=Authentication canceled: no password
+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}''
+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}''
+ftpCloseFailed=Closing the SFTP channel failed
+gssapiFailure=GSS-API error for mechanism OID {0}
+gssapiInitFailure=GSS-API initialization failure for mechanism {0}
+gssapiUnexpectedMechanism=Server {0} replied with unknown mechanism name ''{1}'' in {2} authentication
+gssapiUnexpectedMessage=Received unexpected ssh message {1} in {0} authentication
+identityFileCannotDecrypt=Given passphrase cannot decrypt identity {0}
+identityFileNoKey=No keys found in identity {0}
+identityFileMultipleKeys=Multiple key pairs found in identity {0}
+identityFileUnsupportedFormat=Unsupported format in identity {0}
+kexServerKeyInvalid=Server key did not validate
+keyEncryptedMsg=Key ''{0}'' is encrypted. Enter the passphrase to decrypt it.
+keyEncryptedPrompt=Passphrase
+keyEncryptedRetry=Encrypted key ''{0}'' could not be decrypted. Enter the passphrase again.
+keyLoadFailed=Could not load key ''{0}''
+knownHostsCouldNotUpdate=Could not update known hosts file {0}
+knownHostsFileLockedRead=Could not read known hosts file (locked) {0}
+knownHostsFileLockedUpdate=Could not update known hosts file (locked) {0}
+knownHostsFileReadFailed=Failed to read known hosts file {0}
+knownHostsInvalidLine=Known hosts file {0} contains invalid line {1}
+knownHostsInvalidPath=Invalid path for known hosts file {0}
+knownHostsKeyFingerprints=The {0} key''s fingerprints are:
+knownHostsModifiedKeyAcceptPrompt=Accept this key and continue connecting all the same?
+knownHostsModifiedKeyDenyMsg=To resolve this add the correct host key to your known hosts file {0}
+knownHostsModifiedKeyStorePrompt=If so, also store the new key?
+knownHostsModifiedKeyWarning=WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!\n\
+The connection might be compromised (man-in-the-middle attack).\n\
+It is also possible that the {0} key of the host has just been changed.\n\
+The expected {1} key for host ''{2}'' has the fingerprints:\n\
+{3}\n\
+{4}\n\
+The {0} key actually received has the fingerprints:\n\
+{5}\n\
+{6}
+knownHostsRevokedKeyMsg=Host ''{0}'' sent a key that is marked as revoked in the known hosts file {1}.
+knownHostsUnknownKeyMsg=The authenticity of host ''{0}'' cannot be established.
+knownHostsUnknownKeyPrompt=Accept and store this key, and continue connecting?
+knownHostsUnknownKeyType=Cannot read server key from known hosts file {0}; line {1}
+knownHostsUserAskCreationMsg=File {0} does not exist.
+knownHostsUserAskCreationPrompt=Create file {0} ?
+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
+proxyPasswordPrompt=Proxy password
+proxySocksAuthenticationFailed=Authentication to SOCKS5 proxy {0} failed
+proxySocksFailureForbidden=SOCKS5 proxy {0}: connection to {1} not allowed by ruleset
+proxySocksFailureGeneral=SOCKS5 proxy {0}: general failure
+proxySocksFailureHostUnreachable=SOCKS5 proxy {0}: host unreachable {1}
+proxySocksFailureNetworkUnreachable=SOCKS5 proxy {0}: network unreachable {1}
+proxySocksFailureRefused=SOCKS5 proxy {0}: connection refused {1}
+proxySocksFailureTTL=TTL expired in SOCKS5 proxy connection {0}
+proxySocksFailureUnspecified=Unspecified failure in SOCKS5 proxy connection {0}
+proxySocksFailureUnsupportedAddress=SOCKS5 proxy {0} does not support address type
+proxySocksFailureUnsupportedCommand=SOCKS5 proxy {0} does not support CONNECT command
+proxySocksGssApiFailure=Cannot authenticate with GSS-API to SOCKS5 proxy {0}
+proxySocksGssApiMessageTooShort=SOCKS5 proxy {0} sent too short message
+proxySocksGssApiUnknownMessage=SOCKS5 proxy {0} sent unexpected GSS-API message type, expected 1, got {1}
+proxySocksGssApiVersionMismatch=SOCKS5 proxy {0} sent wrong GSS-API version number, expected 1, got {1}
+proxySocksNoRemoteHostName=Could not send remote address {0}
+proxySocksPasswordTooLong=Password for proxy {0} must be at most 255 bytes long, is {1} bytes
+proxySocksUnexpectedMessage=Unexpected message received from SOCKS5 proxy {0}; client state {1}: {2}
+proxySocksUnexpectedVersion=Expected SOCKS version 5, got {0}
+proxySocksUsernameTooLong=User name for proxy {0} must be at most 255 bytes long, is {1} bytes: {2}
+sessionCloseFailed=Closing the session failed
+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
+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/CachingKeyPairProvider.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/CachingKeyPairProvider.java
new file mode 100644
index 0000000..ad2ff52
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/CachingKeyPairProvider.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd;
+
+import static java.text.MessageFormat.format;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.concurrent.CancellationException;
+
+import org.eclipse.jgit.transport.sshd.KeyCache;
+
+/**
+ * A {@link EncryptedFileKeyPairProvider} that uses an external
+ * {@link KeyCache}.
+ */
+public class CachingKeyPairProvider extends EncryptedFileKeyPairProvider {
+
+	private final KeyCache cache;
+
+	/**
+	 * Creates a new {@link CachingKeyPairProvider} using the given
+	 * {@link KeyCache}. If the cache is {@code null}, this is a simple
+	 * {@link EncryptedFileKeyPairProvider}.
+	 *
+	 * @param paths
+	 *            to load keys from
+	 * @param cache
+	 *            to use, may be {@code null} if no external caching is desired
+	 */
+	public CachingKeyPairProvider(List<Path> paths, KeyCache cache) {
+		super(paths);
+		this.cache = cache;
+	}
+
+	@Override
+	protected Iterable<KeyPair> loadKeys(Collection<? extends Path> resources) {
+		if (resources.isEmpty()) {
+			return Collections.emptyList();
+		}
+		return () -> new CancellingKeyPairIterator(resources);
+	}
+
+	@Override
+	protected KeyPair doLoadKey(Path resource)
+			throws IOException, GeneralSecurityException {
+		// By calling doLoadKey(String, Path, FilePasswordProvider) instead of
+		// super.doLoadKey(Path) we can bypass the key caching in
+		// AbstractResourceKeyPairProvider, over which we have no real control.
+		String resourceId = resource.toString();
+		if (cache == null) {
+			return doLoadKey(resourceId, resource, getPasswordFinder());
+		}
+		Throwable t[] = { null };
+		KeyPair key = cache.get(resource, p -> {
+			try {
+				return doLoadKey(resourceId, p, getPasswordFinder());
+			} catch (IOException | GeneralSecurityException e) {
+				t[0] = e;
+				return null;
+			}
+		});
+		if (t[0] != null) {
+			if (t[0] instanceof CancellationException) {
+				throw (CancellationException) t[0];
+			}
+			throw new IOException(
+					format(SshdText.get().keyLoadFailed, resource), t[0]);
+		}
+		return key;
+	}
+
+	private class CancellingKeyPairIterator implements Iterator<KeyPair> {
+
+		private final Iterator<Path> paths;
+
+		private KeyPair nextItem;
+
+		private boolean nextSet;
+
+		public CancellingKeyPairIterator(Collection<? extends Path> resources) {
+			List<Path> copy = new ArrayList<>(resources.size());
+			copy.addAll(resources);
+			paths = copy.iterator();
+		}
+
+		@Override
+		public boolean hasNext() {
+			if (nextSet) {
+				return nextItem != null;
+			}
+			nextSet = true;
+			while (nextItem == null && paths.hasNext()) {
+				try {
+					nextItem = doLoadKey(paths.next());
+				} catch (CancellationException cancelled) {
+					throw cancelled;
+				} catch (Exception other) {
+					log.warn(other.toString());
+				}
+			}
+			return nextItem != null;
+		}
+
+		@Override
+		public KeyPair next() {
+			if (!nextSet && !hasNext()) {
+				throw new NoSuchElementException();
+			}
+			KeyPair result = nextItem;
+			nextItem = null;
+			nextSet = false;
+			if (result == null) {
+				throw new NoSuchElementException();
+			}
+			return result;
+		}
+
+	}
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/EncryptedFileKeyPairProvider.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/EncryptedFileKeyPairProvider.java
new file mode 100644
index 0000000..ff81989
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/EncryptedFileKeyPairProvider.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd;
+
+import static java.text.MessageFormat.format;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.security.auth.DestroyFailedException;
+
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser;
+import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.common.util.security.SecurityUtils;
+import org.eclipse.jgit.internal.transport.sshd.RepeatingFilePasswordProvider.ResourceDecodeResult;
+
+/**
+ * A {@link FileKeyPairProvider} that asks repeatedly for a passphrase for an
+ * encrypted private key if the {@link FilePasswordProvider} is a
+ * {@link RepeatingFilePasswordProvider}.
+ */
+public class EncryptedFileKeyPairProvider extends FileKeyPairProvider {
+
+	// TODO: remove this class once we're based on sshd > 2.1.0. See upstream
+	// issue SSHD-850 https://issues.apache.org/jira/browse/SSHD-850 and commit
+	// https://github.com/apache/mina-sshd/commit/f19bd2e34
+
+	/**
+	 * Creates a new {@link EncryptedFileKeyPairProvider} for the given
+	 * {@link Path}s.
+	 *
+	 * @param paths
+	 *            to read keys from
+	 */
+	public EncryptedFileKeyPairProvider(List<Path> paths) {
+		super(paths);
+	}
+
+	@Override
+	protected KeyPair doLoadKey(String resourceKey, InputStream inputStream,
+			FilePasswordProvider provider)
+			throws IOException, GeneralSecurityException {
+		if (!(provider instanceof RepeatingFilePasswordProvider)) {
+			return super.doLoadKey(resourceKey, inputStream, provider);
+		}
+		KeyPairResourceParser parser = SecurityUtils.getKeyPairResourceParser();
+		if (parser == null) {
+			// This is an internal configuration error, thus no translation.
+			throw new NoSuchProviderException(
+					"No registered key-pair resource parser"); //$NON-NLS-1$
+		}
+		RepeatingFilePasswordProvider realProvider = (RepeatingFilePasswordProvider) provider;
+		// Read the stream now so that we can process the content several
+		// times.
+		List<String> lines = IoUtils.readAllLines(inputStream);
+		Collection<KeyPair> ids = null;
+		while (ids == null) {
+			try {
+				ids = parser.loadKeyPairs(resourceKey, realProvider, lines);
+				realProvider.handleDecodeAttemptResult(resourceKey, "", null); //$NON-NLS-1$
+				// No exception; success. Exit the loop even if ids is still
+				// null!
+				break;
+			} catch (IOException | GeneralSecurityException
+					| RuntimeException e) {
+				ResourceDecodeResult loadResult = realProvider
+						.handleDecodeAttemptResult(resourceKey, "", e); //$NON-NLS-1$
+				if (loadResult == null
+						|| loadResult == ResourceDecodeResult.TERMINATE) {
+					throw e;
+				} else if (loadResult == ResourceDecodeResult.RETRY) {
+					continue;
+				}
+				// IGNORE doesn't make any sense here, but OK, let's ignore it.
+				// ids == null, so we'll throw an exception below.
+				break;
+			}
+		}
+		if (ids == null) {
+			// The javadoc on loadKeyPairs says it might return null if no
+			// key pair found. Bad API.
+			throw new InvalidKeyException(
+					format(SshdText.get().identityFileNoKey, resourceKey));
+		}
+		Iterator<KeyPair> keys = ids.iterator();
+		if (!keys.hasNext()) {
+			throw new InvalidKeyException(format(
+					SshdText.get().identityFileUnsupportedFormat, resourceKey));
+		}
+		KeyPair result = keys.next();
+		if (keys.hasNext()) {
+			log.warn(format(SshdText.get().identityFileMultipleKeys,
+					resourceKey));
+			keys.forEachRemaining(k -> {
+				PrivateKey pk = k.getPrivate();
+				if (pk != null) {
+					try {
+						pk.destroy();
+					} catch (DestroyFailedException e) {
+						// Ignore
+					}
+				}
+			});
+		}
+		return result;
+	}
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/GssApiMechanisms.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/GssApiMechanisms.java
new file mode 100644
index 0000000..cf68eac
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/GssApiMechanisms.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.ietf.jgss.Oid;
+
+/**
+ * Global repository of GSS-API mechanisms that we can use.
+ */
+public class GssApiMechanisms {
+
+	private GssApiMechanisms() {
+		// No instantiation
+	}
+
+	/** Prefix to use with {@link GSSName#NT_HOSTBASED_SERVICE}. */
+	public static final String GSSAPI_HOST_PREFIX = "host@"; //$NON-NLS-1$
+
+	/** The {@link Oid} of Kerberos 5. */
+	public static final Oid KERBEROS_5 = createOid("1.2.840.113554.1.2.2"); //$NON-NLS-1$
+
+	/** SGNEGO is not to be used with ssh. */
+	public static final Oid SPNEGO = createOid("1.3.6.1.5.5.2"); //$NON-NLS-1$
+
+	/** Protects {@link #supportedMechanisms}. */
+	private static final Object LOCK = new Object();
+
+	/**
+	 * The {@link AtomicBoolean} is set to {@code true} when the mechanism could
+	 * be initialized successfully at least once.
+	 */
+	private static Map<Oid, Boolean> supportedMechanisms;
+
+	/**
+	 * Retrieves an immutable collection of the supported mechanisms.
+	 *
+	 * @return the supported mechanisms
+	 */
+	@NonNull
+	public static Collection<Oid> getSupportedMechanisms() {
+		synchronized (LOCK) {
+			if (supportedMechanisms == null) {
+				GSSManager manager = GSSManager.getInstance();
+				Oid[] mechs = manager.getMechs();
+				Map<Oid, Boolean> mechanisms = new LinkedHashMap<>();
+				if (mechs != null) {
+					for (Oid oid : mechs) {
+						mechanisms.put(oid, Boolean.FALSE);
+					}
+				}
+				supportedMechanisms = mechanisms;
+			}
+			return Collections.unmodifiableSet(supportedMechanisms.keySet());
+		}
+	}
+
+	/**
+	 * Report that this mechanism was used successfully.
+	 *
+	 * @param mechanism
+	 *            that worked
+	 */
+	public static void worked(@NonNull Oid mechanism) {
+		synchronized (LOCK) {
+			supportedMechanisms.put(mechanism, Boolean.TRUE);
+		}
+	}
+
+	/**
+	 * Mark the mechanisms as failed.
+	 *
+	 * @param mechanism
+	 *            to mark
+	 */
+	public static void failed(@NonNull Oid mechanism) {
+		synchronized (LOCK) {
+			Boolean worked = supportedMechanisms.get(mechanism);
+			if (worked != null && !worked.booleanValue()) {
+				// If it never worked, remove it
+				supportedMechanisms.remove(mechanism);
+			}
+		}
+	}
+
+	/**
+	 * Resolves an {@link InetSocketAddress}.
+	 *
+	 * @param remote
+	 *            to resolve
+	 * @return the resolved {@link InetAddress}, or {@code null} if unresolved.
+	 */
+	public static InetAddress resolve(@NonNull InetSocketAddress remote) {
+		InetAddress address = remote.getAddress();
+		if (address == null) {
+			try {
+				address = InetAddress.getByName(remote.getHostString());
+			} catch (UnknownHostException e) {
+				return null;
+			}
+		}
+		return address;
+	}
+
+	/**
+	 * Determines a canonical host name for use use with GSS-API.
+	 *
+	 * @param remote
+	 *            to get the host name from
+	 * @return the canonical host name, if it can be determined, otherwise the
+	 *         {@link InetSocketAddress#getHostString() unprocessed host name}.
+	 */
+	@NonNull
+	public static String getCanonicalName(@NonNull InetSocketAddress remote) {
+		InetAddress address = resolve(remote);
+		if (address == null) {
+			return remote.getHostString();
+		}
+		return address.getCanonicalHostName();
+	}
+
+	/**
+	 * Creates a {@link GSSContext} for the given mechanism to authenticate with
+	 * the host given by {@code fqdn}.
+	 *
+	 * @param mechanism
+	 *            {@link Oid} of the mechanism to use
+	 * @param fqdn
+	 *            fully qualified domain name of the host to authenticate with
+	 * @return the context, if the mechanism is available and the context could
+	 *         be created, or {@code null} otherwise
+	 */
+	public static GSSContext createContext(@NonNull Oid mechanism,
+			@NonNull String fqdn) {
+		GSSContext context = null;
+		try {
+			GSSManager manager = GSSManager.getInstance();
+			context = manager.createContext(
+					manager.createName(
+							GssApiMechanisms.GSSAPI_HOST_PREFIX + fqdn,
+							GSSName.NT_HOSTBASED_SERVICE),
+					mechanism, null, GSSContext.DEFAULT_LIFETIME);
+		} catch (GSSException e) {
+			closeContextSilently(context);
+			failed(mechanism);
+			return null;
+		}
+		worked(mechanism);
+		return context;
+	}
+
+	/**
+	 * Closes (disposes of) a {@link GSSContext} ignoring any
+	 * {@link GSSException}s.
+	 *
+	 * @param context
+	 *            to dispose
+	 */
+	public static void closeContextSilently(GSSContext context) {
+		if (context != null) {
+			try {
+				context.dispose();
+			} catch (GSSException e) {
+				// Ignore
+			}
+		}
+	}
+
+	private static Oid createOid(String rep) {
+		try {
+			return new Oid(rep);
+		} catch (GSSException e) {
+			// Does not occur
+			return null;
+		}
+	}
+
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/GssApiWithMicAuthFactory.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/GssApiWithMicAuthFactory.java
new file mode 100644
index 0000000..ba56305
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/GssApiWithMicAuthFactory.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd;
+
+import org.apache.sshd.client.auth.AbstractUserAuthFactory;
+import org.apache.sshd.client.auth.UserAuth;
+
+/**
+ * Factory to create {@link GssApiWithMicAuthentication} handlers.
+ */
+public class GssApiWithMicAuthFactory extends AbstractUserAuthFactory {
+
+	/** The authentication identifier for GSSApi-with-MIC. */
+	public static final String NAME = "gssapi-with-mic"; //$NON-NLS-1$
+
+	/** The singleton {@link GssApiWithMicAuthFactory}. */
+	public static final GssApiWithMicAuthFactory INSTANCE = new GssApiWithMicAuthFactory();
+
+	private GssApiWithMicAuthFactory() {
+		super(NAME);
+	}
+
+	@Override
+	public UserAuth create() {
+		return new GssApiWithMicAuthentication();
+	}
+
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/GssApiWithMicAuthentication.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/GssApiWithMicAuthentication.java
new file mode 100644
index 0000000..aef263d
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/GssApiWithMicAuthentication.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd;
+
+import static java.text.MessageFormat.format;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.net.UnknownHostException;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.apache.sshd.client.auth.AbstractUserAuth;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.SshConstants;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.MessageProp;
+import org.ietf.jgss.Oid;
+
+/**
+ * GSSAPI-with-MIC authentication handler (Kerberos 5).
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc4462">RFC 4462</a>
+ */
+public class GssApiWithMicAuthentication extends AbstractUserAuth {
+
+	/** Synonym used in RFC 4462. */
+	private static final byte SSH_MSG_USERAUTH_GSSAPI_RESPONSE = SshConstants.SSH_MSG_USERAUTH_INFO_REQUEST;
+
+	/** Synonym used in RFC 4462. */
+	private static final byte SSH_MSG_USERAUTH_GSSAPI_TOKEN = SshConstants.SSH_MSG_USERAUTH_INFO_RESPONSE;
+
+	private enum ProtocolState {
+		STARTED, TOKENS, MIC_SENT, FAILED
+	}
+
+	private Collection<Oid> mechanisms;
+
+	private Iterator<Oid> nextMechanism;
+
+	private Oid currentMechanism;
+
+	private ProtocolState state;
+
+	private GSSContext context;
+
+	/** Creates a new {@link GssApiWithMicAuthentication}. */
+	public GssApiWithMicAuthentication() {
+		super(GssApiWithMicAuthFactory.NAME);
+	}
+
+	@Override
+	protected boolean sendAuthDataRequest(ClientSession session, String service)
+			throws Exception {
+		if (mechanisms == null) {
+			mechanisms = GssApiMechanisms.getSupportedMechanisms();
+			nextMechanism = mechanisms.iterator();
+		}
+		if (context != null) {
+			close(false);
+		}
+		if (!nextMechanism.hasNext()) {
+			return false;
+		}
+		state = ProtocolState.STARTED;
+		currentMechanism = nextMechanism.next();
+		// RFC 4462 states that SPNEGO must not be used with ssh
+		while (GssApiMechanisms.SPNEGO.equals(currentMechanism)) {
+			if (!nextMechanism.hasNext()) {
+				return false;
+			}
+			currentMechanism = nextMechanism.next();
+		}
+		try {
+			String hostName = getHostName(session);
+			context = GssApiMechanisms.createContext(currentMechanism,
+					hostName);
+			context.requestMutualAuth(true);
+			context.requestConf(true);
+			context.requestInteg(true);
+			context.requestCredDeleg(true);
+			context.requestAnonymity(false);
+		} catch (GSSException | NullPointerException e) {
+			close(true);
+			if (log.isDebugEnabled()) {
+				log.debug(format(SshdText.get().gssapiInitFailure,
+						currentMechanism.toString()));
+			}
+			currentMechanism = null;
+			state = ProtocolState.FAILED;
+			return false;
+		}
+		Buffer buffer = session
+				.createBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST);
+		buffer.putString(session.getUsername());
+		buffer.putString(service);
+		buffer.putString(getName());
+		buffer.putInt(1);
+		buffer.putBytes(currentMechanism.getDER());
+		session.writePacket(buffer);
+		return true;
+	}
+
+	@Override
+	protected boolean processAuthDataRequest(ClientSession session,
+			String service, Buffer in) throws Exception {
+		// SSH_MSG_USERAUTH_FAILURE and SSH_MSG_USERAUTH_SUCCESS, as well as
+		// SSH_MSG_USERAUTH_BANNER are handled by the framework.
+		int command = in.getUByte();
+		if (context == null) {
+			return false;
+		}
+		try {
+			switch (command) {
+			case SSH_MSG_USERAUTH_GSSAPI_RESPONSE: {
+				if (state != ProtocolState.STARTED) {
+					return unexpectedMessage(command);
+				}
+				// Initial reply from the server with the mechanism to use.
+				Oid mechanism = new Oid(in.getBytes());
+				if (!currentMechanism.equals(mechanism)) {
+					return false;
+				}
+				replyToken(session, service, new byte[0]);
+				return true;
+			}
+			case SSH_MSG_USERAUTH_GSSAPI_TOKEN: {
+				if (context.isEstablished() || state != ProtocolState.TOKENS) {
+					return unexpectedMessage(command);
+				}
+				// Server sent us a token
+				replyToken(session, service, in.getBytes());
+				return true;
+			}
+			default:
+				return unexpectedMessage(command);
+			}
+		} catch (GSSException e) {
+			log.warn(format(SshdText.get().gssapiFailure,
+					currentMechanism.toString()), e);
+			state = ProtocolState.FAILED;
+			return false;
+		}
+	}
+
+	@Override
+	public void destroy() {
+		try {
+			close(false);
+		} finally {
+			super.destroy();
+		}
+	}
+
+	private void close(boolean silent) {
+		try {
+			if (context != null) {
+				context.dispose();
+				context = null;
+			}
+		} catch (GSSException e) {
+			if (!silent) {
+				log.warn(SshdText.get().gssapiFailure, e);
+			}
+		}
+	}
+
+	private void sendToken(ClientSession session, byte[] receivedToken)
+			throws IOException, GSSException {
+		state = ProtocolState.TOKENS;
+		byte[] token = context.initSecContext(receivedToken, 0,
+				receivedToken.length);
+		if (token != null) {
+			Buffer buffer = session.createBuffer(SSH_MSG_USERAUTH_GSSAPI_TOKEN);
+			buffer.putBytes(token);
+			session.writePacket(buffer);
+		}
+	}
+
+	private void sendMic(ClientSession session, String service)
+			throws IOException, GSSException {
+		state = ProtocolState.MIC_SENT;
+		// Produce MIC
+		Buffer micBuffer = new ByteArrayBuffer();
+		micBuffer.putBytes(session.getSessionId());
+		micBuffer.putByte(SshConstants.SSH_MSG_USERAUTH_REQUEST);
+		micBuffer.putString(session.getUsername());
+		micBuffer.putString(service);
+		micBuffer.putString(getName());
+		byte[] micBytes = micBuffer.getCompactData();
+		byte[] mic = context.getMIC(micBytes, 0, micBytes.length,
+				new MessageProp(0, true));
+		Buffer buffer = session
+				.createBuffer(SshConstants.SSH_MSG_USERAUTH_GSSAPI_MIC);
+		buffer.putBytes(mic);
+		session.writePacket(buffer);
+	}
+
+	private void replyToken(ClientSession session, String service, byte[] bytes)
+			throws IOException, GSSException {
+		sendToken(session, bytes);
+		if (context.isEstablished()) {
+			sendMic(session, service);
+		}
+	}
+
+	private String getHostName(ClientSession session) {
+		SocketAddress remote = session.getConnectAddress();
+		if (remote instanceof InetSocketAddress) {
+			InetAddress address = GssApiMechanisms
+					.resolve((InetSocketAddress) remote);
+			if (address != null) {
+				return address.getCanonicalHostName();
+			}
+		}
+		if (session instanceof JGitClientSession) {
+			String hostName = ((JGitClientSession) session).getHostConfigEntry()
+					.getHostName();
+			try {
+				hostName = InetAddress.getByName(hostName)
+						.getCanonicalHostName();
+			} catch (UnknownHostException e) {
+				// Ignore here; try with the non-canonical name
+			}
+			return hostName;
+		}
+		throw new IllegalStateException(
+				"Wrong session class :" + session.getClass().getName()); //$NON-NLS-1$
+	}
+
+	private boolean unexpectedMessage(int command) {
+		log.warn(format(SshdText.get().gssapiUnexpectedMessage, getName(),
+				Integer.toString(command)));
+		return false;
+	}
+
+}
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
new file mode 100644
index 0000000..dcf330a
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd;
+
+import static java.text.MessageFormat.format;
+
+import java.io.IOException;
+import java.net.SocketAddress;
+import java.security.PublicKey;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.sshd.client.ClientFactoryManager;
+import org.apache.sshd.client.config.hosts.HostConfigEntry;
+import org.apache.sshd.client.keyverifier.KnownHostsServerKeyVerifier.HostEntryPair;
+import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
+import org.apache.sshd.client.session.ClientSessionImpl;
+import org.apache.sshd.common.FactoryManager;
+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.util.Readable;
+import org.eclipse.jgit.errors.InvalidPatternException;
+import org.eclipse.jgit.fnmatch.FileNameMatcher;
+import org.eclipse.jgit.internal.transport.sshd.proxy.StatefulProxyConnector;
+import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.SshConstants;
+
+/**
+ * A {@link org.apache.sshd.client.session.ClientSession ClientSession} that can
+ * be associated with the {@link HostConfigEntry} the session was created for.
+ * The {@link JGitSshClient} creates such sessions and sets this association.
+ * <p>
+ * Also provides for associating a JGit {@link CredentialsProvider} with a
+ * session.
+ * </p>
+ */
+public class JGitClientSession extends ClientSessionImpl {
+
+	private HostConfigEntry hostConfig;
+
+	private CredentialsProvider credentialsProvider;
+
+	private StatefulProxyConnector proxyHandler;
+
+	/**
+	 * @param manager
+	 * @param session
+	 * @throws Exception
+	 */
+	public JGitClientSession(ClientFactoryManager manager, IoSession session)
+			throws Exception {
+		super(manager, session);
+	}
+
+	/**
+	 * Retrieves the {@link HostConfigEntry} this session was created for.
+	 *
+	 * @return the {@link HostConfigEntry}, or {@code null} if none set
+	 */
+	public HostConfigEntry getHostConfigEntry() {
+		return hostConfig;
+	}
+
+	/**
+	 * Sets the {@link HostConfigEntry} this session was created for.
+	 *
+	 * @param hostConfig
+	 *            the {@link HostConfigEntry}
+	 */
+	public void setHostConfigEntry(HostConfigEntry hostConfig) {
+		this.hostConfig = hostConfig;
+	}
+
+	/**
+	 * Sets the {@link CredentialsProvider} for this session.
+	 *
+	 * @param provider
+	 *            to set
+	 */
+	public void setCredentialsProvider(CredentialsProvider provider) {
+		credentialsProvider = provider;
+	}
+
+	/**
+	 * Retrieves the {@link CredentialsProvider} set for this session.
+	 *
+	 * @return the provider, or {@code null} if none is set.
+	 */
+	public CredentialsProvider getCredentialsProvider() {
+		return credentialsProvider;
+	}
+
+	/**
+	 * Sets a {@link StatefulProxyConnector} to handle proxy connection
+	 * protocols.
+	 *
+	 * @param handler
+	 *            to set
+	 */
+	public void setProxyHandler(StatefulProxyConnector handler) {
+		proxyHandler = handler;
+	}
+
+	@Override
+	protected IoWriteFuture sendIdentification(String ident)
+			throws IOException {
+		StatefulProxyConnector proxy = proxyHandler;
+		if (proxy != null) {
+			try {
+				// We must not block here; the framework starts reading messages
+				// from the peer only once the initial sendKexInit() following
+				// this call to sendIdentification() has returned!
+				proxy.runWhenDone(() -> {
+					JGitClientSession.super.sendIdentification(ident);
+					return null;
+				});
+				// Called only from the ClientSessionImpl constructor, where the
+				// return value is ignored.
+				return null;
+			} catch (IOException e) {
+				throw e;
+			} catch (Exception other) {
+				throw new IOException(other.getLocalizedMessage(), other);
+			}
+		} else {
+			return super.sendIdentification(ident);
+		}
+	}
+
+	@Override
+	protected byte[] sendKexInit() throws IOException {
+		StatefulProxyConnector proxy = proxyHandler;
+		if (proxy != null) {
+			try {
+				// We must not block here; the framework starts reading messages
+				// from the peer only once the initial sendKexInit() has
+				// returned!
+				proxy.runWhenDone(() -> {
+					JGitClientSession.super.sendKexInit();
+					return null;
+				});
+				// This is called only from the ClientSessionImpl
+				// constructor, where the return value is ignored.
+				return null;
+			} catch (IOException e) {
+				throw e;
+			} catch (Exception other) {
+				throw new IOException(other.getLocalizedMessage(), other);
+			}
+		} else {
+			return super.sendKexInit();
+		}
+	}
+
+	/**
+	 * {@inheritDoc}
+	 *
+	 * As long as we're still setting up the proxy connection, diverts messages
+	 * to the {@link StatefulProxyConnector}.
+	 */
+	@Override
+	public void messageReceived(Readable buffer) throws Exception {
+		StatefulProxyConnector proxy = proxyHandler;
+		if (proxy != null) {
+			proxy.messageReceived(getIoSession(), buffer);
+		} else {
+			super.messageReceived(buffer);
+		}
+	}
+
+	@Override
+	protected void checkKeys() throws SshException {
+		ServerKeyVerifier serverKeyVerifier = getServerKeyVerifier();
+		// The super implementation always uses
+		// getIoSession().getRemoteAddress(). In case of a proxy connection,
+		// that would be the address of the proxy!
+		SocketAddress remoteAddress = getConnectAddress();
+		PublicKey serverKey = getKex().getServerKey();
+		if (!serverKeyVerifier.verifyServerKey(this, remoteAddress,
+				serverKey)) {
+			throw new SshException(
+					org.apache.sshd.common.SshConstants.SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE,
+					SshdText.get().kexServerKeyInvalid);
+		}
+	}
+
+	@Override
+	protected String resolveAvailableSignaturesProposal(
+			FactoryManager manager) {
+		Set<String> defaultSignatures = new LinkedHashSet<>();
+		defaultSignatures.addAll(getSignatureFactoriesNames());
+		HostConfigEntry config = resolveAttribute(
+				JGitSshClient.HOST_CONFIG_ENTRY);
+		String hostKeyAlgorithms = config
+				.getProperty(SshConstants.HOST_KEY_ALGORITHMS);
+		if (hostKeyAlgorithms != null && !hostKeyAlgorithms.isEmpty()) {
+			char first = hostKeyAlgorithms.charAt(0);
+			if (first == '+') {
+				// Additions make not much sense -- it's either in
+				// defaultSignatures already, or we have no implementation for
+				// it. No point in proposing it.
+				return String.join(",", defaultSignatures); //$NON-NLS-1$
+			} else if (first == '-') {
+				// This takes wildcard patterns!
+				removeFromList(defaultSignatures,
+						SshConstants.HOST_KEY_ALGORITHMS,
+						hostKeyAlgorithms.substring(1));
+				if (defaultSignatures.isEmpty()) {
+					// Too bad: user config error. Warn here, and then fail
+					// later.
+					log.warn(format(
+							SshdText.get().configNoRemainingHostKeyAlgorithms,
+							hostKeyAlgorithms));
+				}
+				return String.join(",", defaultSignatures); //$NON-NLS-1$
+			} else {
+				// Default is overridden -- only accept the ones for which we do
+				// have an implementation.
+				List<String> newNames = filteredList(defaultSignatures,
+						hostKeyAlgorithms);
+				if (newNames.isEmpty()) {
+					log.warn(format(
+							SshdText.get().configNoKnownHostKeyAlgorithms,
+							hostKeyAlgorithms));
+					// Use the default instead.
+				} else {
+					return String.join(",", newNames); //$NON-NLS-1$
+				}
+			}
+		}
+		// No HostKeyAlgorithms; using default -- change order to put existing
+		// keys first.
+		ServerKeyVerifier verifier = getServerKeyVerifier();
+		if (verifier instanceof ServerKeyLookup) {
+			SocketAddress remoteAddress = resolvePeerAddress(
+					resolveAttribute(JGitSshClient.ORIGINAL_REMOTE_ADDRESS));
+			List<HostEntryPair> allKnownKeys = ((ServerKeyLookup) verifier)
+					.lookup(this, remoteAddress);
+			Set<String> reordered = new LinkedHashSet<>();
+			for (HostEntryPair h : allKnownKeys) {
+				PublicKey key = h.getServerKey();
+				if (key != null) {
+					String keyType = KeyUtils.getKeyType(key);
+					if (keyType != null) {
+						reordered.add(keyType);
+					}
+				}
+			}
+			reordered.addAll(defaultSignatures);
+			return String.join(",", reordered); //$NON-NLS-1$
+		}
+		return String.join(",", defaultSignatures); //$NON-NLS-1$
+	}
+
+	private void removeFromList(Set<String> current, String key,
+			String patterns) {
+		for (String toRemove : patterns.split("\\s*,\\s*")) { //$NON-NLS-1$
+			if (toRemove.indexOf('*') < 0 && toRemove.indexOf('?') < 0) {
+				current.remove(toRemove);
+				continue;
+			}
+			try {
+				FileNameMatcher matcher = new FileNameMatcher(toRemove, null);
+				for (Iterator<String> i = current.iterator(); i.hasNext();) {
+					matcher.reset();
+					matcher.append(i.next());
+					if (matcher.isMatch()) {
+						i.remove();
+					}
+				}
+			} catch (InvalidPatternException e) {
+				log.warn(format(SshdText.get().configInvalidPattern, key,
+						toRemove));
+			}
+		}
+	}
+
+	private List<String> filteredList(Set<String> known, String values) {
+		List<String> newNames = new ArrayList<>();
+		for (String newValue : values.split("\\s*,\\s*")) { //$NON-NLS-1$
+			if (known.contains(newValue)) {
+				newNames.add(newValue);
+			}
+		}
+		return newNames;
+	}
+
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitHostConfigEntry.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitHostConfigEntry.java
new file mode 100644
index 0000000..a0705f2
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitHostConfigEntry.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.sshd.client.config.hosts.HostConfigEntry;
+import org.eclipse.jgit.annotations.NonNull;
+
+/**
+ * A {@link HostConfigEntry} that provides access to the multi-valued keys as
+ * lists of strings. The super class treats them as single strings containing
+ * comma-separated lists.
+ *
+ * @since 5.2
+ */
+public class JGitHostConfigEntry extends HostConfigEntry {
+
+	private Map<String, List<String>> multiValuedOptions;
+
+	@Override
+	public String getProperty(String name, String defaultValue) {
+		// Upstream bug fix (SSHD-867): if there are _no_ properties at all, the
+		// super implementation returns always null even if a default value is
+		// given.
+		//
+		// See https://issues.apache.org/jira/projects/SSHD/issues/SSHD-867
+		//
+		// TODO: remove this override once we're based on sshd > 2.1.0
+		Map<String, String> properties = getProperties();
+		if (properties == null || properties.isEmpty()) {
+			return defaultValue;
+		}
+		return super.getProperty(name, defaultValue);
+	}
+
+	/**
+	 * Sets the multi-valued options.
+	 *
+	 * @param options
+	 *            to set, may be {@code null} to set an empty map
+	 */
+	public void setMultiValuedOptions(Map<String, List<String>> options) {
+		multiValuedOptions = options;
+	}
+
+	/**
+	 * Retrieves all multi-valued options.
+	 *
+	 * @return an unmodifiable map
+	 */
+	@NonNull
+	public Map<String, List<String>> getMultiValuedOptions() {
+		Map<String, List<String>> options = multiValuedOptions;
+		if (options == null) {
+			return Collections.emptyMap();
+		}
+		return Collections.unmodifiableMap(options);
+	}
+
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPasswordAuthFactory.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPasswordAuthFactory.java
new file mode 100644
index 0000000..315d025
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPasswordAuthFactory.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd;
+
+import org.apache.sshd.client.auth.AbstractUserAuthFactory;
+import org.apache.sshd.client.auth.UserAuth;
+import org.apache.sshd.client.auth.password.UserAuthPasswordFactory;
+
+/**
+ * A customized {@link UserAuthPasswordFactory} that creates instance of
+ * {@link JGitPasswordAuthentication}.
+ */
+public class JGitPasswordAuthFactory extends AbstractUserAuthFactory {
+
+	/** The singleton {@link JGitPasswordAuthFactory}. */
+	public static final JGitPasswordAuthFactory INSTANCE = new JGitPasswordAuthFactory();
+
+	private JGitPasswordAuthFactory() {
+		super(UserAuthPasswordFactory.NAME);
+	}
+
+	@Override
+	public UserAuth create() {
+		return new JGitPasswordAuthentication();
+	}
+}
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
new file mode 100644
index 0000000..9a6ed39
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPasswordAuthentication.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+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;
+import org.apache.sshd.client.session.ClientSession;
+
+/**
+ * A password authentication handler that uses the {@link JGitUserInteraction}
+ * to ask the user for the password. It also respects the
+ * {@code NumberOfPasswordPrompts} ssh config.
+ */
+public class JGitPasswordAuthentication extends UserAuthPassword {
+
+	private int maxAttempts;
+
+	private int attempts;
+
+	@Override
+	public void init(ClientSession session, String service) throws Exception {
+		super.init(session, service);
+		maxAttempts = Math.max(1,
+				session.getIntProperty(
+						ClientAuthenticationManager.PASSWORD_PROMPTS,
+						ClientAuthenticationManager.DEFAULT_PASSWORD_PROMPTS));
+		attempts = 0;
+	}
+
+	@Override
+	protected boolean sendAuthDataRequest(ClientSession session, String service)
+			throws Exception {
+		if (++attempts > maxAttempts) {
+			return false;
+		}
+		UserInteraction interaction = session.getUserInteraction();
+		if (!interaction.isInteractionAllowed(session)) {
+			return false;
+		}
+		String password = getPassword(session, interaction);
+		if (password == null) {
+			throw new CancellationException();
+		}
+		// sendPassword takes a buffer as first argument, but actually doesn't
+		// use it and creates its own buffer...
+		sendPassword(null, session, password, password);
+		return true;
+	}
+
+	private String getPassword(ClientSession session,
+			UserInteraction interaction) {
+		String[] results = interaction.interactive(session, null, null, "", //$NON-NLS-1$
+				new String[] { SshdText.get().passwordPrompt },
+				new boolean[] { false });
+		return (results == null || results.length == 0) ? null : results[0];
+	}
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthFactory.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthFactory.java
new file mode 100644
index 0000000..0b3de4a
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthFactory.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd;
+
+import java.util.List;
+
+import org.apache.sshd.client.auth.AbstractUserAuthFactory;
+import org.apache.sshd.client.auth.UserAuth;
+import org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyFactory;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.signature.Signature;
+import org.apache.sshd.common.signature.SignatureFactoriesManager;
+
+/**
+ * A customized authentication factory for public key user authentication. The
+ * default implementation {@link UserAuthPublicKeyFactory} ends up doing some
+ * crazy stream "magic" that loads too many keys too early.
+ */
+public class JGitPublicKeyAuthFactory extends AbstractUserAuthFactory
+		implements SignatureFactoriesManager {
+
+	/** The singleton {@link JGitPublicKeyAuthFactory}. */
+	public static final JGitPublicKeyAuthFactory INSTANCE = new JGitPublicKeyAuthFactory();
+
+	private JGitPublicKeyAuthFactory() {
+		super(UserAuthPublicKeyFactory.NAME);
+	}
+
+	@Override
+	public UserAuth create() {
+		return new JGitPublicKeyAuthentication(getSignatureFactories());
+	}
+
+	@Override
+	public List<NamedFactory<Signature>> getSignatureFactories() {
+		return null;
+	}
+
+	@Override
+	public void setSignatureFactories(List<NamedFactory<Signature>> factories) {
+		throw new UnsupportedOperationException();
+	}
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthentication.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthentication.java
new file mode 100644
index 0000000..63b3990
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthentication.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd;
+
+import java.util.List;
+
+import org.apache.sshd.client.auth.pubkey.UserAuthPublicKey;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.signature.Signature;
+
+/**
+ * A specialized public key authentication handler that uses our own
+ * {@link JGitPublicKeyIterator}. The super class creates in
+ * {@link #init(ClientSession, String)} a
+ * {@link org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyIterator}, which
+ * in its constructor does some strange {@link java.util.stream.Stream} "magic"
+ * that ends up loading keys prematurely.
+ */
+public class JGitPublicKeyAuthentication extends UserAuthPublicKey {
+
+	private ClientSession clientSession;
+
+	private String serviceName;
+
+	/**
+	 * Creates a new {@link JGitPublicKeyAuthentication}.
+	 *
+	 * @param factories
+	 *            signature factories to use
+	 */
+	public JGitPublicKeyAuthentication(
+			List<NamedFactory<Signature>> factories) {
+		super(factories);
+	}
+
+	@Override
+	public void init(ClientSession session, String service) throws Exception {
+		// Do *not* call super.init(); it'll create a UserAuthPublicKeyIterator
+		// and that's where things then go wrong. Instead, do the whole
+		// initialization directly here.
+		clientSession = session;
+		serviceName = service;
+		releaseKeys();
+		// Use our own iterator!
+		keys = new JGitPublicKeyIterator(session, this);
+	}
+
+	@Override
+	public ClientSession getClientSession() {
+		return clientSession;
+	}
+
+	@Override
+	public ClientSession getSession() {
+		return clientSession;
+	}
+
+	@Override
+	public String getService() {
+		return serviceName;
+	}
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyIterator.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyIterator.java
new file mode 100644
index 0000000..cda1262
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyIterator.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd;
+
+import java.io.IOException;
+import java.nio.channels.Channel;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.sshd.agent.SshAgent;
+import org.apache.sshd.agent.SshAgentFactory;
+import org.apache.sshd.client.auth.pubkey.AbstractKeyPairIterator;
+import org.apache.sshd.client.auth.pubkey.KeyAgentIdentity;
+import org.apache.sshd.client.auth.pubkey.KeyPairIdentity;
+import org.apache.sshd.client.auth.pubkey.PublicKeyIdentity;
+import org.apache.sshd.client.config.hosts.HostConfigEntry;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.FactoryManager;
+import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
+import org.apache.sshd.common.signature.SignatureFactoriesManager;
+
+/**
+ * A new iterator over key pairs that we use instead of the default
+ * {@link org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyIterator}, which
+ * in its constructor does some strange {@link java.util.stream.Stream} "magic"
+ * that ends up loading keys prematurely. This class uses plain
+ * {@link Iterator}s instead to avoid that problem. Used in
+ * {@link JGitPublicKeyAuthentication}.
+ *
+ * @see <a href=
+ *      "https://issues.apache.org/jira/projects/SSHD/issues/SSHD-860">Upstream
+ *      issue SSHD-860</a>
+ */
+public class JGitPublicKeyIterator
+		extends AbstractKeyPairIterator<PublicKeyIdentity> implements Channel {
+
+	// Re: the cause for the problem mentioned above has not been determined.
+	// It looks as if either the Apache code inadvertently calls
+	// GenericUtils.isEmpty() on all streams (which would load the first key
+	// of each stream), or the Java stream implementation does some prefetching.
+	// It's not entirely clear. Using Iterators we have more control over
+	// what happens when.
+
+	private final AtomicBoolean open = new AtomicBoolean(true);
+
+	private SshAgent agent;
+
+	private final List<Iterator<PublicKeyIdentity>> keys = new ArrayList<>(3);
+
+	private final Iterator<Iterator<PublicKeyIdentity>> keyIter;
+
+	private Iterator<PublicKeyIdentity> current;
+
+	private Boolean hasElement;
+
+	/**
+	 * Creates a new {@link JGitPublicKeyIterator}.
+	 *
+	 * @param session
+	 *            we're trying to authenticate
+	 * @param signatureFactories
+	 *            to use
+	 * @throws Exception
+	 *             if an {@link SshAgentFactory} is configured and getting
+	 *             identities from the agent fails
+	 */
+	public JGitPublicKeyIterator(ClientSession session,
+			SignatureFactoriesManager signatureFactories) throws Exception {
+		super(session);
+		boolean useAgent = true;
+		if (session instanceof JGitClientSession) {
+			HostConfigEntry config = ((JGitClientSession) session)
+					.getHostConfigEntry();
+			useAgent = !config.isIdentitiesOnly();
+		}
+		if (useAgent) {
+			FactoryManager manager = session.getFactoryManager();
+			SshAgentFactory factory = manager == null ? null
+					: manager.getAgentFactory();
+			if (factory != null) {
+				try {
+					agent = factory.createClient(manager);
+					keys.add(new AgentIdentityIterator(agent));
+				} catch (IOException e) {
+					try {
+						closeAgent();
+					} catch (IOException err) {
+						e.addSuppressed(err);
+					}
+					throw e;
+				}
+			}
+		}
+		keys.add(
+				new KeyPairIdentityIterator(session.getRegisteredIdentities(),
+						session, signatureFactories));
+		keys.add(new KeyPairIdentityIterator(session.getKeyPairProvider(),
+				session, signatureFactories));
+		keyIter = keys.iterator();
+	}
+
+	@Override
+	public boolean isOpen() {
+		return open.get();
+	}
+
+	@Override
+	public void close() throws IOException {
+		if (open.getAndSet(false)) {
+			closeAgent();
+		}
+	}
+
+	@Override
+	public boolean hasNext() {
+		if (!isOpen()) {
+			return false;
+		}
+		if (hasElement != null) {
+			return hasElement.booleanValue();
+		}
+		while (current == null || !current.hasNext()) {
+			if (keyIter.hasNext()) {
+				current = keyIter.next();
+			} else {
+				current = null;
+				hasElement = Boolean.FALSE;
+				return false;
+			}
+		}
+		hasElement = Boolean.TRUE;
+		return true;
+	}
+
+	@Override
+	public PublicKeyIdentity next() {
+		if (!isOpen() || hasElement == null && !hasNext()
+				|| !hasElement.booleanValue()) {
+			throw new NoSuchElementException();
+		}
+		hasElement = null;
+		PublicKeyIdentity result;
+		try {
+			result = current.next();
+		} catch (NoSuchElementException e) {
+			result = null;
+		}
+		return result;
+	}
+
+	private void closeAgent() throws IOException {
+		if (agent == null) {
+			return;
+		}
+		try {
+			agent.close();
+		} finally {
+			agent = null;
+		}
+	}
+
+	/**
+	 * An {@link Iterator} that maps the data obtained from an agent to
+	 * {@link PublicKeyIdentity}.
+	 */
+	private static class AgentIdentityIterator
+			implements Iterator<PublicKeyIdentity> {
+
+		private final SshAgent agent;
+
+		private final Iterator<? extends Map.Entry<PublicKey, String>> iter;
+
+		public AgentIdentityIterator(SshAgent agent) throws IOException {
+			this.agent = agent;
+			iter = agent == null ? null : agent.getIdentities().iterator();
+		}
+
+		@Override
+		public boolean hasNext() {
+			return iter != null && iter.hasNext();
+		}
+
+		@Override
+		public PublicKeyIdentity next() {
+			if (iter == null) {
+				throw new NoSuchElementException();
+			}
+			Map.Entry<PublicKey, String> entry = iter.next();
+			return new KeyAgentIdentity(agent, entry.getKey(),
+					entry.getValue());
+		}
+	}
+
+	/**
+	 * An {@link Iterator} that maps {@link KeyPair} to
+	 * {@link PublicKeyIdentity}.
+	 */
+	private static class KeyPairIdentityIterator
+			implements Iterator<PublicKeyIdentity> {
+
+		private final Iterator<KeyPair> keyPairs;
+
+		private final ClientSession session;
+
+		private final SignatureFactoriesManager signatureFactories;
+
+		public KeyPairIdentityIterator(KeyIdentityProvider provider,
+				ClientSession session,
+				SignatureFactoriesManager signatureFactories) {
+			this.session = session;
+			this.signatureFactories = signatureFactories;
+			keyPairs = provider == null ? null : provider.loadKeys().iterator();
+		}
+
+		@Override
+		public boolean hasNext() {
+			return keyPairs != null && keyPairs.hasNext();
+		}
+
+		@Override
+		public PublicKeyIdentity next() {
+			if (keyPairs == null) {
+				throw new NoSuchElementException();
+			}
+			KeyPair key = keyPairs.next();
+			return new KeyPairIdentity(signatureFactories, session, key);
+		}
+	}
+}
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
new file mode 100644
index 0000000..212b67f
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java
@@ -0,0 +1,442 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd;
+
+import static java.text.MessageFormat.format;
+import static org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile.positive;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.nio.file.Files;
+import java.nio.file.InvalidPathException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.KeyPair;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.config.hosts.HostConfigEntry;
+import org.apache.sshd.client.future.ConnectFuture;
+import org.apache.sshd.client.future.DefaultConnectFuture;
+import org.apache.sshd.client.session.ClientSessionImpl;
+import org.apache.sshd.client.session.SessionFactory;
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.future.SshFutureListener;
+import org.apache.sshd.common.io.IoConnectFuture;
+import org.apache.sshd.common.io.IoSession;
+import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.session.helpers.AbstractSession;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.eclipse.jgit.internal.transport.sshd.proxy.HttpClientConnector;
+import org.eclipse.jgit.internal.transport.sshd.proxy.Socks5ClientConnector;
+import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.SshConstants;
+import org.eclipse.jgit.transport.sshd.KeyCache;
+import org.eclipse.jgit.transport.sshd.ProxyData;
+import org.eclipse.jgit.transport.sshd.ProxyDataFactory;
+
+/**
+ * Customized {@link SshClient} for JGit. It creates specialized
+ * {@link JGitClientSession}s that know about the {@link HostConfigEntry} they
+ * were created for, and it loads all KeyPair identities lazily.
+ */
+public class JGitSshClient extends SshClient {
+
+	/**
+	 * We need access to this during the constructor of the ClientSession,
+	 * before setConnectAddress() can have been called. So we have to remember
+	 * it in an attribute on the SshClient, from where we can then retrieve it.
+	 */
+	static final AttributeKey<HostConfigEntry> HOST_CONFIG_ENTRY = new AttributeKey<>();
+
+	static final AttributeKey<InetSocketAddress> ORIGINAL_REMOTE_ADDRESS = new AttributeKey<>();
+
+	/**
+	 * An attribute key for the comma-separated list of default preferred
+	 * authentication mechanisms.
+	 */
+	public static final AttributeKey<String> PREFERRED_AUTHENTICATIONS = new AttributeKey<>();
+
+	private KeyCache keyCache;
+
+	private CredentialsProvider credentialsProvider;
+
+	private ProxyDataFactory proxyDatabase;
+
+	@Override
+	protected SessionFactory createSessionFactory() {
+		// Override the parent's default
+		return new JGitSessionFactory(this);
+	}
+
+	@Override
+	public ConnectFuture connect(HostConfigEntry hostConfig)
+			throws IOException {
+		if (connector == null) {
+			throw new IllegalStateException("SshClient not started."); //$NON-NLS-1$
+		}
+		Objects.requireNonNull(hostConfig, "No host configuration"); //$NON-NLS-1$
+		String host = 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$
+		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();
+		}
+		connector.connect(address).addListener(listener);
+		return connectFuture;
+	}
+
+	private void copyProperty(String value, String key) {
+		if (value != null && !value.isEmpty()) {
+			getProperties().put(key, value);
+		}
+	}
+
+	private ProxyData getProxyData(InetSocketAddress remoteAddress) {
+		ProxyDataFactory factory = getProxyDatabase();
+		return factory == null ? null : factory.get(remoteAddress);
+	}
+
+	private InetSocketAddress configureProxy(ProxyData proxyData,
+			InetSocketAddress remoteAddress) {
+		Proxy proxy = proxyData.getProxy();
+		if (proxy.type() == Proxy.Type.DIRECT
+				|| !(proxy.address() instanceof InetSocketAddress)) {
+			return remoteAddress;
+		}
+		InetSocketAddress address = (InetSocketAddress) proxy.address();
+		switch (proxy.type()) {
+		case HTTP:
+			setClientProxyConnector(
+					new HttpClientConnector(address, remoteAddress,
+							proxyData.getUser(), proxyData.getPassword()));
+			return address;
+		case SOCKS:
+			setClientProxyConnector(
+					new Socks5ClientConnector(address, remoteAddress,
+							proxyData.getUser(), proxyData.getPassword()));
+			return address;
+		default:
+			log.warn(format(SshdText.get().unknownProxyProtocol,
+					proxy.type().name()));
+			return remoteAddress;
+		}
+	}
+
+	private SshFutureListener<IoConnectFuture> createConnectCompletionListener(
+			ConnectFuture connectFuture, String username,
+			InetSocketAddress address, HostConfigEntry hostConfig) {
+		return new SshFutureListener<IoConnectFuture>() {
+
+			@Override
+			public void operationComplete(IoConnectFuture future) {
+				if (future.isCanceled()) {
+					connectFuture.cancel();
+					return;
+				}
+				Throwable t = future.getException();
+				if (t != null) {
+					connectFuture.setException(t);
+					return;
+				}
+				IoSession ioSession = future.getSession();
+				try {
+					JGitClientSession session = createSession(ioSession,
+							username, address, hostConfig);
+					connectFuture.setSession(session);
+				} catch (RuntimeException e) {
+					connectFuture.setException(e);
+					ioSession.close(true);
+				}
+			}
+
+			@Override
+			public String toString() {
+				return "JGitSshClient$ConnectCompletionListener[" + username //$NON-NLS-1$
+						+ '@' + address + ']';
+			}
+		};
+	}
+
+	private JGitClientSession createSession(IoSession ioSession,
+			String username, InetSocketAddress address,
+			HostConfigEntry hostConfig) {
+		AbstractSession rawSession = AbstractSession.getSession(ioSession);
+		if (!(rawSession instanceof JGitClientSession)) {
+			throw new IllegalStateException("Wrong session type: " //$NON-NLS-1$
+					+ rawSession.getClass().getCanonicalName());
+		}
+		JGitClientSession session = (JGitClientSession) rawSession;
+		session.setUsername(username);
+		session.setConnectAddress(address);
+		session.setHostConfigEntry(hostConfig);
+		if (session.getCredentialsProvider() == null) {
+			session.setCredentialsProvider(getCredentialsProvider());
+		}
+		int numberOfPasswordPrompts = getNumberOfPasswordPrompts(hostConfig);
+		session.getProperties().put(PASSWORD_PROMPTS,
+				Integer.valueOf(numberOfPasswordPrompts));
+		FilePasswordProvider provider = getFilePasswordProvider();
+		if (provider instanceof RepeatingFilePasswordProvider) {
+			((RepeatingFilePasswordProvider) provider)
+					.setAttempts(numberOfPasswordPrompts);
+		}
+		FileKeyPairProvider ourConfiguredKeysProvider = null;
+		List<Path> identities = hostConfig.getIdentities().stream()
+				.map(s -> {
+					try {
+						return Paths.get(s);
+					} catch (InvalidPathException e) {
+						log.warn(format(SshdText.get().configInvalidPath,
+								SshConstants.IDENTITY_FILE, s), e);
+						return null;
+					}
+				}).filter(p -> p != null && Files.exists(p))
+				.collect(Collectors.toList());
+		ourConfiguredKeysProvider = new CachingKeyPairProvider(identities,
+				keyCache);
+		ourConfiguredKeysProvider.setPasswordFinder(getFilePasswordProvider());
+		if (hostConfig.isIdentitiesOnly()) {
+			session.setKeyPairProvider(ourConfiguredKeysProvider);
+		} else {
+			KeyPairProvider defaultKeysProvider = getKeyPairProvider();
+			if (defaultKeysProvider instanceof FileKeyPairProvider) {
+				((FileKeyPairProvider) defaultKeysProvider)
+						.setPasswordFinder(getFilePasswordProvider());
+			}
+			KeyPairProvider combinedProvider = new CombinedKeyPairProvider(
+					ourConfiguredKeysProvider, defaultKeysProvider);
+			session.setKeyPairProvider(combinedProvider);
+		}
+		return session;
+	}
+
+	private int getNumberOfPasswordPrompts(HostConfigEntry hostConfig) {
+		String prompts = hostConfig
+				.getProperty(SshConstants.NUMBER_OF_PASSWORD_PROMPTS);
+		if (prompts != null) {
+			prompts = prompts.trim();
+			int value = positive(prompts);
+			if (value > 0) {
+				return value;
+			}
+			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;
+	}
+
+	/**
+	 * Set a cache for loaded keys. Newly discovered keys will be added when
+	 * IdentityFile host entries from the ssh config file are used during
+	 * session authentication.
+	 *
+	 * @param cache
+	 *            to use
+	 */
+	public void setKeyCache(KeyCache cache) {
+		keyCache = cache;
+	}
+
+	/**
+	 * Sets a {@link ProxyDataFactory} for connecting through proxies.
+	 *
+	 * @param factory
+	 *            to use, or {@code null} if proxying is not desired or
+	 *            supported
+	 */
+	public void setProxyDatabase(ProxyDataFactory factory) {
+		proxyDatabase = factory;
+	}
+
+	/**
+	 * Retrieves the {@link ProxyDataFactory}.
+	 *
+	 * @return the factory, or {@code null} if none is set
+	 */
+	protected ProxyDataFactory getProxyDatabase() {
+		return proxyDatabase;
+	}
+
+	/**
+	 * Sets the {@link CredentialsProvider} for this client.
+	 *
+	 * @param provider
+	 *            to set
+	 */
+	public void setCredentialsProvider(CredentialsProvider provider) {
+		credentialsProvider = provider;
+	}
+
+	/**
+	 * Retrieves the {@link CredentialsProvider} set for this client.
+	 *
+	 * @return the provider, or {@code null} if none is set.
+	 */
+	public CredentialsProvider getCredentialsProvider() {
+		return credentialsProvider;
+	}
+
+	/**
+	 * A {@link SessionFactory} to create our own specialized
+	 * {@link JGitClientSession}s.
+	 */
+	private static class JGitSessionFactory extends SessionFactory {
+
+		public JGitSessionFactory(JGitSshClient client) {
+			super(client);
+		}
+
+		@Override
+		protected ClientSessionImpl doCreateSession(IoSession ioSession)
+				throws Exception {
+			return new JGitClientSession(getClient(), ioSession);
+		}
+	}
+
+	/**
+	 * A {@link KeyPairProvider} that iterates over the {@link Iterable}s
+	 * returned by other {@link KeyPairProvider}s.
+	 */
+	private static class CombinedKeyPairProvider implements KeyPairProvider {
+
+		private final List<KeyPairProvider> providers;
+
+		public CombinedKeyPairProvider(KeyPairProvider... providers) {
+			this(Arrays.stream(providers).filter(Objects::nonNull)
+					.collect(Collectors.toList()));
+		}
+
+		public CombinedKeyPairProvider(List<KeyPairProvider> providers) {
+			this.providers = providers;
+		}
+
+		@Override
+		public Iterable<String> getKeyTypes() {
+			throw new UnsupportedOperationException(
+					"Should not have been called in a ssh client"); //$NON-NLS-1$
+		}
+
+		@Override
+		public KeyPair loadKey(String type) {
+			throw new UnsupportedOperationException(
+					"Should not have been called in a ssh client"); //$NON-NLS-1$
+		}
+
+		@Override
+		public Iterable<KeyPair> loadKeys() {
+			return () -> new Iterator<KeyPair>() {
+
+				private Iterator<KeyPairProvider> factories = providers.iterator();
+				private Iterator<KeyPair> current;
+
+				private Boolean hasElement;
+
+				@Override
+				public boolean hasNext() {
+					if (hasElement != null) {
+						return hasElement.booleanValue();
+					}
+					while (current == null || !current.hasNext()) {
+						if (factories.hasNext()) {
+							current = factories.next().loadKeys().iterator();
+						} else {
+							current = null;
+							hasElement = Boolean.FALSE;
+							return false;
+						}
+					}
+					hasElement = Boolean.TRUE;
+					return true;
+				}
+
+				@Override
+				public KeyPair next() {
+					if (hasElement == null && !hasNext()
+							|| !hasElement.booleanValue()) {
+						throw new NoSuchElementException();
+					}
+					hasElement = null;
+					KeyPair result;
+					try {
+						result = current.next();
+					} catch (NoSuchElementException e) {
+						result = null;
+					}
+					return result;
+				}
+
+			};
+		}
+
+	}
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshConfig.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshConfig.java
new file mode 100644
index 0000000..9eced0f
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshConfig.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd;
+
+import static org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile.flag;
+import static org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile.positive;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.sshd.client.config.hosts.HostConfigEntry;
+import org.apache.sshd.client.config.hosts.HostConfigEntryResolver;
+import org.apache.sshd.common.util.net.SshdSocketAddress;
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile;
+import org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile.HostEntry;
+import org.eclipse.jgit.transport.SshConstants;
+
+/**
+ * A {@link HostConfigEntryResolver} adapted specifically for JGit.
+ * <p>
+ * We use our own config file parser and entry resolution since the default
+ * {@link org.apache.sshd.client.config.hosts.ConfigFileHostEntryResolver
+ * ConfigFileHostEntryResolver} has a number of problems:
+ * </p>
+ * <ul>
+ * <li>It does case-insensitive pattern matching. Matching in OpenSsh is
+ * case-sensitive! Compare also bug 531118.</li>
+ * <li>It only merges values from the global items (before the first "Host"
+ * line) into the host entries. Otherwise it selects the most specific match.
+ * OpenSsh processes <em>all</em> entries in the order they appear in the file
+ * and whenever one matches, it updates values as appropriate.</li>
+ * <li>We have to ensure that ~ replacement uses the same HOME directory as
+ * JGit. Compare bug bug 526175.</li>
+ * </ul>
+ * Therefore, this re-uses the parsing and caching from
+ * {@link OpenSshConfigFile}.
+ *
+ * @since 5.2
+ */
+public class JGitSshConfig implements HostConfigEntryResolver {
+
+	private OpenSshConfigFile configFile;
+
+	/**
+	 * Creates a new {@link OpenSshConfigFile} that will read the config from
+	 * file {@code config} use the given file {@code home} as "home" directory.
+	 *
+	 * @param home
+	 *            user's home directory for the purpose of ~ replacement
+	 * @param config
+	 *            file to load.
+	 * @param localUserName
+	 *            user name of the current user on the local host OS
+	 */
+	public JGitSshConfig(@NonNull File home, @NonNull File config,
+			@NonNull String localUserName) {
+		configFile = new OpenSshConfigFile(home, config, localUserName);
+	}
+
+	@Override
+	public HostConfigEntry resolveEffectiveHost(String host, int port,
+			String username) throws IOException {
+		HostEntry entry = configFile.lookup(host, port, username);
+		JGitHostConfigEntry config = new JGitHostConfigEntry();
+		// Apache MINA conflates all keys, even multi-valued ones, in one map
+		// and puts multiple values separated by commas in one string. See
+		// the javadoc on HostConfigEntry.
+		Map<String, String> allOptions = new TreeMap<>(
+				String.CASE_INSENSITIVE_ORDER);
+		allOptions.putAll(entry.getOptions());
+		// And what if a value contains a comma??
+		entry.getMultiValuedOptions().entrySet().stream()
+				.forEach(e -> allOptions.put(e.getKey(),
+						String.join(",", e.getValue()))); //$NON-NLS-1$
+		config.setProperties(allOptions);
+		// The following is an extension from JGitHostConfigEntry
+		config.setMultiValuedOptions(entry.getMultiValuedOptions());
+		// Also make sure the underlying properties are set
+		String hostName = entry.getValue(SshConstants.HOST_NAME);
+		if (hostName == null || hostName.isEmpty()) {
+			hostName = host;
+		}
+		config.setHostName(hostName);
+		config.setProperty(SshConstants.HOST_NAME, hostName);
+		config.setHost(SshdSocketAddress.isIPv6Address(hostName) ? "" : hostName); //$NON-NLS-1$
+		String user = username != null && !username.isEmpty() ? username
+				: entry.getValue(SshConstants.USER);
+		if (user == null || user.isEmpty()) {
+			user = configFile.getLocalUserName();
+		}
+		config.setUsername(user);
+		config.setProperty(SshConstants.USER, user);
+		int p = port >= 0 ? port : positive(entry.getValue(SshConstants.PORT));
+		config.setPort(p >= 0 ? p : SshConstants.SSH_DEFAULT_PORT);
+		config.setProperty(SshConstants.PORT,
+				Integer.toString(config.getPort()));
+		config.setIdentities(entry.getValues(SshConstants.IDENTITY_FILE));
+		config.setIdentitiesOnly(
+				flag(entry.getValue(SshConstants.IDENTITIES_ONLY)));
+		return config;
+	}
+
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitUserInteraction.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitUserInteraction.java
new file mode 100644
index 0000000..99288b7
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitUserInteraction.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd;
+
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.sshd.client.auth.keyboard.UserInteraction;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.session.SessionListener;
+import org.eclipse.jgit.transport.CredentialItem;
+import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.SshConstants;
+import org.eclipse.jgit.transport.URIish;
+
+/**
+ * A {@link UserInteraction} callback implementation based on a
+ * {@link CredentialsProvider}.
+ */
+public class JGitUserInteraction implements UserInteraction {
+
+	private final CredentialsProvider provider;
+
+	/**
+	 * We need to reset the JGit credentials provider if we have repeated
+	 * attempts.
+	 */
+	private final Map<Session, SessionListener> ongoing = new ConcurrentHashMap<>();
+
+	/**
+	 * Creates a new {@link JGitUserInteraction} for interactive password input
+	 * based on the given {@link CredentialsProvider}.
+	 *
+	 * @param provider
+	 *            to use
+	 */
+	public JGitUserInteraction(CredentialsProvider provider) {
+		this.provider = provider;
+	}
+
+	@Override
+	public boolean isInteractionAllowed(ClientSession session) {
+		return provider != null && provider.isInteractive();
+	}
+
+	@Override
+	public String[] interactive(ClientSession session, String name,
+			String instruction, String lang, String[] prompt, boolean[] echo) {
+		// This is keyboard-interactive or password authentication
+		List<CredentialItem> items = new ArrayList<>();
+		int numberOfHiddenInputs = 0;
+		for (int i = 0; i < prompt.length; i++) {
+			boolean hidden = i < echo.length && !echo[i];
+			if (hidden) {
+				numberOfHiddenInputs++;
+			}
+		}
+		// RFC 4256 (SSH_MSG_USERAUTH_INFO_REQUEST) says: "The language tag is
+		// deprecated and SHOULD be the empty string." and "[If there are no
+		// prompts] the client SHOULD still display the name and instruction
+		// fields" and "[The] client SHOULD print the name and instruction (if
+		// non-empty)"
+		if (name != null && !name.isEmpty()) {
+			items.add(new CredentialItem.InformationalMessage(name));
+		}
+		if (instruction != null && !instruction.isEmpty()) {
+			items.add(new CredentialItem.InformationalMessage(instruction));
+		}
+		for (int i = 0; i < prompt.length; i++) {
+			boolean hidden = i < echo.length && !echo[i];
+			if (hidden && numberOfHiddenInputs == 1) {
+				// We need to somehow trigger storing the password in the
+				// Eclipse secure storage in EGit. Currently, this is done only
+				// for password fields.
+				items.add(new CredentialItem.Password());
+				// TODO Possibly change EGit to store all hidden strings
+				// (keyed by the URI and the prompt?) so that we don't have to
+				// use this kludge here.
+			} else {
+				items.add(new CredentialItem.StringType(prompt[i], hidden));
+			}
+		}
+		if (items.isEmpty()) {
+			// Huh? No info, no prompts?
+			return prompt; // Is known to have length zero here
+		}
+		URIish uri = toURI(session.getUsername(),
+				(InetSocketAddress) session.getConnectAddress());
+		// Reset the provider for this URI if it's not the first attempt and we
+		// have hidden inputs. Otherwise add a session listener that will remove
+		// itself once authenticated.
+		if (numberOfHiddenInputs > 0) {
+			SessionListener listener = ongoing.get(session);
+			if (listener != null) {
+				provider.reset(uri);
+			} else {
+				listener = new SessionAuthMarker(ongoing);
+				ongoing.put(session, listener);
+				session.addSessionListener(listener);
+			}
+		}
+		if (provider.get(uri, items)) {
+			return items.stream().map(i -> {
+				if (i instanceof CredentialItem.Password) {
+					return new String(((CredentialItem.Password) i).getValue());
+				} else if (i instanceof CredentialItem.StringType) {
+					return ((CredentialItem.StringType) i).getValue();
+				}
+				return null;
+			}).filter(s -> s != null).toArray(String[]::new);
+		}
+		// TODO What to throw to abort the connection/authentication process?
+		// In UserAuthKeyboardInteractive.getUserResponses() it's clear that
+		// returning null is valid and signifies "an error"; we'll try the
+		// next authentication method. But if the user explicitly canceled,
+		// then we don't want to try the next methods...
+		//
+		// Probably not a serious issue with the typical order of public-key,
+		// keyboard-interactive, password.
+		return null;
+	}
+
+	@Override
+	public String getUpdatedPassword(ClientSession session, String prompt,
+			String lang) {
+		// TODO Implement password update in password authentication?
+		return null;
+	}
+
+	/**
+	 * Creates a {@link URIish} from the given remote address and user name.
+	 *
+	 * @param userName
+	 *            for the uri
+	 * @param remote
+	 *            address of the remote host
+	 * @return the uri, with {@link SshConstants#SSH_SCHEME} as scheme
+	 */
+	public static URIish toURI(String userName, InetSocketAddress remote) {
+		String host = remote.getHostString();
+		int port = remote.getPort();
+		return new URIish() //
+				.setScheme(SshConstants.SSH_SCHEME) //
+				.setHost(host) //
+				.setPort(port) //
+				.setUser(userName);
+	}
+
+	/**
+	 * A {@link SessionListener} that removes itself from the session when
+	 * authentication is done or the session is closed.
+	 */
+	private static class SessionAuthMarker implements SessionListener {
+
+		private final Map<Session, SessionListener> registered;
+
+		public SessionAuthMarker(Map<Session, SessionListener> registered) {
+			this.registered = registered;
+		}
+
+		@Override
+		public void sessionEvent(Session session, SessionListener.Event event) {
+			if (event == SessionListener.Event.Authenticated) {
+				session.removeSessionListener(this);
+				registered.remove(session, this);
+			}
+		}
+
+		@Override
+		public void sessionClosed(Session session) {
+			session.removeSessionListener(this);
+			registered.remove(session, this);
+		}
+	}
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/KnownHostEntryReader.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/KnownHostEntryReader.java
new file mode 100644
index 0000000..4db24a1
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/KnownHostEntryReader.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd;
+
+import static java.text.MessageFormat.format;
+import static org.apache.sshd.client.config.hosts.HostPatternsHolder.NON_STANDARD_PORT_PATTERN_ENCLOSURE_END_DELIM;
+import static org.apache.sshd.client.config.hosts.HostPatternsHolder.NON_STANDARD_PORT_PATTERN_ENCLOSURE_START_DELIM;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.sshd.client.config.hosts.HostPatternValue;
+import org.apache.sshd.client.config.hosts.HostPatternsHolder;
+import org.apache.sshd.client.config.hosts.KnownHostEntry;
+import org.apache.sshd.client.config.hosts.KnownHostHashValue;
+import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Apache MINA sshd 2.0.0 KnownHostEntry cannot read a host entry line like
+ * "host:port ssh-rsa <key>"; it complains about an illegal character in the
+ * host name (correct would be "[host]:port"). The default known_hosts reader
+ * also aborts reading on the first error.
+ * <p>
+ * This reader is a bit more robust and tries to handle this case if there is
+ * only one colon (otherwise it might be an IPv6 address (without port)), and it
+ * skips and logs invalid entries, but still returns all other valid entries
+ * from the file.
+ * </p>
+ */
+public class KnownHostEntryReader {
+
+	private static final Logger LOG = LoggerFactory
+			.getLogger(KnownHostEntryReader.class);
+
+	private KnownHostEntryReader() {
+		// No instantiation
+	}
+
+	/**
+	 * Reads a known_hosts file and returns all valid entries. Invalid entries
+	 * are skipped (and a message is logged).
+	 *
+	 * @param path
+	 *            of the file to read
+	 * @return a {@link List} of all valid entries read from the file
+	 * @throws IOException
+	 *             if the file cannot be read.
+	 */
+	public static List<KnownHostEntry> readFromFile(Path path)
+			throws IOException {
+		List<KnownHostEntry> result = new LinkedList<>();
+		try (BufferedReader r = Files.newBufferedReader(path,
+				StandardCharsets.UTF_8)) {
+			r.lines().forEachOrdered(l -> {
+				if (l == null) {
+					return;
+				}
+				String line = clean(l);
+				if (line.isEmpty()) {
+					return;
+				}
+				try {
+					KnownHostEntry entry = parseHostEntry(line);
+					if (entry != null) {
+						result.add(entry);
+					} else {
+						LOG.warn(format(SshdText.get().knownHostsInvalidLine,
+								path, line));
+					}
+				} catch (RuntimeException e) {
+					LOG.warn(format(SshdText.get().knownHostsInvalidLine, path,
+							line), e);
+				}
+			});
+		}
+		return result;
+	}
+
+	private static String clean(String line) {
+		int i = line.indexOf('#');
+		return i < 0 ? line.trim() : line.substring(0, i).trim();
+	}
+
+	private static KnownHostEntry parseHostEntry(String line) {
+		KnownHostEntry entry = new KnownHostEntry();
+		entry.setConfigLine(line);
+		String tmp = line;
+		int i = 0;
+		if (tmp.charAt(0) == KnownHostEntry.MARKER_INDICATOR) {
+			// A marker
+			i = tmp.indexOf(' ', 1);
+			if (i < 0) {
+				return null;
+			}
+			entry.setMarker(tmp.substring(1, i));
+			tmp = tmp.substring(i + 1).trim();
+		}
+		i = tmp.indexOf(' ');
+		if (i < 0) {
+			return null;
+		}
+		// Hash, or host patterns
+		if (tmp.charAt(0) == KnownHostHashValue.HASHED_HOST_DELIMITER) {
+			// Hashed host entry
+			KnownHostHashValue hash = KnownHostHashValue
+					.parse(tmp.substring(0, i));
+			if (hash == null) {
+				return null;
+			}
+			entry.setHashedEntry(hash);
+			entry.setPatterns(null);
+		} else {
+			Collection<HostPatternValue> patterns = parsePatterns(
+					tmp.substring(0, i));
+			if (patterns == null || patterns.isEmpty()) {
+				return null;
+			}
+			entry.setHashedEntry(null);
+			entry.setPatterns(patterns);
+		}
+		tmp = tmp.substring(i + 1).trim();
+		AuthorizedKeyEntry key = AuthorizedKeyEntry
+				.parseAuthorizedKeyEntry(tmp);
+		if (key == null) {
+			return null;
+		}
+		entry.setKeyEntry(key);
+		return entry;
+	}
+
+	private static Collection<HostPatternValue> parsePatterns(String text) {
+		if (text.isEmpty()) {
+			return null;
+		}
+		List<String> items = Arrays.stream(text.split(",")) //$NON-NLS-1$
+				.filter(item -> item != null && !item.isEmpty()).map(item -> {
+					if (NON_STANDARD_PORT_PATTERN_ENCLOSURE_START_DELIM == item
+							.charAt(0)) {
+						return item;
+					}
+					int firstColon = item.indexOf(':');
+					if (firstColon < 0) {
+						return item;
+					}
+					int secondColon = item.indexOf(':', firstColon + 1);
+					if (secondColon > 0) {
+						// Assume an IPv6 address (without port).
+						return item;
+					}
+					// We have "host:port", should be "[host]:port"
+					return NON_STANDARD_PORT_PATTERN_ENCLOSURE_START_DELIM
+							+ item.substring(0, firstColon)
+							+ NON_STANDARD_PORT_PATTERN_ENCLOSURE_END_DELIM
+							+ item.substring(firstColon);
+				}).collect(Collectors.toList());
+		return items.isEmpty() ? null : HostPatternsHolder.parsePatterns(items);
+	}
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyVerifier.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyVerifier.java
new file mode 100644
index 0000000..540b586
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyVerifier.java
@@ -0,0 +1,745 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd;
+
+import static java.text.MessageFormat.format;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.InvalidPathException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.GeneralSecurityException;
+import java.security.PublicKey;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
+
+import org.apache.sshd.client.config.hosts.HostConfigEntry;
+import org.apache.sshd.client.config.hosts.KnownHostEntry;
+import org.apache.sshd.client.keyverifier.KnownHostsServerKeyVerifier;
+import org.apache.sshd.client.keyverifier.KnownHostsServerKeyVerifier.HostEntryPair;
+import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
+import org.apache.sshd.common.digest.BuiltinDigests;
+import org.apache.sshd.common.util.io.ModifiableFileWatcher;
+import org.apache.sshd.common.util.net.SshdSocketAddress;
+import org.eclipse.jgit.internal.storage.file.LockFile;
+import org.eclipse.jgit.transport.CredentialItem;
+import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.SshConstants;
+import org.eclipse.jgit.transport.URIish;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A sever host key verifier that honors the {@code StrictHostKeyChecking} and
+ * {@code UserKnownHostsFile} values from the ssh configuration.
+ * <p>
+ * The verifier can be given default known_hosts files in the constructor, which
+ * will be used if the ssh config does not specify a {@code UserKnownHostsFile}.
+ * If the ssh config <em>does</em> set {@code UserKnownHostsFile}, the verifier
+ * uses the given files in the order given. Non-existing or unreadable files are
+ * ignored.
+ * <p>
+ * {@code StrictHostKeyChecking} accepts the following values:
+ * </p>
+ * <dl>
+ * <dt>ask</dt>
+ * <dd>Ask the user whether new or changed keys shall be accepted and be added
+ * to the known_hosts file.</dd>
+ * <dt>yes/true</dt>
+ * <dd>Accept only keys listed in the known_hosts file.</dd>
+ * <dt>no/false</dt>
+ * <dd>Silently accept all new or changed keys, add new keys to the known_hosts
+ * file.</dd>
+ * <dt>accept-new</dt>
+ * <dd>Silently accept keys for new hosts and add them to the known_hosts
+ * file.</dd>
+ * </dl>
+ * <p>
+ * If {@code StrictHostKeyChecking} is not set, or set to any other value, the
+ * default value <b>ask</b> is active.
+ * </p>
+ * <p>
+ * This implementation relies on the {@link ClientSession} being a
+ * {@link JGitClientSession}. By default Apache MINA sshd does not forward the
+ * config file host entry to the session, so it would be unknown here which
+ * entry it was and what setting of {@code StrictHostKeyChecking} should be
+ * used. If used with some other session type, the implementation assumes
+ * "<b>ask</b>".
+ * <p>
+ * <p>
+ * Asking the user is done via a {@link CredentialsProvider} obtained from the
+ * session. If none is set, the implementation falls back to strict host key
+ * checking ("<b>yes</b>").
+ * </p>
+ * <p>
+ * Note that adding a key to the known hosts file may create the file. You can
+ * specify in the constructor whether the user shall be asked about that, too.
+ * If the the user declines updating the file, but the key was otherwise
+ * accepted (user confirmed for "<b>ask</b>", or "no" or "accept-new" are
+ * active), the key is accepted for this session only.
+ * </p>
+ * <p>
+ * If several known hosts files are specified, a new key is always added to the
+ * first file (even if it doesn't exist yet; see the note about file creation
+ * above).
+ * </p>
+ *
+ * @see <a href="http://man.openbsd.org/OpenBSD-current/man5/ssh_config.5">man
+ *      ssh-config</a>
+ */
+public class OpenSshServerKeyVerifier
+		implements ServerKeyVerifier, ServerKeyLookup {
+
+	// TODO: GlobalKnownHostsFile? May need some kind of LRU caching; these
+	// files may be large!
+
+	private static final Logger LOG = LoggerFactory
+			.getLogger(OpenSshServerKeyVerifier.class);
+
+	/** Can be used to mark revoked known host lines. */
+	private static final String MARKER_REVOKED = "revoked"; //$NON-NLS-1$
+
+	private final boolean askAboutNewFile;
+
+	private final Map<Path, HostKeyFile> knownHostsFiles = new ConcurrentHashMap<>();
+
+	private final List<HostKeyFile> defaultFiles = new ArrayList<>();
+
+	private enum ModifiedKeyHandling {
+		DENY, ALLOW, ALLOW_AND_STORE
+	}
+
+	/**
+	 * Creates a new {@link OpenSshServerKeyVerifier}.
+	 *
+	 * @param askAboutNewFile
+	 *            whether to ask the user, if possible, about creating a new
+	 *            non-existing known_hosts file
+	 * @param defaultFiles
+	 *            typically ~/.ssh/known_hosts and ~/.ssh/known_hosts2. May be
+	 *            empty or {@code null}, in which case no default files are
+	 *            installed. The files need not exist.
+	 */
+	public OpenSshServerKeyVerifier(boolean askAboutNewFile,
+			List<Path> defaultFiles) {
+		if (defaultFiles != null) {
+			for (Path file : defaultFiles) {
+				HostKeyFile newFile = new HostKeyFile(file);
+				knownHostsFiles.put(file, newFile);
+				this.defaultFiles.add(newFile);
+			}
+		}
+		this.askAboutNewFile = askAboutNewFile;
+	}
+
+	private List<HostKeyFile> getFilesToUse(ClientSession session) {
+		List<HostKeyFile> filesToUse = defaultFiles;
+		if (session instanceof JGitClientSession) {
+			HostConfigEntry entry = ((JGitClientSession) session)
+					.getHostConfigEntry();
+			if (entry instanceof JGitHostConfigEntry) {
+				// Always true!
+				List<HostKeyFile> userFiles = addUserHostKeyFiles(
+						((JGitHostConfigEntry) entry).getMultiValuedOptions()
+								.get(SshConstants.USER_KNOWN_HOSTS_FILE));
+				if (!userFiles.isEmpty()) {
+					filesToUse = userFiles;
+				}
+			}
+		}
+		return filesToUse;
+	}
+
+	@Override
+	public List<HostEntryPair> lookup(ClientSession session,
+			SocketAddress remote) {
+		List<HostKeyFile> filesToUse = getFilesToUse(session);
+		HostKeyHelper helper = new HostKeyHelper();
+		List<HostEntryPair> result = new ArrayList<>();
+		Collection<SshdSocketAddress> candidates = helper
+				.resolveHostNetworkIdentities(session, remote);
+		for (HostKeyFile file : filesToUse) {
+			for (HostEntryPair current : file.get()) {
+				KnownHostEntry entry = current.getHostEntry();
+				for (SshdSocketAddress host : candidates) {
+					if (entry.isHostMatch(host.getHostName(), host.getPort())) {
+						result.add(current);
+						break;
+					}
+				}
+			}
+		}
+		return result;
+	}
+
+	@Override
+	public boolean verifyServerKey(ClientSession clientSession,
+			SocketAddress remoteAddress, PublicKey serverKey) {
+		List<HostKeyFile> filesToUse = getFilesToUse(clientSession);
+		AskUser ask = new AskUser();
+		HostEntryPair[] modified = { null };
+		Path path = null;
+		HostKeyHelper helper = new HostKeyHelper();
+		for (HostKeyFile file : filesToUse) {
+			try {
+				if (find(clientSession, remoteAddress, serverKey, file.get(),
+						modified, helper)) {
+					return true;
+				}
+			} catch (RevokedKeyException e) {
+				ask.revokedKey(clientSession, remoteAddress, serverKey,
+						file.getPath());
+				return false;
+			}
+			if (path == null && modified[0] != null) {
+				// Remember the file in which we might need to update the
+				// entry
+				path = file.getPath();
+			}
+		}
+		if (modified[0] != null) {
+			// We found an entry, but with a different key
+			ModifiedKeyHandling toDo = ask.acceptModifiedServerKey(
+					clientSession, remoteAddress, modified[0].getServerKey(),
+					serverKey, path);
+			if (toDo == ModifiedKeyHandling.ALLOW_AND_STORE) {
+				try {
+					updateModifiedServerKey(clientSession, remoteAddress,
+							serverKey, modified[0], path, helper);
+					knownHostsFiles.get(path).resetReloadAttributes();
+				} catch (IOException e) {
+					LOG.warn(format(SshdText.get().knownHostsCouldNotUpdate,
+							path));
+				}
+			}
+			if (toDo == ModifiedKeyHandling.DENY) {
+				return false;
+			}
+			// TODO: OpenSsh disables password and keyboard-interactive
+			// authentication in this case. Also agent and local port forwarding
+			// are switched off. (Plus a few other things such as X11 forwarding
+			// that are of no interest to a git client.)
+			return true;
+		} else if (ask.acceptUnknownKey(clientSession, remoteAddress,
+				serverKey)) {
+			if (!filesToUse.isEmpty()) {
+				HostKeyFile toUpdate = filesToUse.get(0);
+				path = toUpdate.getPath();
+				try {
+					updateKnownHostsFile(clientSession, remoteAddress,
+							serverKey, path, helper);
+					toUpdate.resetReloadAttributes();
+				} catch (IOException e) {
+					LOG.warn(format(SshdText.get().knownHostsCouldNotUpdate,
+							path));
+				}
+			}
+			return true;
+		}
+		return false;
+	}
+
+	private static class RevokedKeyException extends Exception {
+		private static final long serialVersionUID = 1L;
+	}
+
+	private boolean find(ClientSession clientSession,
+			SocketAddress remoteAddress, PublicKey serverKey,
+			List<HostEntryPair> entries, HostEntryPair[] modified,
+			HostKeyHelper helper) throws RevokedKeyException {
+		Collection<SshdSocketAddress> candidates = helper
+				.resolveHostNetworkIdentities(clientSession, remoteAddress);
+		for (HostEntryPair current : entries) {
+			KnownHostEntry entry = current.getHostEntry();
+			for (SshdSocketAddress host : candidates) {
+				if (entry.isHostMatch(host.getHostName(), host.getPort())) {
+					boolean isRevoked = MARKER_REVOKED
+							.equals(entry.getMarker());
+					if (KeyUtils.compareKeys(serverKey,
+							current.getServerKey())) {
+						// Exact match
+						if (isRevoked) {
+							throw new RevokedKeyException();
+						}
+						modified[0] = null;
+						return true;
+					} else if (!isRevoked) {
+						// Server sent a different key
+						modified[0] = current;
+						// Keep going -- maybe there's another entry for this
+						// host
+					}
+				}
+			}
+		}
+		return false;
+	}
+
+	private List<HostKeyFile> addUserHostKeyFiles(List<String> fileNames) {
+		if (fileNames == null || fileNames.isEmpty()) {
+			return Collections.emptyList();
+		}
+		List<HostKeyFile> userFiles = new ArrayList<>();
+		for (String name : fileNames) {
+			try {
+				Path path = Paths.get(name);
+				HostKeyFile file = knownHostsFiles.computeIfAbsent(path,
+						p -> new HostKeyFile(path));
+				userFiles.add(file);
+			} catch (InvalidPathException e) {
+				LOG.warn(format(SshdText.get().knownHostsInvalidPath,
+						name));
+			}
+		}
+		return userFiles;
+	}
+
+	private void updateKnownHostsFile(ClientSession clientSession,
+			SocketAddress remoteAddress, PublicKey serverKey, Path path,
+			HostKeyHelper updater)
+			throws IOException {
+		KnownHostEntry entry = updater.prepareKnownHostEntry(clientSession,
+				remoteAddress, serverKey);
+		if (entry == null) {
+			return;
+		}
+		if (!Files.exists(path)) {
+			if (askAboutNewFile) {
+				CredentialsProvider provider = getCredentialsProvider(
+						clientSession);
+				if (provider == null) {
+					// We can't ask, so don't create the file
+					return;
+				}
+				URIish uri = new URIish().setPath(path.toString());
+				if (!askUser(provider, uri, //
+						format(SshdText.get().knownHostsUserAskCreationPrompt,
+								path), //
+						format(SshdText.get().knownHostsUserAskCreationMsg,
+								path))) {
+					return;
+				}
+			}
+		}
+		LockFile lock = new LockFile(path.toFile());
+		if (lock.lockForAppend()) {
+			try {
+				try (BufferedWriter writer = new BufferedWriter(
+						new OutputStreamWriter(lock.getOutputStream(),
+								StandardCharsets.UTF_8))) {
+					writer.newLine();
+					writer.write(entry.getConfigLine());
+					writer.newLine();
+				}
+				lock.commit();
+			} catch (IOException e) {
+				lock.unlock();
+				throw e;
+			}
+		} else {
+			LOG.warn(format(SshdText.get().knownHostsFileLockedUpdate,
+					path));
+		}
+	}
+
+	private void updateModifiedServerKey(ClientSession clientSession,
+			SocketAddress remoteAddress, PublicKey serverKey,
+			HostEntryPair entry, Path path, HostKeyHelper helper)
+			throws IOException {
+		KnownHostEntry hostEntry = entry.getHostEntry();
+		String oldLine = hostEntry.getConfigLine();
+		String newLine = helper.prepareModifiedServerKeyLine(clientSession,
+				remoteAddress, hostEntry, oldLine, entry.getServerKey(),
+				serverKey);
+		if (newLine == null || newLine.isEmpty()) {
+			return;
+		}
+		if (oldLine == null || oldLine.isEmpty() || newLine.equals(oldLine)) {
+			// Shouldn't happen.
+			return;
+		}
+		LockFile lock = new LockFile(path.toFile());
+		if (lock.lock()) {
+			try {
+				try (BufferedWriter writer = new BufferedWriter(
+						new OutputStreamWriter(lock.getOutputStream(),
+								StandardCharsets.UTF_8));
+						BufferedReader reader = Files.newBufferedReader(path,
+								StandardCharsets.UTF_8)) {
+					boolean done = false;
+					String line;
+					while ((line = reader.readLine()) != null) {
+						String toWrite = line;
+						if (!done) {
+							int pos = line.indexOf('#');
+							String toTest = pos < 0 ? line
+									: line.substring(0, pos);
+							if (toTest.trim().equals(oldLine)) {
+								toWrite = newLine;
+								done = true;
+							}
+						}
+						writer.write(toWrite);
+						writer.newLine();
+					}
+				}
+				lock.commit();
+			} catch (IOException e) {
+				lock.unlock();
+				throw e;
+			}
+		} else {
+			LOG.warn(format(SshdText.get().knownHostsFileLockedUpdate,
+					path));
+		}
+	}
+
+	private static CredentialsProvider getCredentialsProvider(
+			ClientSession session) {
+		if (session instanceof JGitClientSession) {
+			return ((JGitClientSession) session).getCredentialsProvider();
+		}
+		return null;
+	}
+
+	private static boolean askUser(CredentialsProvider provider, URIish uri,
+			String prompt, String... messages) {
+		List<CredentialItem> items = new ArrayList<>(messages.length + 1);
+		for (String message : messages) {
+			items.add(new CredentialItem.InformationalMessage(message));
+		}
+		if (prompt != null) {
+			CredentialItem.YesNoType answer = new CredentialItem.YesNoType(
+					prompt);
+			items.add(answer);
+			return provider.get(uri, items) && answer.getValue();
+		} else {
+			return provider.get(uri, items);
+		}
+	}
+
+	private static class AskUser {
+
+		private enum Check {
+			ASK, DENY, ALLOW;
+		}
+
+		@SuppressWarnings("nls")
+		private Check checkMode(ClientSession session,
+				SocketAddress remoteAddress, boolean changed) {
+			if (!(remoteAddress instanceof InetSocketAddress)) {
+				return Check.DENY;
+			}
+			if (session instanceof JGitClientSession) {
+				HostConfigEntry entry = ((JGitClientSession) session)
+						.getHostConfigEntry();
+				String value = entry.getProperty(
+						SshConstants.STRICT_HOST_KEY_CHECKING, "ask");
+				switch (value.toLowerCase(Locale.ROOT)) {
+				case SshConstants.YES:
+				case SshConstants.ON:
+					return Check.DENY;
+				case SshConstants.NO:
+				case SshConstants.OFF:
+					return Check.ALLOW;
+				case "accept-new":
+					return changed ? Check.DENY : Check.ALLOW;
+				default:
+					break;
+				}
+			}
+			if (getCredentialsProvider(session) == null) {
+				// This is called only for new, unknown hosts. If we have no way
+				// to interact with the user, the fallback mode is to deny the
+				// key.
+				return Check.DENY;
+			}
+			return Check.ASK;
+		}
+
+		public void revokedKey(ClientSession clientSession,
+				SocketAddress remoteAddress, PublicKey serverKey, Path path) {
+			CredentialsProvider provider = getCredentialsProvider(
+					clientSession);
+			if (provider == null) {
+				return;
+			}
+			InetSocketAddress remote = (InetSocketAddress) remoteAddress;
+			URIish uri = JGitUserInteraction.toURI(clientSession.getUsername(),
+					remote);
+			String sha256 = KeyUtils.getFingerPrint(BuiltinDigests.sha256,
+					serverKey);
+			String md5 = KeyUtils.getFingerPrint(BuiltinDigests.md5, serverKey);
+			String keyAlgorithm = serverKey.getAlgorithm();
+			askUser(provider, uri, null, //
+					format(SshdText.get().knownHostsRevokedKeyMsg,
+							remote.getHostString(), path),
+					format(SshdText.get().knownHostsKeyFingerprints,
+							keyAlgorithm),
+					md5, sha256);
+		}
+
+		public boolean acceptUnknownKey(ClientSession clientSession,
+				SocketAddress remoteAddress, PublicKey serverKey) {
+			Check check = checkMode(clientSession, remoteAddress, false);
+			if (check != Check.ASK) {
+				return check == Check.ALLOW;
+			}
+			CredentialsProvider provider = getCredentialsProvider(
+					clientSession);
+			InetSocketAddress remote = (InetSocketAddress) remoteAddress;
+			// Ask the user
+			String sha256 = KeyUtils.getFingerPrint(BuiltinDigests.sha256,
+					serverKey);
+			String md5 = KeyUtils.getFingerPrint(BuiltinDigests.md5, serverKey);
+			String keyAlgorithm = serverKey.getAlgorithm();
+			String remoteHost = remote.getHostString();
+			URIish uri = JGitUserInteraction.toURI(clientSession.getUsername(),
+					remote);
+			String prompt = SshdText.get().knownHostsUnknownKeyPrompt;
+			return askUser(provider, uri, prompt, //
+					format(SshdText.get().knownHostsUnknownKeyMsg,
+							remoteHost),
+					format(SshdText.get().knownHostsKeyFingerprints,
+							keyAlgorithm),
+					md5, sha256);
+		}
+
+		public ModifiedKeyHandling acceptModifiedServerKey(
+				ClientSession clientSession,
+				SocketAddress remoteAddress, PublicKey expected,
+				PublicKey actual, Path path) {
+			Check check = checkMode(clientSession, remoteAddress, true);
+			if (check == Check.ALLOW) {
+				// Never auto-store on CHECK.ALLOW
+				return ModifiedKeyHandling.ALLOW;
+			}
+			InetSocketAddress remote = (InetSocketAddress) remoteAddress;
+			String keyAlgorithm = actual.getAlgorithm();
+			String remoteHost = remote.getHostString();
+			URIish uri = JGitUserInteraction.toURI(clientSession.getUsername(),
+					remote);
+			List<String> messages = new ArrayList<>();
+			String warning = format(
+					SshdText.get().knownHostsModifiedKeyWarning,
+					keyAlgorithm, expected.getAlgorithm(), remoteHost,
+					KeyUtils.getFingerPrint(BuiltinDigests.md5, expected),
+					KeyUtils.getFingerPrint(BuiltinDigests.sha256, expected),
+					KeyUtils.getFingerPrint(BuiltinDigests.md5, actual),
+					KeyUtils.getFingerPrint(BuiltinDigests.sha256, actual));
+			for (String line : warning.split("\n")) { //$NON-NLS-1$
+				messages.add(line);
+			}
+
+			CredentialsProvider provider = getCredentialsProvider(
+					clientSession);
+			if (check == Check.DENY) {
+				if (provider != null) {
+					messages.add(format(
+							SshdText.get().knownHostsModifiedKeyDenyMsg, path));
+					askUser(provider, uri, null,
+							messages.toArray(new String[0]));
+				}
+				return ModifiedKeyHandling.DENY;
+			}
+			// ASK -- two questions: procceed? and store?
+			List<CredentialItem> items = new ArrayList<>(messages.size() + 2);
+			for (String message : messages) {
+				items.add(new CredentialItem.InformationalMessage(message));
+			}
+			CredentialItem.YesNoType proceed = new CredentialItem.YesNoType(
+					SshdText.get().knownHostsModifiedKeyAcceptPrompt);
+			CredentialItem.YesNoType store = new CredentialItem.YesNoType(
+					SshdText.get().knownHostsModifiedKeyStorePrompt);
+			items.add(proceed);
+			items.add(store);
+			if (provider.get(uri, items) && proceed.getValue()) {
+				return store.getValue() ? ModifiedKeyHandling.ALLOW_AND_STORE
+						: ModifiedKeyHandling.ALLOW;
+			}
+			return ModifiedKeyHandling.DENY;
+		}
+
+	}
+
+	private static class HostKeyFile extends ModifiableFileWatcher
+			implements Supplier<List<HostEntryPair>> {
+
+		private List<HostEntryPair> entries = Collections.emptyList();
+
+		public HostKeyFile(Path path) {
+			super(path);
+		}
+
+		@Override
+		public List<HostEntryPair> get() {
+			Path path = getPath();
+			try {
+				if (checkReloadRequired()) {
+					if (!Files.exists(path)) {
+						// Has disappeared.
+						resetReloadAttributes();
+						return Collections.emptyList();
+					}
+					LockFile lock = new LockFile(path.toFile());
+					if (lock.lock()) {
+						try {
+							entries = reload(getPath());
+						} finally {
+							lock.unlock();
+						}
+					} else {
+						LOG.warn(format(SshdText.get().knownHostsFileLockedRead,
+								path));
+					}
+				}
+			} catch (IOException e) {
+				LOG.warn(format(SshdText.get().knownHostsFileReadFailed, path));
+			}
+			return Collections.unmodifiableList(entries);
+		}
+
+		private List<HostEntryPair> reload(Path path) throws IOException {
+			try {
+				List<KnownHostEntry> rawEntries = KnownHostEntryReader
+						.readFromFile(path);
+				updateReloadAttributes();
+				if (rawEntries == null || rawEntries.isEmpty()) {
+					return Collections.emptyList();
+				}
+				List<HostEntryPair> newEntries = new LinkedList<>();
+				for (KnownHostEntry entry : rawEntries) {
+					AuthorizedKeyEntry keyPart = entry.getKeyEntry();
+					if (keyPart == null) {
+						continue;
+					}
+					try {
+						PublicKey serverKey = keyPart.resolvePublicKey(
+								PublicKeyEntryResolver.IGNORING);
+						if (serverKey == null) {
+							LOG.warn(format(
+									SshdText.get().knownHostsUnknownKeyType,
+									path, entry.getConfigLine()));
+						} else {
+							newEntries.add(new HostEntryPair(entry, serverKey));
+						}
+					} catch (GeneralSecurityException e) {
+						LOG.warn(format(SshdText.get().knownHostsInvalidLine,
+								path, entry.getConfigLine()));
+					}
+				}
+				return newEntries;
+			} catch (FileNotFoundException e) {
+				resetReloadAttributes();
+				return Collections.emptyList();
+			}
+		}
+	}
+
+	// The stuff below is just a hack to avoid having to copy a lot of code from
+	// KnownHostsServerKeyVerifier
+
+	private static class HostKeyHelper extends KnownHostsServerKeyVerifier {
+
+		public HostKeyHelper() {
+			// These two arguments will never be used in any way.
+			super((c, r, s) -> false, new File(".").toPath()); //$NON-NLS-1$
+		}
+
+		@Override
+		protected KnownHostEntry prepareKnownHostEntry(
+				ClientSession clientSession, SocketAddress remoteAddress,
+				PublicKey serverKey) throws IOException {
+			// Make this method accessible
+			try {
+				return super.prepareKnownHostEntry(clientSession, remoteAddress,
+						serverKey);
+			} catch (Exception e) {
+				throw new IOException(e.getMessage(), e);
+			}
+		}
+
+		@Override
+		protected String prepareModifiedServerKeyLine(
+				ClientSession clientSession, SocketAddress remoteAddress,
+				KnownHostEntry entry, String curLine, PublicKey expected,
+				PublicKey actual) throws IOException {
+			// Make this method accessible
+			try {
+				return super.prepareModifiedServerKeyLine(clientSession,
+						remoteAddress, entry, curLine, expected, actual);
+			} catch (Exception e) {
+				throw new IOException(e.getMessage(), e);
+			}
+		}
+
+		@Override
+		protected Collection<SshdSocketAddress> resolveHostNetworkIdentities(
+				ClientSession clientSession, SocketAddress remoteAddress) {
+			// Make this method accessible
+			return super.resolveHostNetworkIdentities(clientSession,
+					remoteAddress);
+		}
+	}
+
+}
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
new file mode 100644
index 0000000..93bd102
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/PasswordProviderWrapper.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.URIish;
+import org.eclipse.jgit.transport.sshd.KeyPasswordProvider;
+
+/**
+ * A bridge from sshd's {@link RepeatingFilePasswordProvider} to our
+ * {@link KeyPasswordProvider} API.
+ */
+public class PasswordProviderWrapper implements RepeatingFilePasswordProvider {
+
+	private final KeyPasswordProvider delegate;
+
+	private Map<String, AtomicInteger> counts = new ConcurrentHashMap<>();
+
+	/**
+	 * @param delegate
+	 */
+	public PasswordProviderWrapper(@NonNull KeyPasswordProvider delegate) {
+		this.delegate = delegate;
+	}
+
+	@Override
+	public void setAttempts(int numberOfPasswordPrompts) {
+		delegate.setAttempts(numberOfPasswordPrompts);
+	}
+
+	@Override
+	public int getAttempts() {
+		return delegate.getAttempts();
+	}
+
+	@Override
+	public String getPassword(String resourceKey) throws IOException {
+		int attempt = counts
+				.computeIfAbsent(resourceKey, k -> new AtomicInteger()).get();
+		char[] passphrase = delegate.getPassphrase(toUri(resourceKey), attempt);
+		if (passphrase == null) {
+			return null;
+		}
+		try {
+			return new String(passphrase);
+		} finally {
+			Arrays.fill(passphrase, '\000');
+		}
+	}
+
+	@Override
+	public ResourceDecodeResult handleDecodeAttemptResult(String resourceKey,
+			String password, Exception err)
+			throws IOException, GeneralSecurityException {
+		AtomicInteger count = counts.get(resourceKey);
+		int numberOfAttempts = count == null ? 0 : count.incrementAndGet();
+		ResourceDecodeResult result = null;
+		try {
+			if (delegate.keyLoaded(toUri(resourceKey), numberOfAttempts, err)) {
+				result = ResourceDecodeResult.RETRY;
+			} else {
+				result = ResourceDecodeResult.TERMINATE;
+			}
+		} finally {
+			if (result != ResourceDecodeResult.RETRY) {
+				counts.remove(resourceKey);
+			}
+		}
+		return result;
+	}
+
+	/**
+	 * Creates a {@link URIish} from a given string. The
+	 * {@link CredentialsProvider} uses uris as resource identifications.
+	 *
+	 * @param resourceKey
+	 *            to convert
+	 * @return the uri
+	 */
+	private URIish toUri(String resourceKey) {
+		try {
+			return new URIish(resourceKey);
+		} catch (URISyntaxException e) {
+			return new URIish().setPath(resourceKey); // Doesn't check!!
+		}
+	}
+
+}
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
new file mode 100644
index 0000000..5d58bd6
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/RepeatingFilePasswordProvider.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+
+/**
+ * A {@link FilePasswordProvider} augmented to support repeatedly asking for
+ * passwords.
+ *
+ * @since 5.2
+ */
+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;
+	}
+
+	// The following part of this interface is from the upstream resolution of
+	// SSHD-850. See https://github.com/apache/mina-sshd/commit/f19bd2e34 .
+	// TODO: remove this once we move to sshd > 2.1.0
+
+	/**
+	 * Result value of
+	 * {@link RepeatingFilePasswordProvider#handleDecodeAttemptResult(String, String, Exception)}.
+	 */
+	public enum ResourceDecodeResult {
+		/** Re-throw the decoding exception. */
+		TERMINATE,
+		/** Retry the decoding process - including password prompt. */
+		RETRY,
+		/** Skip attempt and see if we can proceed without the key. */
+		IGNORE;
+	}
+
+	/**
+	 * Invoked to inform the password provider about the decoding result.
+	 * <b>Note:</b> any exception thrown from this method (including if called
+	 * to inform about success) will be propagated instead of the original (if
+	 * any was reported)
+	 *
+	 * @param resourceKey
+	 *            The resource key representing the <U>private</U> file
+	 * @param password
+	 *            The password that was attempted
+	 * @param err
+	 *            The attempt result - {@code null} for success
+	 * @return How to proceed in case of error - <u>ignored</u> if invoked in
+	 *         order to report success. <b>Note:</b> {@code null} is same as
+	 *         {@link ResourceDecodeResult#TERMINATE}.
+	 * @throws IOException
+	 * @throws GeneralSecurityException
+	 */
+	ResourceDecodeResult handleDecodeAttemptResult(String resourceKey,
+			String password, Exception err)
+			throws IOException, GeneralSecurityException;
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/ServerKeyLookup.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/ServerKeyLookup.java
new file mode 100644
index 0000000..4f5f497
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/ServerKeyLookup.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd;
+
+import java.net.SocketAddress;
+import java.util.List;
+
+import org.apache.sshd.client.keyverifier.KnownHostsServerKeyVerifier.HostEntryPair;
+import org.apache.sshd.client.session.ClientSession;
+import org.eclipse.jgit.annotations.NonNull;
+
+/**
+ * Offers operations to retrieve server keys from known_hosts files.
+ */
+public interface ServerKeyLookup {
+
+	/**
+	 * Retrieves all entries for a given remote address.
+	 *
+	 * @param session
+	 *            needed to determine the config files if specified in the ssh
+	 *            config
+	 * @param remote
+	 *            to find entries for
+	 * @return a possibly empty list of entries found, including revoked ones
+	 */
+	@NonNull
+	List<HostEntryPair> lookup(ClientSession session, SocketAddress remote);
+}
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
new file mode 100644
index 0000000..5c79f2d
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java
@@ -0,0 +1,91 @@
+package org.eclipse.jgit.internal.transport.sshd;
+
+import org.eclipse.jgit.nls.NLS;
+import org.eclipse.jgit.nls.TranslationBundle;
+
+/**
+ * Externalized text messages for localization.
+ */
+public final class SshdText extends TranslationBundle {
+
+	/**
+	 * Get an instance of this translation bundle.
+	 *
+	 * @return an instance of this translation bundle
+	 */
+	public static SshdText get() {
+		return NLS.getBundleFor(SshdText.class);
+	}
+
+	// @formatter:off
+	/***/ public String authenticationCanceled;
+	/***/ public String closeListenerFailed;
+	/***/ public String configInvalidPath;
+	/***/ public String configInvalidPattern;
+	/***/ public String configInvalidPositive;
+	/***/ public String configNoKnownHostKeyAlgorithms;
+	/***/ public String configNoRemainingHostKeyAlgorithms;
+	/***/ public String ftpCloseFailed;
+	/***/ public String gssapiFailure;
+	/***/ public String gssapiInitFailure;
+	/***/ public String gssapiUnexpectedMechanism;
+	/***/ public String gssapiUnexpectedMessage;
+	/***/ public String identityFileCannotDecrypt;
+	/***/ public String identityFileNoKey;
+	/***/ public String identityFileMultipleKeys;
+	/***/ public String identityFileUnsupportedFormat;
+	/***/ public String kexServerKeyInvalid;
+	/***/ public String keyEncryptedMsg;
+	/***/ public String keyEncryptedPrompt;
+	/***/ public String keyEncryptedRetry;
+	/***/ public String keyLoadFailed;
+	/***/ public String knownHostsCouldNotUpdate;
+	/***/ public String knownHostsFileLockedRead;
+	/***/ public String knownHostsFileLockedUpdate;
+	/***/ public String knownHostsFileReadFailed;
+	/***/ public String knownHostsInvalidLine;
+	/***/ public String knownHostsInvalidPath;
+	/***/ public String knownHostsKeyFingerprints;
+	/***/ public String knownHostsModifiedKeyAcceptPrompt;
+	/***/ public String knownHostsModifiedKeyDenyMsg;
+	/***/ public String knownHostsModifiedKeyStorePrompt;
+	/***/ public String knownHostsModifiedKeyWarning;
+	/***/ public String knownHostsRevokedKeyMsg;
+	/***/ public String knownHostsUnknownKeyMsg;
+	/***/ public String knownHostsUnknownKeyPrompt;
+	/***/ public String knownHostsUnknownKeyType;
+	/***/ public String knownHostsUserAskCreationMsg;
+	/***/ public String knownHostsUserAskCreationPrompt;
+	/***/ public String passwordPrompt;
+	/***/ public String proxyCannotAuthenticate;
+	/***/ public String proxyHttpFailure;
+	/***/ public String proxyHttpInvalidUserName;
+	/***/ public String proxyHttpUnexpectedReply;
+	/***/ public String proxyHttpUnspecifiedFailureReason;
+	/***/ public String proxyPasswordPrompt;
+	/***/ public String proxySocksAuthenticationFailed;
+	/***/ public String proxySocksFailureForbidden;
+	/***/ public String proxySocksFailureGeneral;
+	/***/ public String proxySocksFailureHostUnreachable;
+	/***/ public String proxySocksFailureNetworkUnreachable;
+	/***/ public String proxySocksFailureRefused;
+	/***/ public String proxySocksFailureTTL;
+	/***/ public String proxySocksFailureUnspecified;
+	/***/ public String proxySocksFailureUnsupportedAddress;
+	/***/ public String proxySocksFailureUnsupportedCommand;
+	/***/ public String proxySocksGssApiFailure;
+	/***/ public String proxySocksGssApiMessageTooShort;
+	/***/ public String proxySocksGssApiUnknownMessage;
+	/***/ public String proxySocksGssApiVersionMismatch;
+	/***/ public String proxySocksNoRemoteHostName;
+	/***/ public String proxySocksPasswordTooLong;
+	/***/ public String proxySocksUnexpectedMessage;
+	/***/ public String proxySocksUnexpectedVersion;
+	/***/ public String proxySocksUsernameTooLong;
+	/***/ public String sessionCloseFailed;
+	/***/ public String sshClosingDown;
+	/***/ public String sshCommandTimeout;
+	/***/ public String sshProcessStillRunning;
+	/***/ public String unknownProxyProtocol;
+
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/auth/AbstractAuthenticationHandler.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/auth/AbstractAuthenticationHandler.java
new file mode 100644
index 0000000..6caa1b6
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/auth/AbstractAuthenticationHandler.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd.auth;
+
+import java.net.InetSocketAddress;
+
+/**
+ * Abstract base class for {@link AuthenticationHandler}s encapsulating basic
+ * common things.
+ *
+ * @param <ParameterType>
+ *            defining the parameter type for the authentication
+ * @param <TokenType>
+ *            defining the token type for the authentication
+ */
+public abstract class AbstractAuthenticationHandler<ParameterType, TokenType>
+		implements AuthenticationHandler<ParameterType, TokenType> {
+
+	/** The {@link InetSocketAddress} or the proxy to connect to. */
+	protected InetSocketAddress proxy;
+
+	/** The last set parameters. */
+	protected ParameterType params;
+
+	/** A flag telling whether this authentication is done. */
+	protected boolean done;
+
+	/**
+	 * Creates a new {@link AbstractAuthenticationHandler} to authenticate with
+	 * the given {@code proxy}.
+	 *
+	 * @param proxy
+	 *            the {@link InetSocketAddress} of the proxy to connect to
+	 */
+	public AbstractAuthenticationHandler(InetSocketAddress proxy) {
+		this.proxy = proxy;
+	}
+
+	@Override
+	public final void setParams(ParameterType input) {
+		params = input;
+	}
+
+	@Override
+	public final boolean isDone() {
+		return done;
+	}
+
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/auth/AuthenticationHandler.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/auth/AuthenticationHandler.java
new file mode 100644
index 0000000..0e98a2e
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/auth/AuthenticationHandler.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd.auth;
+
+import java.io.Closeable;
+
+/**
+ * An {@code AuthenticationHandler} encapsulates a possibly multi-step
+ * authentication protocol. Intended usage:
+ *
+ * <pre>
+ * setParams(something);
+ * start();
+ * sendToken(getToken());
+ * while (!isDone()) {
+ * 	setParams(receiveMessageAndExtractParams());
+ * 	process();
+ * 	Object t = getToken();
+ * 	if (t != null) {
+ * 		sendToken(t);
+ * 	}
+ * }
+ * </pre>
+ *
+ * An {@code AuthenticationHandler} may be stateful and therefore is a
+ * {@link Closeable}.
+ *
+ * @param <ParameterType>
+ *            defining the parameter type for {@link #setParams(Object)}
+ * @param <TokenType>
+ *            defining the token type for {@link #getToken()}
+ */
+public interface AuthenticationHandler<ParameterType, TokenType>
+		extends Closeable {
+
+	/**
+	 * Produces the initial authentication token that can be then retrieved via
+	 * {@link #getToken()}.
+	 *
+	 * @throws Exception
+	 *             if an error occurs
+	 */
+	void start() throws Exception;
+
+	/**
+	 * Produces the next authentication token, if any.
+	 *
+	 * @throws Exception
+	 *             if an error occurs
+	 */
+	void process() throws Exception;
+
+	/**
+	 * Sets the parameters for the next token generation via {@link #start()} or
+	 * {@link #process()}.
+	 *
+	 * @param input
+	 *            to set, may be {@code null}
+	 */
+	void setParams(ParameterType input);
+
+	/**
+	 * Retrieves the last token generated.
+	 *
+	 * @return the token, or {@code null} if there is none
+	 * @throws Exception
+	 *             if an error occurs
+	 */
+	TokenType getToken() throws Exception;
+
+	/**
+	 * Tells whether is authentication mechanism is done (successfully or
+	 * unsuccessfully).
+	 *
+	 * @return whether this authentication is done
+	 */
+	boolean isDone();
+
+	@Override
+	void close();
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/auth/BasicAuthentication.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/auth/BasicAuthentication.java
new file mode 100644
index 0000000..efb1f55
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/auth/BasicAuthentication.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd.auth;
+
+import java.net.Authenticator;
+import java.net.Authenticator.RequestorType;
+import java.net.InetSocketAddress;
+import java.net.PasswordAuthentication;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Arrays;
+import java.util.concurrent.CancellationException;
+
+import org.eclipse.jgit.internal.transport.sshd.SshdText;
+import org.eclipse.jgit.transport.SshConstants;
+
+/**
+ * An abstract implementation of a username-password authentication. It can be
+ * given an initial known username-password pair; if so, this will be tried
+ * first. Subsequent rounds will then try to obtain a user name and password via
+ * the global {@link Authenticator}.
+ *
+ * @param <ParameterType>
+ *            defining the parameter type for the authentication
+ * @param <TokenType>
+ *            defining the token type for the authentication
+ */
+public abstract class BasicAuthentication<ParameterType, TokenType>
+		extends AbstractAuthenticationHandler<ParameterType, TokenType> {
+
+	/** The current user name. */
+	protected String user;
+
+	/** The current password. */
+	protected byte[] password;
+
+	/**
+	 * Creates a new {@link BasicAuthentication} to authenticate with the given
+	 * {@code proxy}.
+	 *
+	 * @param proxy
+	 *            {@link InetSocketAddress} of the proxy to connect to
+	 * @param initialUser
+	 *            initial user name to try; may be {@code null}
+	 * @param initialPassword
+	 *            initial password to try, may be {@code null}
+	 */
+	public BasicAuthentication(InetSocketAddress proxy, String initialUser,
+			char[] initialPassword) {
+		super(proxy);
+		this.user = initialUser;
+		this.password = convert(initialPassword);
+	}
+
+	private byte[] convert(char[] pass) {
+		if (pass == null) {
+			return new byte[0];
+		}
+		ByteBuffer bytes = StandardCharsets.UTF_8.encode(CharBuffer.wrap(pass));
+		byte[] pwd = new byte[bytes.remaining()];
+		bytes.get(pwd);
+		if (bytes.hasArray()) {
+			Arrays.fill(bytes.array(), (byte) 0);
+		}
+		Arrays.fill(pass, '\000');
+		return pwd;
+	}
+
+	/**
+	 * Clears the {@link #password}.
+	 */
+	protected void clearPassword() {
+		if (password != null) {
+			Arrays.fill(password, (byte) 0);
+		}
+		password = new byte[0];
+	}
+
+	@Override
+	public final void close() {
+		clearPassword();
+		done = true;
+	}
+
+	@Override
+	public final void start() throws Exception {
+		if (user != null && !user.isEmpty()
+				|| password != null && password.length > 0) {
+			return;
+		}
+		askCredentials();
+	}
+
+	@Override
+	public void process() throws Exception {
+		askCredentials();
+	}
+
+	/**
+	 * Asks for credentials via the global {@link Authenticator}.
+	 */
+	protected void askCredentials() {
+		clearPassword();
+		PasswordAuthentication auth = AccessController
+				.doPrivileged(new PrivilegedAction<PasswordAuthentication>() {
+
+					@Override
+					public PasswordAuthentication run() {
+						return Authenticator.requestPasswordAuthentication(
+								proxy.getHostString(), proxy.getAddress(),
+								proxy.getPort(), SshConstants.SSH_SCHEME,
+								SshdText.get().proxyPasswordPrompt, "Basic", //$NON-NLS-1$
+								null, RequestorType.PROXY);
+					}
+				});
+		if (auth == null) {
+			user = ""; //$NON-NLS-1$
+			throw new CancellationException(
+					SshdText.get().authenticationCanceled);
+		}
+		user = auth.getUserName();
+		password = convert(auth.getPassword());
+	}
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/auth/GssApiAuthentication.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/auth/GssApiAuthentication.java
new file mode 100644
index 0000000..63cc954
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/auth/GssApiAuthentication.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd.auth;
+
+import static java.text.MessageFormat.format;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+
+import org.eclipse.jgit.internal.transport.sshd.GssApiMechanisms;
+import org.eclipse.jgit.internal.transport.sshd.SshdText;
+import org.ietf.jgss.GSSContext;
+
+/**
+ * An abstract implementation of a GSS-API multi-round authentication.
+ *
+ * @param <ParameterType>
+ *            defining the parameter type for the authentication
+ * @param <TokenType>
+ *            defining the token type for the authentication
+ */
+public abstract class GssApiAuthentication<ParameterType, TokenType>
+		extends AbstractAuthenticationHandler<ParameterType, TokenType> {
+
+	private GSSContext context;
+
+	/** The last token generated. */
+	protected byte[] token;
+
+	/**
+	 * Creates a new {@link GssApiAuthentication} to authenticate with the given
+	 * {@code proxy}.
+	 *
+	 * @param proxy
+	 *            the {@link InetSocketAddress} of the proxy to connect to
+	 */
+	public GssApiAuthentication(InetSocketAddress proxy) {
+		super(proxy);
+	}
+
+	@Override
+	public void close() {
+		GssApiMechanisms.closeContextSilently(context);
+		context = null;
+		done = true;
+	}
+
+	@Override
+	public final void start() throws Exception {
+		try {
+			context = createContext();
+			context.requestMutualAuth(true);
+			context.requestConf(false);
+			context.requestInteg(false);
+			byte[] empty = new byte[0];
+			token = context.initSecContext(empty, 0, 0);
+		} catch (Exception e) {
+			close();
+			throw e;
+		}
+	}
+
+	@Override
+	public final void process() throws Exception {
+		if (context == null) {
+			throw new IOException(
+					format(SshdText.get().proxyCannotAuthenticate, proxy));
+		}
+		try {
+			byte[] received = extractToken(params);
+			token = context.initSecContext(received, 0, received.length);
+			checkDone();
+		} catch (Exception e) {
+			close();
+			throw e;
+		}
+	}
+
+	private void checkDone() throws Exception {
+		done = context.isEstablished();
+		if (done) {
+			context.dispose();
+			context = null;
+		}
+	}
+
+	/**
+	 * Creates the {@link GSSContext} to use.
+	 *
+	 * @return a fresh {@link GSSContext} to use
+	 * @throws Exception
+	 *             if the context cannot be created
+	 */
+	protected abstract GSSContext createContext() throws Exception;
+
+	/**
+	 * Extracts the token from the last set parameters.
+	 *
+	 * @param input
+	 *            to extract the token from
+	 * @return the extracted token, or {@code null} if none
+	 * @throws Exception
+	 *             if an error occurs
+	 */
+	protected abstract byte[] extractToken(ParameterType input)
+			throws Exception;
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/AbstractClientProxyConnector.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/AbstractClientProxyConnector.java
new file mode 100644
index 0000000..2e87c57
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/AbstractClientProxyConnector.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd.proxy;
+
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.sshd.client.session.ClientSession;
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.internal.transport.sshd.JGitClientSession;
+
+/**
+ * Basic common functionality for a {@link StatefulProxyConnector}.
+ */
+public abstract class AbstractClientProxyConnector
+		implements StatefulProxyConnector {
+
+	private static final long DEFAULT_PROXY_TIMEOUT_MILLIS = TimeUnit.SECONDS
+			.toMillis(30L);
+
+	/** Guards {@link #done} and {@link #bufferedCommands}. */
+	private Object lock = new Object();
+
+	private boolean done;
+
+	private List<Callable<Void>> bufferedCommands = new ArrayList<>();
+
+	private AtomicReference<Runnable> unregister = new AtomicReference<>();
+
+	private long remainingProxyProtocolTime = DEFAULT_PROXY_TIMEOUT_MILLIS;
+
+	private long lastProxyOperationTime = 0L;
+
+	/** The ultimate remote address to connect to. */
+	protected final InetSocketAddress remoteAddress;
+
+	/** The proxy address. */
+	protected final InetSocketAddress proxyAddress;
+
+	/** The user to authenticate at the proxy with. */
+	protected String proxyUser;
+
+	/** The password to use for authentication at the proxy. */
+	protected char[] proxyPassword;
+
+	/**
+	 * Creates a new {@link AbstractClientProxyConnector}.
+	 *
+	 * @param proxyAddress
+	 *            of the proxy server we're connecting to
+	 * @param remoteAddress
+	 *            of the target server to connect to
+	 * @param proxyUser
+	 *            to authenticate at the proxy with; may be {@code null}
+	 * @param proxyPassword
+	 *            to authenticate at the proxy with; may be {@code null}
+	 */
+	public AbstractClientProxyConnector(@NonNull InetSocketAddress proxyAddress,
+			@NonNull InetSocketAddress remoteAddress, String proxyUser,
+			char[] proxyPassword) {
+		this.proxyAddress = proxyAddress;
+		this.remoteAddress = remoteAddress;
+		this.proxyUser = proxyUser;
+		this.proxyPassword = proxyPassword == null ? new char[0]
+				: proxyPassword;
+	}
+
+	/**
+	 * Initializes this instance. Installs itself as proxy handler on the
+	 * session.
+	 *
+	 * @param session
+	 *            to initialize for
+	 */
+	protected void init(ClientSession session) {
+		remainingProxyProtocolTime = session.getLongProperty(
+				StatefulProxyConnector.TIMEOUT_PROPERTY,
+				DEFAULT_PROXY_TIMEOUT_MILLIS);
+		if (remainingProxyProtocolTime <= 0L) {
+			remainingProxyProtocolTime = DEFAULT_PROXY_TIMEOUT_MILLIS;
+		}
+		if (session instanceof JGitClientSession) {
+			JGitClientSession s = (JGitClientSession) session;
+			unregister.set(() -> s.setProxyHandler(null));
+			s.setProxyHandler(this);
+		} else {
+			// Internal error, no translation
+			throw new IllegalStateException(
+					"Not a JGit session: " + session.getClass().getName()); //$NON-NLS-1$
+		}
+	}
+
+	/**
+	 * Obtains the timeout for the whole rest of the proxy connection protocol.
+	 *
+	 * @return the timeout in milliseconds, always > 0L
+	 */
+	protected long getTimeout() {
+		long last = lastProxyOperationTime;
+		long now = System.nanoTime();
+		lastProxyOperationTime = now;
+		long remaining = remainingProxyProtocolTime;
+		if (last != 0L) {
+			long elapsed = now - last;
+			remaining -= elapsed;
+			if (remaining < 0L) {
+				remaining = 10L; // Give it grace period.
+			}
+		}
+		remainingProxyProtocolTime = remaining;
+		return remaining;
+	}
+
+	/**
+	 * Adjusts the timeout calculation to not account of elapsed time since the
+	 * last time the timeout was gotten. Can be used for instance to ignore time
+	 * spent in user dialogs be counted against the overall proxy connection
+	 * protocol timeout.
+	 */
+	protected void adjustTimeout() {
+		lastProxyOperationTime = System.nanoTime();
+	}
+
+	/**
+	 * Sets the "done" flag.
+	 *
+	 * @param success
+	 *            whether the connector terminated successfully.
+	 * @throws Exception
+	 *             if starting ssh fails
+	 */
+	protected void setDone(boolean success) throws Exception {
+		List<Callable<Void>> buffered;
+		Runnable unset = unregister.getAndSet(null);
+		if (unset != null) {
+			unset.run();
+		}
+		synchronized (lock) {
+			done = true;
+			buffered = bufferedCommands;
+			bufferedCommands = null;
+		}
+		if (success && buffered != null) {
+			for (Callable<Void> starter : buffered) {
+				starter.call();
+			}
+		}
+	}
+
+	@Override
+	public void runWhenDone(Callable<Void> starter) throws Exception {
+		synchronized (lock) {
+			if (!done) {
+				bufferedCommands.add(starter);
+				return;
+			}
+		}
+		starter.call();
+	}
+
+	/**
+	 * Clears the proxy password.
+	 */
+	protected void clearPassword() {
+		Arrays.fill(proxyPassword, '\000');
+		proxyPassword = new char[0];
+	}
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/AuthenticationChallenge.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/AuthenticationChallenge.java
new file mode 100644
index 0000000..4a6572d
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/AuthenticationChallenge.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd.proxy;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.eclipse.jgit.annotations.NonNull;
+
+/**
+ * A simple representation of an authentication challenge as sent in a
+ * "WWW-Authenticate" or "Proxy-Authenticate" header. Such challenges start with
+ * a mechanism name, followed either by one single token, or by a list of
+ * key=value pairs.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc7235#section-2.1">RFC 7235, sec.
+ *      2.1</a>
+ */
+public class AuthenticationChallenge {
+
+	private final String mechanism;
+
+	private String token;
+
+	private Map<String, String> arguments;
+
+	/**
+	 * Create a new {@link AuthenticationChallenge} with the given mechanism.
+	 *
+	 * @param mechanism
+	 *            for the challenge
+	 */
+	public AuthenticationChallenge(String mechanism) {
+		this.mechanism = mechanism;
+	}
+
+	/**
+	 * Retrieves the authentication mechanism specified by this challenge, for
+	 * instance "Basic".
+	 *
+	 * @return the mechanism name
+	 */
+	public String getMechanism() {
+		return mechanism;
+	}
+
+	/**
+	 * Retrieves the token of the challenge, if any.
+	 *
+	 * @return the token, or {@code null} if there is none.
+	 */
+	public String getToken() {
+		return token;
+	}
+
+	/**
+	 * Retrieves the arguments of the challenge.
+	 *
+	 * @return a possibly empty map of the key=value arguments of the challenge
+	 */
+	@NonNull
+	public Map<String, String> getArguments() {
+		return arguments == null ? Collections.emptyMap() : arguments;
+	}
+
+	void addArgument(String key, String value) {
+		if (arguments == null) {
+			arguments = new LinkedHashMap<>();
+		}
+		arguments.put(key, value);
+	}
+
+	void setToken(String token) {
+		this.token = token;
+	}
+
+	@Override
+	public String toString() {
+		return "AuthenticationChallenge[" + mechanism + ',' + token + ',' //$NON-NLS-1$
+				+ (arguments == null ? "<none>" : arguments.toString()) + ']'; //$NON-NLS-1$
+	}
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/HttpClientConnector.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/HttpClientConnector.java
new file mode 100644
index 0000000..46cdd52
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/HttpClientConnector.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd.proxy;
+
+import static java.text.MessageFormat.format;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.io.IoSession;
+import org.apache.sshd.common.util.Readable;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.internal.transport.sshd.GssApiMechanisms;
+import org.eclipse.jgit.internal.transport.sshd.SshdText;
+import org.eclipse.jgit.internal.transport.sshd.auth.AuthenticationHandler;
+import org.eclipse.jgit.internal.transport.sshd.auth.BasicAuthentication;
+import org.eclipse.jgit.internal.transport.sshd.auth.GssApiAuthentication;
+import org.eclipse.jgit.util.Base64;
+import org.ietf.jgss.GSSContext;
+
+/**
+ * Simple HTTP proxy connector using Basic Authentication.
+ */
+public class HttpClientConnector extends AbstractClientProxyConnector {
+
+	private static final String HTTP_HEADER_PROXY_AUTHENTICATION = "Proxy-Authentication:"; //$NON-NLS-1$
+
+	private static final String HTTP_HEADER_PROXY_AUTHORIZATION = "Proxy-Authorization:"; //$NON-NLS-1$
+
+	private HttpAuthenticationHandler basic;
+
+	private HttpAuthenticationHandler negotiate;
+
+	private List<HttpAuthenticationHandler> availableAuthentications;
+
+	private Iterator<HttpAuthenticationHandler> clientAuthentications;
+
+	private HttpAuthenticationHandler authenticator;
+
+	private boolean ongoing;
+
+	/**
+	 * Creates a new {@link HttpClientConnector}. The connector supports
+	 * anonymous proxy connections as well as Basic and Negotiate
+	 * authentication.
+	 *
+	 * @param proxyAddress
+	 *            of the proxy server we're connecting to
+	 * @param remoteAddress
+	 *            of the target server to connect to
+	 */
+	public HttpClientConnector(@NonNull InetSocketAddress proxyAddress,
+			@NonNull InetSocketAddress remoteAddress) {
+		this(proxyAddress, remoteAddress, null, null);
+	}
+
+	/**
+	 * Creates a new {@link HttpClientConnector}. The connector supports
+	 * anonymous proxy connections as well as Basic and Negotiate
+	 * authentication. If a user name and password are given, the connector
+	 * tries pre-emptive Basic authentication.
+	 *
+	 * @param proxyAddress
+	 *            of the proxy server we're connecting to
+	 * @param remoteAddress
+	 *            of the target server to connect to
+	 * @param proxyUser
+	 *            to authenticate at the proxy with
+	 * @param proxyPassword
+	 *            to authenticate at the proxy with
+	 */
+	public HttpClientConnector(@NonNull InetSocketAddress proxyAddress,
+			@NonNull InetSocketAddress remoteAddress, String proxyUser,
+			char[] proxyPassword) {
+		super(proxyAddress, remoteAddress, proxyUser, proxyPassword);
+		basic = new HttpBasicAuthentication();
+		negotiate = new NegotiateAuthentication();
+		availableAuthentications = new ArrayList<>(2);
+		availableAuthentications.add(negotiate);
+		availableAuthentications.add(basic);
+		clientAuthentications = availableAuthentications.iterator();
+	}
+
+	private void close() {
+		HttpAuthenticationHandler current = authenticator;
+		authenticator = null;
+		if (current != null) {
+			current.close();
+		}
+	}
+
+	@Override
+	public void sendClientProxyMetadata(ClientSession sshSession)
+			throws Exception {
+		init(sshSession);
+		IoSession session = sshSession.getIoSession();
+		session.addCloseFutureListener(f -> close());
+		StringBuilder msg = connect();
+		if (proxyUser != null && !proxyUser.isEmpty()
+				|| proxyPassword != null && proxyPassword.length > 0) {
+			authenticator = basic;
+			basic.setParams(null);
+			basic.start();
+			msg = authenticate(msg, basic.getToken());
+			clearPassword();
+			proxyUser = null;
+		}
+		ongoing = true;
+		try {
+			send(msg, session);
+		} catch (Exception e) {
+			ongoing = false;
+			throw e;
+		}
+	}
+
+	private void send(StringBuilder msg, IoSession session) throws Exception {
+		byte[] data = eol(msg).toString().getBytes(StandardCharsets.US_ASCII);
+		Buffer buffer = new ByteArrayBuffer(data.length, false);
+		buffer.putRawBytes(data);
+		session.writePacket(buffer).verify(getTimeout());
+	}
+
+	private StringBuilder connect() {
+		StringBuilder msg = new StringBuilder();
+		// Persistent connections are the default in HTTP 1.1 (see RFC 2616),
+		// but let's be explicit.
+		return msg.append(format(
+				"CONNECT {0}:{1} HTTP/1.1\r\nProxy-Connection: keep-alive\r\nConnection: keep-alive\r\nHost: {0}:{1}\r\n", //$NON-NLS-1$
+				remoteAddress.getHostString(),
+				Integer.toString(remoteAddress.getPort())));
+	}
+
+	private StringBuilder authenticate(StringBuilder msg, String token) {
+		msg.append(HTTP_HEADER_PROXY_AUTHORIZATION).append(' ').append(token);
+		return eol(msg);
+	}
+
+	private StringBuilder eol(StringBuilder msg) {
+		return msg.append('\r').append('\n');
+	}
+
+	@Override
+	public void messageReceived(IoSession session, Readable buffer)
+			throws Exception {
+		try {
+			int length = buffer.available();
+			byte[] data = new byte[length];
+			buffer.getRawBytes(data, 0, length);
+			String[] reply = new String(data, StandardCharsets.US_ASCII)
+					.split("\r\n"); //$NON-NLS-1$
+			handleMessage(session, Arrays.asList(reply));
+		} catch (Exception e) {
+			if (authenticator != null) {
+				authenticator.close();
+				authenticator = null;
+			}
+			ongoing = false;
+			try {
+				setDone(false);
+			} catch (Exception inner) {
+				e.addSuppressed(inner);
+			}
+			throw e;
+		}
+	}
+
+	private void handleMessage(IoSession session, List<String> reply)
+			throws Exception {
+		if (reply.isEmpty() || reply.get(0).isEmpty()) {
+			throw new IOException(
+					format(SshdText.get().proxyHttpUnexpectedReply,
+							proxyAddress, "<empty>")); //$NON-NLS-1$
+		}
+		try {
+			StatusLine status = HttpParser.parseStatusLine(reply.get(0));
+			if (!ongoing) {
+				throw new IOException(format(
+						SshdText.get().proxyHttpUnexpectedReply, proxyAddress,
+						Integer.toString(status.getResultCode()),
+						status.getReason()));
+			}
+			switch (status.getResultCode()) {
+			case HttpURLConnection.HTTP_OK:
+				if (authenticator != null) {
+					authenticator.close();
+				}
+				authenticator = null;
+				ongoing = false;
+				setDone(true);
+				break;
+			case HttpURLConnection.HTTP_PROXY_AUTH:
+				List<AuthenticationChallenge> challenges = HttpParser
+						.getAuthenticationHeaders(reply,
+								HTTP_HEADER_PROXY_AUTHENTICATION);
+				authenticator = selectProtocol(challenges, authenticator);
+				if (authenticator == null) {
+					throw new IOException(
+							format(SshdText.get().proxyCannotAuthenticate,
+									proxyAddress));
+				}
+				String token = authenticator.getToken();
+				if (token == null) {
+					throw new IOException(
+							format(SshdText.get().proxyCannotAuthenticate,
+									proxyAddress));
+				}
+				send(authenticate(connect(), token), session);
+				break;
+			default:
+				throw new IOException(format(SshdText.get().proxyHttpFailure,
+						proxyAddress, Integer.toString(status.getResultCode()),
+						status.getReason()));
+			}
+		} catch (HttpParser.ParseException e) {
+			throw new IOException(
+					format(SshdText.get().proxyHttpUnexpectedReply,
+					proxyAddress, reply.get(0)));
+		}
+	}
+
+	private HttpAuthenticationHandler selectProtocol(
+			List<AuthenticationChallenge> challenges,
+			HttpAuthenticationHandler current) throws Exception {
+		if (current != null && !current.isDone()) {
+			AuthenticationChallenge challenge = getByName(challenges,
+					current.getName());
+			if (challenge != null) {
+				current.setParams(challenge);
+				current.process();
+				return current;
+			}
+		}
+		if (current != null) {
+			current.close();
+		}
+		while (clientAuthentications.hasNext()) {
+			HttpAuthenticationHandler next = clientAuthentications.next();
+			if (!next.isDone()) {
+				AuthenticationChallenge challenge = getByName(challenges,
+						next.getName());
+				if (challenge != null) {
+					next.setParams(challenge);
+					next.start();
+					return next;
+				}
+			}
+		}
+		return null;
+	}
+
+	private AuthenticationChallenge getByName(
+			List<AuthenticationChallenge> challenges,
+			String name) {
+		return challenges.stream()
+				.filter(c -> c.getMechanism().equalsIgnoreCase(name))
+				.findFirst().orElse(null);
+	}
+
+	private interface HttpAuthenticationHandler
+			extends AuthenticationHandler<AuthenticationChallenge, String> {
+
+		public String getName();
+	}
+
+	/**
+	 * @see <a href="https://tools.ietf.org/html/rfc7617">RFC 7617</a>
+	 */
+	private class HttpBasicAuthentication
+			extends BasicAuthentication<AuthenticationChallenge, String>
+			implements HttpAuthenticationHandler {
+
+		private boolean asked;
+
+		public HttpBasicAuthentication() {
+			super(proxyAddress, proxyUser, proxyPassword);
+		}
+
+		@Override
+		public String getName() {
+			return "Basic"; //$NON-NLS-1$
+		}
+
+		@Override
+		protected void askCredentials() {
+			// We ask only once.
+			if (asked) {
+				throw new IllegalStateException(
+						"Basic auth: already asked user for password"); //$NON-NLS-1$
+			}
+			asked = true;
+			super.askCredentials();
+			done = true;
+		}
+
+		@Override
+		public String getToken() throws Exception {
+			if (user.indexOf(':') >= 0) {
+				throw new IOException(format(
+						SshdText.get().proxyHttpInvalidUserName, proxy, user));
+			}
+			byte[] rawUser = user.getBytes(StandardCharsets.UTF_8);
+			byte[] toEncode = new byte[rawUser.length + 1 + password.length];
+			System.arraycopy(rawUser, 0, toEncode, 0, rawUser.length);
+			toEncode[rawUser.length] = ':';
+			System.arraycopy(password, 0, toEncode, rawUser.length + 1,
+					password.length);
+			Arrays.fill(password, (byte) 0);
+			String result = Base64.encodeBytes(toEncode);
+			Arrays.fill(toEncode, (byte) 0);
+			return getName() + ' ' + result;
+		}
+
+	}
+
+	/**
+	 * @see <a href="https://tools.ietf.org/html/rfc4559">RFC 4559</a>
+	 */
+	private class NegotiateAuthentication
+			extends GssApiAuthentication<AuthenticationChallenge, String>
+			implements HttpAuthenticationHandler {
+
+		public NegotiateAuthentication() {
+			super(proxyAddress);
+		}
+
+		@Override
+		public String getName() {
+			return "Negotiate"; //$NON-NLS-1$
+		}
+
+		@Override
+		public String getToken() throws Exception {
+			return getName() + ' ' + Base64.encodeBytes(token);
+		}
+
+		@Override
+		protected GSSContext createContext() throws Exception {
+			return GssApiMechanisms.createContext(GssApiMechanisms.SPNEGO,
+					GssApiMechanisms.getCanonicalName(proxyAddress));
+		}
+
+		@Override
+		protected byte[] extractToken(AuthenticationChallenge input)
+				throws Exception {
+			String received = input.getToken();
+			if (received == null) {
+				return new byte[0];
+			}
+			return Base64.decode(received);
+		}
+
+	}
+}
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
new file mode 100644
index 0000000..b9b32b1
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/HttpParser.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd.proxy;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A basic parser for HTTP response headers. Handles status lines and
+ * authentication headers (WWW-Authenticate, Proxy-Authenticate).
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc7230">RFC 7230</a>
+ * @see <a href="https://tools.ietf.org/html/rfc7235">RFC 7235</a>
+ */
+public final class HttpParser {
+
+	/**
+	 * An exception indicating some problem parsing HTPP headers.
+	 */
+	public static class ParseException extends Exception {
+
+		private static final long serialVersionUID = -1634090143702048640L;
+
+	}
+
+	private HttpParser() {
+		// No instantiation
+	}
+
+	/**
+	 * Parse a HTTP response status line.
+	 *
+	 * @param line
+	 *            to parse
+	 * @return the {@link StatusLine}
+	 * @throws ParseException
+	 *             if the line cannot be parsed or has the wrong HTTP version
+	 */
+	public static StatusLine parseStatusLine(String line)
+			throws ParseException {
+		// Format is HTTP/<version> Code Reason
+		int firstBlank = line.indexOf(' ');
+		if (firstBlank < 0) {
+			throw new ParseException();
+		}
+		int secondBlank = line.indexOf(' ', firstBlank + 1);
+		if (secondBlank < 0) {
+			// Accept the line even if the (according to RFC 2616 mandatory)
+			// reason is missing.
+			secondBlank = line.length();
+		}
+		int resultCode;
+		try {
+			resultCode = Integer.parseUnsignedInt(
+					line.substring(firstBlank + 1, secondBlank));
+		} catch (NumberFormatException e) {
+			throw new ParseException();
+		}
+		// Again, accept even if the reason is missing
+		String reason = ""; //$NON-NLS-1$
+		if (secondBlank < line.length()) {
+			reason = line.substring(secondBlank + 1);
+		}
+		return new StatusLine(line.substring(0, firstBlank), resultCode,
+				reason);
+	}
+
+	/**
+	 * Extract the authentication headers from the header lines. It is assumed
+	 * that the first element in {@code reply} is the raw status line as
+	 * received from the server. It is skipped. Line processing stops on the
+	 * first empty line thereafter.
+	 *
+	 * @param reply
+	 *            The complete (header) lines of the HTTP response
+	 * @param authenticationHeader
+	 *            to look for (including the terminating ':'!)
+	 * @return a list of {@link AuthenticationChallenge}s found.
+	 */
+	public static List<AuthenticationChallenge> getAuthenticationHeaders(
+			List<String> reply, String authenticationHeader) {
+		List<AuthenticationChallenge> challenges = new ArrayList<>();
+		Iterator<String> lines = reply.iterator();
+		// We know we have at least one line. Skip the response line.
+		lines.next();
+		StringBuilder value = null;
+		while (lines.hasNext()) {
+			String line = lines.next();
+			if (line.isEmpty()) {
+				break;
+			}
+			if (Character.isWhitespace(line.charAt(0))) {
+				// Continuation line.
+				if (value == null) {
+					// Skip if we have no current value
+					continue;
+				}
+				// Skip leading whitespace
+				int i = skipWhiteSpace(line, 1);
+				value.append(' ').append(line, i, line.length());
+				continue;
+			}
+			if (value != null) {
+				parseChallenges(challenges, value.toString());
+				value = null;
+			}
+			int firstColon = line.indexOf(':');
+			if (firstColon > 0 && authenticationHeader
+					.equalsIgnoreCase(line.substring(0, firstColon + 1))) {
+				value = new StringBuilder(line.substring(firstColon + 1));
+			}
+		}
+		if (value != null) {
+			parseChallenges(challenges, value.toString());
+		}
+		return challenges;
+	}
+
+	private static void parseChallenges(
+			List<AuthenticationChallenge> challenges,
+			String header) {
+		// Comma-separated list of challenges, each itself a scheme name
+		// followed optionally by either: a comma-separated list of key=value
+		// pairs, where the value may be a quoted string with backslash escapes,
+		// or a single token value, which itself may end in zero or more '='
+		// characters. Ugh.
+		int length = header.length();
+		for (int i = 0; i < length;) {
+			int start = skipWhiteSpace(header, i);
+			int end = scanToken(header, start);
+			if (end <= start) {
+				break;
+			}
+			AuthenticationChallenge challenge = new AuthenticationChallenge(
+					header.substring(start, end));
+			challenges.add(challenge);
+			i = parseChallenge(challenge, header, end);
+		}
+	}
+
+	private static int parseChallenge(AuthenticationChallenge challenge,
+			String header, int from) {
+		int length = header.length();
+		boolean first = true;
+		for (int start = from; start <= length; first = false) {
+			// Now we have either a single token, which may end in zero or more
+			// equal signs, or a comma-separated list of key=value pairs (with
+			// 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);
+			if (end == start) {
+				// Nothing found. Either at end or on a comma.
+				if (start < header.length() && header.charAt(start) == ',') {
+					return start + 1;
+				}
+				return start;
+			}
+			int next = skipWhiteSpace(header, end);
+			// Comma, or equals sign, or end of string
+			if (next >= length || header.charAt(next) != '=') {
+				if (first) {
+					// It must be a token
+					challenge.setToken(header.substring(start, end));
+					if (next < length && header.charAt(next) == ',') {
+						next++;
+					}
+					return next;
+				} else {
+					// This token must be the name of the next authentication
+					// scheme.
+					return start;
+				}
+			}
+			int nextStart = skipWhiteSpace(header, next + 1);
+			if (nextStart >= length) {
+				if (next == end) {
+					// '=' immediately after the key, no value: key must be the
+					// token, and the equals sign is part of the token
+					challenge.setToken(header.substring(start, end + 1));
+				} else {
+					// Key without value...
+					challenge.addArgument(header.substring(start, end), null);
+				}
+				return nextStart;
+			}
+			if (nextStart == end + 1 && header.charAt(nextStart) == '=') {
+				// More than one equals sign: must be the single token.
+				end = nextStart + 1;
+				while (end < length && header.charAt(end) == '=') {
+					end++;
+				}
+				challenge.setToken(header.substring(start, end));
+				end = skipWhiteSpace(header, end);
+				if (end < length && header.charAt(end) == ',') {
+					end++;
+				}
+				return end;
+			}
+			if (header.charAt(nextStart) == ',') {
+				if (next == end) {
+					// '=' immediately after the key, no value: key must be the
+					// token, and the equals sign is part of the token
+					challenge.setToken(header.substring(start, end + 1));
+					return nextStart + 1;
+				} else {
+					// Key without value...
+					challenge.addArgument(header.substring(start, end), null);
+					start = nextStart + 1;
+				}
+			} else {
+				if (header.charAt(nextStart) == '"') {
+					int nextEnd[] = { nextStart + 1 };
+					String value = scanQuotedString(header, nextStart + 1,
+							nextEnd);
+					challenge.addArgument(header.substring(start, end), value);
+					start = nextEnd[0];
+				} else {
+					int nextEnd = scanToken(header, nextStart);
+					challenge.addArgument(header.substring(start, end),
+							header.substring(nextStart, nextEnd));
+					start = nextEnd;
+				}
+				start = skipWhiteSpace(header, start);
+				if (start < length && header.charAt(start) == ',') {
+					start++;
+				}
+			}
+		}
+		return length;
+	}
+
+	private static int skipWhiteSpace(String header, int i) {
+		int length = header.length();
+		while (i < length && Character.isWhitespace(header.charAt(i))) {
+			i++;
+		}
+		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();
+		boolean quoted = false;
+		int i = from;
+		while (i < length) {
+			char c = header.charAt(i++);
+			if (quoted) {
+				result.append(c);
+				quoted = false;
+			} else if (c == '\\') {
+				quoted = true;
+			} else if (c == '"') {
+				break;
+			} else {
+				result.append(c);
+			}
+		}
+		to[0] = i;
+		return result.toString();
+	}
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/Socks5ClientConnector.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/Socks5ClientConnector.java
new file mode 100644
index 0000000..1844fdc
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/Socks5ClientConnector.java
@@ -0,0 +1,642 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd.proxy;
+
+import static java.text.MessageFormat.format;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.io.IoSession;
+import org.apache.sshd.common.util.Readable;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.internal.transport.sshd.GssApiMechanisms;
+import org.eclipse.jgit.internal.transport.sshd.SshdText;
+import org.eclipse.jgit.internal.transport.sshd.auth.AuthenticationHandler;
+import org.eclipse.jgit.internal.transport.sshd.auth.BasicAuthentication;
+import org.eclipse.jgit.internal.transport.sshd.auth.GssApiAuthentication;
+import org.eclipse.jgit.transport.SshConstants;
+import org.ietf.jgss.GSSContext;
+
+/**
+ * A {@link AbstractClientProxyConnector} to connect through a SOCKS5 proxy.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc1928">RFC 1928</a>
+ */
+public class Socks5ClientConnector extends AbstractClientProxyConnector {
+
+	// private static final byte SOCKS_VERSION_4 = 4;
+	private static final byte SOCKS_VERSION_5 = 5;
+
+	private static final byte SOCKS_CMD_CONNECT = 1;
+	// private static final byte SOCKS5_CMD_BIND = 2;
+	// private static final byte SOCKS5_CMD_UDP_ASSOCIATE = 3;
+
+	// Address types
+
+	private static final byte SOCKS_ADDRESS_IPv4 = 1;
+
+	private static final byte SOCKS_ADDRESS_FQDN = 3;
+
+	private static final byte SOCKS_ADDRESS_IPv6 = 4;
+
+	// Reply codes
+
+	private static final byte SOCKS_REPLY_SUCCESS = 0;
+
+	private static final byte SOCKS_REPLY_FAILURE = 1;
+
+	private static final byte SOCKS_REPLY_FORBIDDEN = 2;
+
+	private static final byte SOCKS_REPLY_NETWORK_UNREACHABLE = 3;
+
+	private static final byte SOCKS_REPLY_HOST_UNREACHABLE = 4;
+
+	private static final byte SOCKS_REPLY_CONNECTION_REFUSED = 5;
+
+	private static final byte SOCKS_REPLY_TTL_EXPIRED = 6;
+
+	private static final byte SOCKS_REPLY_COMMAND_UNSUPPORTED = 7;
+
+	private static final byte SOCKS_REPLY_ADDRESS_UNSUPPORTED = 8;
+
+	/**
+	 * Authentication methods for SOCKS5.
+	 *
+	 * @see <a href=
+	 *      "https://www.iana.org/assignments/socks-methods/socks-methods.xhtml">SOCKS
+	 *      Methods, IANA.org</a>
+	 */
+	private enum SocksAuthenticationMethod {
+
+		ANONYMOUS(0),
+		GSSAPI(1),
+		PASSWORD(2),
+		// CHALLENGE_HANDSHAKE(3),
+		// CHALLENGE_RESPONSE(5),
+		// SSL(6),
+		// NDS(7),
+		// MULTI_AUTH(8),
+		// JSON(9),
+		NONE_ACCEPTABLE(0xFF);
+
+		private byte value;
+
+		SocksAuthenticationMethod(int value) {
+			this.value = (byte) value;
+		}
+
+		public byte getValue() {
+			return value;
+		}
+	}
+
+	private enum ProtocolState {
+		NONE,
+
+		INIT {
+			@Override
+			public void handleMessage(Socks5ClientConnector connector,
+					IoSession session, Buffer data) throws Exception {
+				connector.versionCheck(data.getByte());
+				SocksAuthenticationMethod authMethod = connector.getAuthMethod(
+						data.getByte());
+				switch (authMethod) {
+				case ANONYMOUS:
+					connector.sendConnectInfo(session);
+					break;
+				case PASSWORD:
+					connector.doPasswordAuth(session);
+					break;
+				case GSSAPI:
+					connector.doGssApiAuth(session);
+					break;
+				default:
+					throw new IOException(
+							format(SshdText.get().proxyCannotAuthenticate,
+									connector.proxyAddress));
+				}
+			}
+		},
+
+		AUTHENTICATING {
+			@Override
+			public void handleMessage(Socks5ClientConnector connector,
+					IoSession session, Buffer data) throws Exception {
+				connector.authStep(session, data);
+			}
+		},
+
+		CONNECTING {
+			@Override
+			public void handleMessage(Socks5ClientConnector connector,
+					IoSession session, Buffer data) throws Exception {
+				// Special case: when GSS-API authentication completes, the
+				// client moves into CONNECTING as soon as the GSS context is
+				// established and sends the connect request. This is per RFC
+				// 1961. But for the server, RFC 1961 says it _should_ send an
+				// empty token even if none generated when its server side
+				// context is established. That means we may actually get an
+				// empty token here. That message is 4 bytes long (and has
+				// content 0x01, 0x01, 0x00, 0x00). We simply skip this message
+				// if we get it here. If the server for whatever reason sends
+				// back a "GSS failed" message (it shouldn't, at this point)
+				// it will be two bytes 0x01 0xFF, which will fail the version
+				// check.
+				if (data.available() != 4) {
+					connector.versionCheck(data.getByte());
+					connector.establishConnection(data);
+				}
+			}
+		},
+
+		CONNECTED,
+
+		FAILED;
+
+		public void handleMessage(Socks5ClientConnector connector,
+				@SuppressWarnings("unused") IoSession session, Buffer data)
+				throws Exception {
+			throw new IOException(
+					format(SshdText.get().proxySocksUnexpectedMessage,
+							connector.proxyAddress, this,
+							BufferUtils.toHex(data.array())));
+		}
+	}
+
+	private ProtocolState state;
+
+	private AuthenticationHandler<Buffer, Buffer> authenticator;
+
+	private GSSContext context;
+
+	private byte[] authenticationProposals;
+
+	/**
+	 * Creates a new {@link Socks5ClientConnector}. The connector supports
+	 * anonymous connections as well as username-password or Kerberos5 (GSS-API)
+	 * authentication.
+	 *
+	 * @param proxyAddress
+	 *            of the proxy server we're connecting to
+	 * @param remoteAddress
+	 *            of the target server to connect to
+	 */
+	public Socks5ClientConnector(@NonNull InetSocketAddress proxyAddress,
+			@NonNull InetSocketAddress remoteAddress) {
+		this(proxyAddress, remoteAddress, null, null);
+	}
+
+	/**
+	 * Creates a new {@link Socks5ClientConnector}. The connector supports
+	 * anonymous connections as well as username-password or Kerberos5 (GSS-API)
+	 * authentication.
+	 *
+	 * @param proxyAddress
+	 *            of the proxy server we're connecting to
+	 * @param remoteAddress
+	 *            of the target server to connect to
+	 * @param proxyUser
+	 *            to authenticate at the proxy with
+	 * @param proxyPassword
+	 *            to authenticate at the proxy with
+	 */
+	public Socks5ClientConnector(@NonNull InetSocketAddress proxyAddress,
+			@NonNull InetSocketAddress remoteAddress,
+			String proxyUser, char[] proxyPassword) {
+		super(proxyAddress, remoteAddress, proxyUser, proxyPassword);
+		this.state = ProtocolState.NONE;
+	}
+
+	@Override
+	public void sendClientProxyMetadata(ClientSession sshSession)
+			throws Exception {
+		init(sshSession);
+		IoSession session = sshSession.getIoSession();
+		// Send the initial request
+		Buffer buffer = new ByteArrayBuffer(5, false);
+		buffer.putByte(SOCKS_VERSION_5);
+		context = getGSSContext(remoteAddress);
+		authenticationProposals = getAuthenticationProposals();
+		buffer.putByte((byte) authenticationProposals.length);
+		buffer.putRawBytes(authenticationProposals);
+		state = ProtocolState.INIT;
+		session.writePacket(buffer).verify(getTimeout());
+	}
+
+	private byte[] getAuthenticationProposals() {
+		byte[] proposals = new byte[3];
+		int i = 0;
+		proposals[i++] = SocksAuthenticationMethod.ANONYMOUS.getValue();
+		proposals[i++] = SocksAuthenticationMethod.PASSWORD.getValue();
+		if (context != null) {
+			proposals[i++] = SocksAuthenticationMethod.GSSAPI.getValue();
+		}
+		if (i == proposals.length) {
+			return proposals;
+		} else {
+			byte[] result = new byte[i];
+			System.arraycopy(proposals, 0, result, 0, i);
+			return result;
+		}
+	}
+
+	private void sendConnectInfo(IoSession session) throws Exception {
+		GssApiMechanisms.closeContextSilently(context);
+
+		byte[] rawAddress = getRawAddress(remoteAddress);
+		byte[] remoteName = null;
+		byte type;
+		int length = 0;
+		if (rawAddress == null) {
+			remoteName = remoteAddress.getHostString()
+					.getBytes(StandardCharsets.US_ASCII);
+			if (remoteName == null || remoteName.length == 0) {
+				throw new IOException(
+						format(SshdText.get().proxySocksNoRemoteHostName,
+								remoteAddress));
+			} else if (remoteName.length > 255) {
+				// Should not occur; host names must not be longer than 255
+				// US_ASCII characters. Internal error, no translation.
+				throw new IOException(format(
+						"Proxy host name too long for SOCKS (at most 255 characters): {0}", //$NON-NLS-1$
+						remoteAddress.getHostString()));
+			}
+			type = SOCKS_ADDRESS_FQDN;
+			length = remoteName.length + 1;
+		} else {
+			length = rawAddress.length;
+			type = length == 4 ? SOCKS_ADDRESS_IPv4 : SOCKS_ADDRESS_IPv6;
+		}
+		Buffer buffer = new ByteArrayBuffer(4 + length + 2, false);
+		buffer.putByte(SOCKS_VERSION_5);
+		buffer.putByte(SOCKS_CMD_CONNECT);
+		buffer.putByte((byte) 0); // Reserved
+		buffer.putByte(type);
+		if (remoteName != null) {
+			buffer.putByte((byte) remoteName.length);
+			buffer.putRawBytes(remoteName);
+		} else {
+			buffer.putRawBytes(rawAddress);
+		}
+		int port = remoteAddress.getPort();
+		if (port <= 0) {
+			port = SshConstants.SSH_DEFAULT_PORT;
+		}
+		buffer.putByte((byte) ((port >> 8) & 0xFF));
+		buffer.putByte((byte) (port & 0xFF));
+		state = ProtocolState.CONNECTING;
+		session.writePacket(buffer).verify(getTimeout());
+	}
+
+	private void doPasswordAuth(IoSession session) throws Exception {
+		GssApiMechanisms.closeContextSilently(context);
+		authenticator = new SocksBasicAuthentication();
+		session.addCloseFutureListener(f -> close());
+		startAuth(session);
+	}
+
+	private void doGssApiAuth(IoSession session) throws Exception {
+		authenticator = new SocksGssApiAuthentication();
+		session.addCloseFutureListener(f -> close());
+		startAuth(session);
+	}
+
+	private void close() {
+		AuthenticationHandler<?, ?> handler = authenticator;
+		authenticator = null;
+		if (handler != null) {
+			handler.close();
+		}
+	}
+
+	private void startAuth(IoSession session) throws Exception {
+		Buffer buffer = null;
+		try {
+			authenticator.setParams(null);
+			authenticator.start();
+			buffer = authenticator.getToken();
+			state = ProtocolState.AUTHENTICATING;
+			if (buffer == null) {
+				// Internal error; no translation
+				throw new IOException(
+						"No data for proxy authentication with " //$NON-NLS-1$
+								+ proxyAddress);
+			}
+			session.writePacket(buffer).verify(getTimeout());
+		} finally {
+			if (buffer != null) {
+				buffer.clear(true);
+			}
+		}
+	}
+
+	private void authStep(IoSession session, Buffer input) throws Exception {
+		Buffer buffer = null;
+		try {
+			authenticator.setParams(input);
+			authenticator.process();
+			buffer = authenticator.getToken();
+			if (buffer != null) {
+				session.writePacket(buffer).verify(getTimeout());
+			}
+		} finally {
+			if (buffer != null) {
+				buffer.clear(true);
+			}
+		}
+		if (authenticator.isDone()) {
+			sendConnectInfo(session);
+		}
+	}
+
+	private void establishConnection(Buffer data) throws Exception {
+		byte reply = data.getByte();
+		switch (reply) {
+		case SOCKS_REPLY_SUCCESS:
+			state = ProtocolState.CONNECTED;
+			setDone(true);
+			return;
+		case SOCKS_REPLY_FAILURE:
+			throw new IOException(format(
+					SshdText.get().proxySocksFailureGeneral, proxyAddress));
+		case SOCKS_REPLY_FORBIDDEN:
+			throw new IOException(
+					format(SshdText.get().proxySocksFailureForbidden,
+							proxyAddress, remoteAddress));
+		case SOCKS_REPLY_NETWORK_UNREACHABLE:
+			throw new IOException(
+					format(SshdText.get().proxySocksFailureNetworkUnreachable,
+							proxyAddress, remoteAddress));
+		case SOCKS_REPLY_HOST_UNREACHABLE:
+			throw new IOException(
+					format(SshdText.get().proxySocksFailureHostUnreachable,
+							proxyAddress, remoteAddress));
+		case SOCKS_REPLY_CONNECTION_REFUSED:
+			throw new IOException(
+					format(SshdText.get().proxySocksFailureRefused,
+							proxyAddress, remoteAddress));
+		case SOCKS_REPLY_TTL_EXPIRED:
+			throw new IOException(
+					format(SshdText.get().proxySocksFailureTTL, proxyAddress));
+		case SOCKS_REPLY_COMMAND_UNSUPPORTED:
+			throw new IOException(
+					format(SshdText.get().proxySocksFailureUnsupportedCommand,
+							proxyAddress));
+		case SOCKS_REPLY_ADDRESS_UNSUPPORTED:
+			throw new IOException(
+					format(SshdText.get().proxySocksFailureUnsupportedAddress,
+							proxyAddress));
+		default:
+			throw new IOException(format(
+					SshdText.get().proxySocksFailureUnspecified, proxyAddress));
+		}
+	}
+
+	@Override
+	public void messageReceived(IoSession session, Readable buffer)
+			throws Exception {
+		try {
+			// Dispatch according to protocol state
+			ByteArrayBuffer data = new ByteArrayBuffer(buffer.available(),
+					false);
+			data.putBuffer(buffer);
+			data.compact();
+			state.handleMessage(this, session, data);
+		} catch (Exception e) {
+			state = ProtocolState.FAILED;
+			if (authenticator != null) {
+				authenticator.close();
+				authenticator = null;
+			}
+			try {
+				setDone(false);
+			} catch (Exception inner) {
+				e.addSuppressed(inner);
+			}
+			throw e;
+		}
+	}
+
+	private void versionCheck(byte version) throws Exception {
+		if (version != SOCKS_VERSION_5) {
+			throw new IOException(
+					format(SshdText.get().proxySocksUnexpectedVersion,
+							Integer.toString(version & 0xFF)));
+		}
+	}
+
+	private SocksAuthenticationMethod getAuthMethod(byte value) {
+		if (value != SocksAuthenticationMethod.NONE_ACCEPTABLE.getValue()) {
+			for (byte proposed : authenticationProposals) {
+				if (proposed == value) {
+					for (SocksAuthenticationMethod method : SocksAuthenticationMethod
+							.values()) {
+						if (method.getValue() == value) {
+							return method;
+						}
+					}
+					break;
+				}
+			}
+		}
+		return SocksAuthenticationMethod.NONE_ACCEPTABLE;
+	}
+
+	private static byte[] getRawAddress(@NonNull InetSocketAddress address) {
+		InetAddress ipAddress = GssApiMechanisms.resolve(address);
+		return ipAddress == null ? null : ipAddress.getAddress();
+	}
+
+	private static GSSContext getGSSContext(
+			@NonNull InetSocketAddress address) {
+		if (!GssApiMechanisms.getSupportedMechanisms()
+				.contains(GssApiMechanisms.KERBEROS_5)) {
+			return null;
+		}
+		return GssApiMechanisms.createContext(GssApiMechanisms.KERBEROS_5,
+				GssApiMechanisms.getCanonicalName(address));
+	}
+
+	/**
+	 * @see <a href="https://tools.ietf.org/html/rfc1929">RFC 1929</a>
+	 */
+	private class SocksBasicAuthentication
+			extends BasicAuthentication<Buffer, Buffer> {
+
+		private static final byte SOCKS_BASIC_PROTOCOL_VERSION = 1;
+
+		private static final byte SOCKS_BASIC_AUTH_SUCCESS = 0;
+
+		public SocksBasicAuthentication() {
+			super(proxyAddress, proxyUser, proxyPassword);
+		}
+
+		@Override
+		public void process() throws Exception {
+			// Retries impossible. RFC 1929 specifies that the server MUST
+			// close the connection if authentication is unsuccessful.
+			done = true;
+			if (params.getByte() != SOCKS_BASIC_PROTOCOL_VERSION
+					|| params.getByte() != SOCKS_BASIC_AUTH_SUCCESS) {
+				throw new IOException(format(
+						SshdText.get().proxySocksAuthenticationFailed, proxy));
+			}
+		}
+
+		@Override
+		protected void askCredentials() {
+			super.askCredentials();
+			adjustTimeout();
+		}
+
+		@Override
+		public Buffer getToken() throws IOException {
+			if (done) {
+				return null;
+			}
+			try {
+				byte[] rawUser = user.getBytes(StandardCharsets.UTF_8);
+				if (rawUser.length > 255) {
+					throw new IOException(format(
+							SshdText.get().proxySocksUsernameTooLong, proxy,
+							Integer.toString(rawUser.length), user));
+				}
+
+				if (password.length > 255) {
+					throw new IOException(
+							format(SshdText.get().proxySocksPasswordTooLong,
+									proxy, Integer.toString(password.length)));
+				}
+				ByteArrayBuffer buffer = new ByteArrayBuffer(
+						3 + rawUser.length + password.length, false);
+				buffer.putByte(SOCKS_BASIC_PROTOCOL_VERSION);
+				buffer.putByte((byte) rawUser.length);
+				buffer.putRawBytes(rawUser);
+				buffer.putByte((byte) password.length);
+				buffer.putRawBytes(password);
+				return buffer;
+			} finally {
+				clearPassword();
+				done = true;
+			}
+		}
+	}
+
+	/**
+	 * @see <a href="https://tools.ietf.org/html/rfc1961">RFC 1961</a>
+	 */
+	private class SocksGssApiAuthentication
+			extends GssApiAuthentication<Buffer, Buffer> {
+
+		private static final byte SOCKS5_GSSAPI_VERSION = 1;
+
+		private static final byte SOCKS5_GSSAPI_TOKEN = 1;
+
+		private static final int SOCKS5_GSSAPI_FAILURE = 0xFF;
+
+		public SocksGssApiAuthentication() {
+			super(proxyAddress);
+		}
+
+		@Override
+		protected GSSContext createContext() throws Exception {
+			return context;
+		}
+
+		@Override
+		public Buffer getToken() throws Exception {
+			if (token == null) {
+				return null;
+			}
+			Buffer buffer = new ByteArrayBuffer(4 + token.length, false);
+			buffer.putByte(SOCKS5_GSSAPI_VERSION);
+			buffer.putByte(SOCKS5_GSSAPI_TOKEN);
+			buffer.putByte((byte) ((token.length >> 8) & 0xFF));
+			buffer.putByte((byte) (token.length & 0xFF));
+			buffer.putRawBytes(token);
+			return buffer;
+		}
+
+		@Override
+		protected byte[] extractToken(Buffer input) throws Exception {
+			if (context == null) {
+				return null;
+			}
+			int version = input.getUByte();
+			if (version != SOCKS5_GSSAPI_VERSION) {
+				throw new IOException(
+						format(SshdText.get().proxySocksGssApiVersionMismatch,
+								remoteAddress, Integer.toString(version)));
+			}
+			int msgType = input.getUByte();
+			if (msgType == SOCKS5_GSSAPI_FAILURE) {
+				throw new IOException(format(
+						SshdText.get().proxySocksGssApiFailure, remoteAddress));
+			} else if (msgType != SOCKS5_GSSAPI_TOKEN) {
+				throw new IOException(format(
+						SshdText.get().proxySocksGssApiUnknownMessage,
+						remoteAddress, Integer.toHexString(msgType & 0xFF)));
+			}
+			if (input.available() >= 2) {
+				int length = (input.getUByte() << 8) + input.getUByte();
+				if (input.available() >= length) {
+					byte[] value = new byte[length];
+					if (length > 0) {
+						input.getRawBytes(value);
+					}
+					return value;
+				}
+			}
+			throw new IOException(
+					format(SshdText.get().proxySocksGssApiMessageTooShort,
+							remoteAddress));
+		}
+	}
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/StatefulProxyConnector.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/StatefulProxyConnector.java
new file mode 100644
index 0000000..6bd836a
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/StatefulProxyConnector.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd.proxy;
+
+import java.util.concurrent.Callable;
+
+import org.apache.sshd.client.session.ClientProxyConnector;
+import org.apache.sshd.common.io.IoSession;
+import org.apache.sshd.common.util.Readable;
+
+/**
+ * Some proxy connections are stateful and require the exchange of multiple
+ * request-reply messages. The default {@link ClientProxyConnector} has only
+ * support for sending a message; replies get routed through the Ssh session,
+ * and don't get back to this proxy connector. Augment the interface so that the
+ * session can know when to route messages received to the proxy connector, and
+ * when to start handling them itself.
+ */
+public interface StatefulProxyConnector extends ClientProxyConnector {
+
+	/**
+	 * A property key for a session property defining the timeout for setting up
+	 * the proxy connection.
+	 */
+	static final String TIMEOUT_PROPERTY = StatefulProxyConnector.class
+			.getName() + "-timeout"; //$NON-NLS-1$
+
+	/**
+	 * Handle a received message.
+	 *
+	 * @param session
+	 *            to use for writing data
+	 * @param buffer
+	 *            received data
+	 * @throws Exception
+	 *             if data cannot be read, or the connection attempt fails
+	 */
+	void messageReceived(IoSession session, Readable buffer) throws Exception;
+
+	/**
+	 * Runs {@code command} once the proxy connection is established. May be
+	 * called multiple times; commands are run sequentially. If the proxy
+	 * connection is already established, {@code command} is executed directly
+	 * synchronously.
+	 *
+	 * @param command
+	 *            operation to run
+	 * @throws Exception
+	 *             if the operation is run synchronously and throws an exception
+	 */
+	void runWhenDone(Callable<Void> command) throws Exception;
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/StatusLine.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/StatusLine.java
new file mode 100644
index 0000000..7ff0183
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/StatusLine.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.sshd.proxy;
+
+/**
+ * A very simple representation of a HTTP status line.
+ */
+public class StatusLine {
+
+	private final String version;
+
+	private final int resultCode;
+
+	private final String reason;
+
+	/**
+	 * Create a new {@link StatusLine} with the given response code and reason
+	 * string.
+	 *
+	 * @param version
+	 *            the version string (normally "HTTP/1.1" or "HTTP/1.0")
+	 * @param resultCode
+	 *            the HTTP response code (200, 401, etc.)
+	 * @param reason
+	 *            the reason phrase for the code
+	 */
+	public StatusLine(String version, int resultCode, String reason) {
+		this.version = version;
+		this.resultCode = resultCode;
+		this.reason = reason;
+	}
+
+	/**
+	 * Retrieves the version string.
+	 *
+	 * @return the version string
+	 */
+	public String getVersion() {
+		return version;
+	}
+
+	/**
+	 * Retrieves the HTTP response code.
+	 *
+	 * @return the code
+	 */
+	public int getResultCode() {
+		return resultCode;
+	}
+
+	/**
+	 * Retrieves the HTTP reason phrase.
+	 *
+	 * @return the reason
+	 */
+	public String getReason() {
+		return reason;
+	}
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/DefaultProxyDataFactory.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/DefaultProxyDataFactory.java
new file mode 100644
index 0000000..97e0da0
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/DefaultProxyDataFactory.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.transport.sshd;
+
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.SocketAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+
+/**
+ * A default implementation of a {@link ProxyDataFactory} based on the standard
+ * {@link java.net.ProxySelector}.
+ *
+ * @since 5.2
+ */
+public class DefaultProxyDataFactory implements ProxyDataFactory {
+
+	@Override
+	public ProxyData get(InetSocketAddress remoteAddress) {
+		try {
+			List<Proxy> proxies = ProxySelector.getDefault()
+					.select(new URI(Proxy.Type.SOCKS.name(),
+							"//" + remoteAddress.getHostString(), null)); //$NON-NLS-1$
+			ProxyData data = getData(proxies, Proxy.Type.SOCKS);
+			if (data == null) {
+				proxies = ProxySelector.getDefault()
+						.select(new URI(Proxy.Type.HTTP.name(),
+								"//" + remoteAddress.getHostString(), //$NON-NLS-1$
+								null));
+				data = getData(proxies, Proxy.Type.HTTP);
+			}
+			return data;
+		} catch (URISyntaxException e) {
+			return null;
+		}
+	}
+
+	private ProxyData getData(List<Proxy> proxies, Proxy.Type type) {
+		Proxy proxy = proxies.stream().filter(p -> type == p.type()).findFirst()
+				.orElse(null);
+		if (proxy == null) {
+			return null;
+		}
+		SocketAddress address = proxy.address();
+		if (!(address instanceof InetSocketAddress)) {
+			return null;
+		}
+		switch (type) {
+		case HTTP:
+			return new ProxyData(proxy);
+		case SOCKS:
+			return new ProxyData(proxy);
+		default:
+			return null;
+		}
+	}
+}
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
new file mode 100644
index 0000000..2a5f2ff
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/IdentityPasswordProvider.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.transport.sshd;
+
+import static java.text.MessageFormat.format;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.util.ArrayList;
+import java.util.Arrays;
+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.SshdText;
+import org.eclipse.jgit.transport.CredentialItem;
+import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.URIish;
+
+/**
+ * A {@link KeyPasswordProvider} based on a {@link CredentialsProvider}.
+ *
+ * @since 5.2
+ */
+public class IdentityPasswordProvider implements KeyPasswordProvider {
+
+	private CredentialsProvider provider;
+
+	/**
+	 * The number of times to ask successively for a password for a given
+	 * identity resource.
+	 */
+	private int attempts = 1;
+
+	/**
+	 * A simple state object for repeated attempts to get a password for a
+	 * resource.
+	 */
+	protected static class State {
+
+		private int count = 0;
+
+		private char[] password;
+
+		/**
+		 * Obtains the current count. The initial count is zero.
+		 *
+		 * @return the count
+		 */
+		public int getCount() {
+			return count;
+		}
+
+		/**
+		 * Increments the current count. Should be called for each new attempt
+		 * to get a password.
+		 *
+		 * @return the incremented count.
+		 */
+		public int incCount() {
+			return ++count;
+		}
+
+		/**
+		 * Remembers the password.
+		 *
+		 * @param password
+		 *            the password
+		 */
+		public void setPassword(char[] password) {
+			if (this.password != null) {
+				Arrays.fill(this.password, '\000');
+			}
+			if (password != null) {
+				this.password = password.clone();
+			} else {
+				this.password = null;
+			}
+		}
+
+		/**
+		 * Retrieves the password from the current attempt.
+		 *
+		 * @return the password, or {@code null} if none was obtained
+		 */
+		public char[] getPassword() {
+			return password;
+		}
+	}
+
+	/**
+	 * Counts per resource key.
+	 */
+	private final Map<URIish, State> current = new HashMap<>();
+
+	/**
+	 * Creates a new {@link IdentityPasswordProvider} to get the passphrase for
+	 * an encrypted identity.
+	 *
+	 * @param provider
+	 *            to use
+	 */
+	public IdentityPasswordProvider(CredentialsProvider provider) {
+		this.provider = provider;
+	}
+
+	@Override
+	public void setAttempts(int numberOfPasswordPrompts) {
+		if (numberOfPasswordPrompts <= 0) {
+			throw new IllegalArgumentException(
+					"Number of password prompts must be >= 1"); //$NON-NLS-1$
+		}
+		attempts = numberOfPasswordPrompts;
+	}
+
+	@Override
+	public int getAttempts() {
+		return Math.max(1, attempts);
+	}
+
+	@Override
+	public char[] getPassphrase(URIish uri, int attempt) throws IOException {
+		return getPassword(uri, attempt,
+				current.computeIfAbsent(uri, r -> new State()));
+	}
+
+	/**
+	 * Retrieves a password to decrypt a private key.
+	 *
+	 * @param uri
+	 *            identifying the resource to obtain a password for
+	 * @param attempt
+	 *            number of previous attempts to get a passphrase
+	 * @param state
+	 *            encapsulating state information about attempts to get the
+	 *            password
+	 * @return the password, or {@code null} or the empty string if none
+	 *         available.
+	 * @throws IOException
+	 *             if an error occurs
+	 */
+	protected char[] getPassword(URIish uri, int attempt, @NonNull State state)
+			throws IOException {
+		state.setPassword(null);
+		state.incCount();
+		String message = state.count == 1 ? SshdText.get().keyEncryptedMsg
+				: SshdText.get().keyEncryptedRetry;
+		char[] pass = getPassword(uri, message);
+		state.setPassword(pass);
+		return pass;
+	}
+
+	private 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)));
+		CredentialItem.Password password = new CredentialItem.Password(
+				SshdText.get().keyEncryptedPrompt);
+		items.add(password);
+		try {
+			provider.get(uri, items);
+			char[] pass = password.getValue();
+			if (pass == null) {
+				throw new CancellationException(
+						SshdText.get().authenticationCanceled);
+			}
+			return pass.clone();
+		} finally {
+			password.clear();
+		}
+	}
+
+	/**
+	 * Invoked to inform the password provider about the decoding result.
+	 *
+	 * @param uri
+	 *            identifying the key resource the key was attempted to be
+	 *            loaded from
+	 * @param state
+	 *            associated with this key
+	 * @param password
+	 *            the password that was attempted
+	 * @param err
+	 *            the attempt result - {@code null} for success
+	 * @return how to proceed in case of error
+	 * @throws IOException
+	 * @throws GeneralSecurityException
+	 */
+	protected boolean keyLoaded(URIish uri,
+			State state, char[] password, Exception err)
+			throws IOException, GeneralSecurityException {
+		if (err == null) {
+			return false; // Success, don't retry
+		} else if (err instanceof GeneralSecurityException) {
+			throw new InvalidKeyException(
+					format(SshdText.get().identityFileCannotDecrypt, uri), err);
+		} else {
+			// Unencrypted key (state == null && password == null), or exception
+			// before having asked for the password (state != null && password
+			// == null; might also be a user cancellation), or number of
+			// attempts exhausted.
+			if (state == null || password == null
+					|| state.getCount() >= attempts) {
+				return false;
+			}
+			return true;
+		}
+	}
+
+	@Override
+	public boolean keyLoaded(URIish uri, int attempt, Exception error)
+			throws IOException, GeneralSecurityException {
+		State state = null;
+		boolean retry = false;
+		try {
+			state = current.get(uri);
+			retry = keyLoaded(uri, state,
+					state == null ? null : state.getPassword(), error);
+		} finally {
+			if (state != null) {
+				state.setPassword(null);
+			}
+			if (!retry) {
+				current.remove(uri);
+			}
+		}
+		return retry;
+	}
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/JGitKeyCache.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/JGitKeyCache.java
new file mode 100644
index 0000000..52325c6
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/JGitKeyCache.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.transport.sshd;
+
+import java.nio.file.Path;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
+
+import javax.security.auth.DestroyFailedException;
+
+/**
+ * A simple {@link KeyCache}. JGit uses one such cache in its
+ * {@link SshdSessionFactory} to avoid loading keys multiple times.
+ *
+ * @since 5.2
+ */
+public class JGitKeyCache implements KeyCache {
+
+	private AtomicReference<Map<Path, KeyPair>> cache = new AtomicReference<>(
+			new ConcurrentHashMap<>());
+
+	@Override
+	public KeyPair get(Path path,
+			Function<? super Path, ? extends KeyPair> loader) {
+		return cache.get().computeIfAbsent(path, loader);
+	}
+
+	@Override
+	public void close() {
+		Map<Path, KeyPair> map = cache.getAndSet(null);
+		if (map == null) {
+			return;
+		}
+		for (KeyPair k : map.values()) {
+			PrivateKey p = k.getPrivate();
+			try {
+				p.destroy();
+			} catch (DestroyFailedException e) {
+				// Ignore here. We did our best.
+			}
+		}
+		map.clear();
+	}
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/KeyCache.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/KeyCache.java
new file mode 100644
index 0000000..d8c1d30
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/KeyCache.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.transport.sshd;
+
+import java.nio.file.Path;
+import java.security.KeyPair;
+import java.util.function.Function;
+
+/**
+ * A cache for {@link KeyPair}s.
+ *
+ * @since 5.2
+ */
+public interface KeyCache {
+
+	/**
+	 * Obtains a {@link KeyPair} from the cache. Implementations must be
+	 * thread-safe.
+	 *
+	 * @param path
+	 *            of the key
+	 * @param loader
+	 *            to load the key if it isn't present in the cache yet
+	 * @return the {@link KeyPair}, or {@code null} if not present and could not
+	 *         be loaded
+	 */
+	KeyPair get(Path path, Function<? super Path, ? extends KeyPair> loader);
+
+	/**
+	 * Removes all {@link KeyPair} from this cache and destroys their private
+	 * keys. This cache instance must not be used anymore thereafter.
+	 */
+	void close();
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/KeyPasswordProvider.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/KeyPasswordProvider.java
new file mode 100644
index 0000000..0f315a4
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/KeyPasswordProvider.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.transport.sshd;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+
+import org.eclipse.jgit.transport.URIish;
+
+/**
+ * A {@code KeyPasswordProvider} provides passwords for encrypted private keys.
+ *
+ * @since 5.2
+ */
+public interface KeyPasswordProvider {
+
+	/**
+	 * Obtains a passphrase to use to decrypt an ecrypted private key. Returning
+	 * {@code null} or an empty array will skip this key. To cancel completely,
+	 * the operation should raise
+	 * {@link java.util.concurrent.CancellationException}.
+	 *
+	 * @param uri
+	 *            identifying the key resource that is being attempted to be
+	 *            loaded
+	 * @param attempt
+	 *            the number of previous attempts to get a passphrase; >= 0
+	 * @return the passphrase
+	 * @throws IOException
+	 *             if no password can be obtained
+	 */
+	char[] getPassphrase(URIish uri, int attempt) throws IOException;
+
+	/**
+	 * Define the maximum number of attempts to get a passphrase that should be
+	 * attempted for one identity resource through this provider.
+	 *
+	 * @param maxNumberOfAttempts
+	 *            number of times to ask for a passphrase;
+	 *            {@link IllegalArgumentException} may be thrown if <= 0
+	 */
+	void setAttempts(int maxNumberOfAttempts);
+
+	/**
+	 * Gets the maximum number of attempts to get a passphrase that should be
+	 * attempted for one identity resource through this provider. The default
+	 * return 1.
+	 *
+	 * @return the number of times to ask for a passphrase; should be >= 1.
+	 */
+	default int getAttempts() {
+		return 1;
+	}
+
+	/**
+	 * Invoked after a key has been loaded. If this raises an exception, the
+	 * original {@code error} is lost unless it is attached to that exception.
+	 *
+	 * @param uri
+	 *            identifying the key resource the key was attempted to be
+	 *            loaded from
+	 * @param attempt
+	 *            the number of times {@link #getPassphrase(URIish, int)} had
+	 *            been called; zero indicates that {@code uri} refers to a
+	 *            non-encrypted key
+	 * @param error
+	 *            {@code null} if the key was loaded successfully; otherwise an
+	 *            exception indicating why the key could not be loaded
+	 * @return {@code true} to re-try again; {@code false} to re-raise the
+	 *         {@code error} exception; Ignored if the key was loaded
+	 *         successfully, i.e., if {@code error == null}.
+	 * @throws IOException
+	 * @throws GeneralSecurityException
+	 */
+	boolean keyLoaded(URIish uri, int attempt, Exception error)
+			throws IOException, GeneralSecurityException;
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/ProxyData.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/ProxyData.java
new file mode 100644
index 0000000..39b1e02
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/ProxyData.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.transport.sshd;
+
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.util.Arrays;
+
+import org.eclipse.jgit.annotations.NonNull;
+
+/**
+ * A DTO encapsulating the data needed to connect through a proxy server.
+ *
+ * @since 5.2
+ */
+public class ProxyData {
+
+	private final @NonNull Proxy proxy;
+
+	private final String proxyUser;
+
+	private final char[] proxyPassword;
+
+	/**
+	 * Creates a new {@link ProxyData} instance without user name or password.
+	 *
+	 * @param proxy
+	 *            to connect to; must not be {@link java.net.Proxy.Type#DIRECT}
+	 *            and must have an {@link InetSocketAddress}.
+	 */
+	public ProxyData(@NonNull Proxy proxy) {
+		this(proxy, null, null);
+	}
+
+	/**
+	 * Creates a new {@link ProxyData} instance.
+	 *
+	 * @param proxy
+	 *            to connect to; must not be {@link java.net.Proxy.Type#DIRECT}
+	 *            and must have an {@link InetSocketAddress}.
+	 * @param proxyUser
+	 *            to use for log-in to the proxy, may be {@code null}
+	 * @param proxyPassword
+	 *            to use for log-in to the proxy, may be {@code null}
+	 */
+	public ProxyData(@NonNull Proxy proxy, String proxyUser,
+			char[] proxyPassword) {
+		this.proxy = proxy;
+		if (!(proxy.address() instanceof InetSocketAddress)) {
+			// Internal error not translated
+			throw new IllegalArgumentException(
+					"Proxy does not have an InetSocketAddress"); //$NON-NLS-1$
+		}
+		this.proxyUser = proxyUser;
+		this.proxyPassword = proxyPassword == null ? null
+				: proxyPassword.clone();
+	}
+
+	/**
+	 * Obtains the remote {@link InetSocketAddress} of the proxy to connect to.
+	 *
+	 * @return the remote address of the proxy
+	 */
+	@NonNull
+	public Proxy getProxy() {
+		return proxy;
+	}
+
+	/**
+	 * Obtains the user to log in at the proxy with.
+	 *
+	 * @return the user name, or {@code null} if none
+	 */
+	public String getUser() {
+		return proxyUser;
+	}
+
+	/**
+	 * Obtains a copy of the internally stored password.
+	 *
+	 * @return the password or {@code null} if none
+	 */
+	public char[] getPassword() {
+		return proxyPassword == null ? null : proxyPassword.clone();
+	}
+
+	/**
+	 * Clears the stored password, if any.
+	 */
+	public void clearPassword() {
+		if (proxyPassword != null) {
+			Arrays.fill(proxyPassword, '\000');
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/ProxyDataFactory.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/ProxyDataFactory.java
new file mode 100644
index 0000000..334fff4
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/ProxyDataFactory.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.transport.sshd;
+
+import java.net.InetSocketAddress;
+
+/**
+ * Interface for obtaining {@link ProxyData} to connect through some proxy.
+ *
+ * @since 5.2
+ */
+public interface ProxyDataFactory {
+
+	/**
+	 * Get the {@link ProxyData} to connect to a proxy. It should return a
+	 * <em>new</em> {@link ProxyData} instance every time; if the returned
+	 * {@link ProxyData} contains a password, the {@link SshdSession} will clear
+	 * it once it is no longer needed.
+	 *
+	 * @param remoteAddress
+	 *            to connect to
+	 * @return the {@link ProxyData} or {@code null} if a direct connection is
+	 *         to be made
+	 */
+	ProxyData get(InetSocketAddress remoteAddress);
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SessionCloseListener.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SessionCloseListener.java
new file mode 100644
index 0000000..31fc61f
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SessionCloseListener.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.transport.sshd;
+
+/**
+ * A {@code SessionCloseListener} is invoked when a {@link SshdSession} is
+ * closed.
+ *
+ * @since 5.2
+ */
+@FunctionalInterface
+public interface SessionCloseListener {
+
+	/**
+	 * Invoked when a {@link SshdSession} has been closed.
+	 *
+	 * @param session
+	 *            that was closed.
+	 */
+	void sessionClosed(SshdSession session);
+}
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
new file mode 100644
index 0000000..7d0e686
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSession.java
@@ -0,0 +1,485 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.transport.sshd;
+
+import static java.text.MessageFormat.format;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumSet;
+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 org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.channel.ChannelExec;
+import org.apache.sshd.client.channel.ClientChannelEvent;
+import org.apache.sshd.client.session.ClientSession;
+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.subsystem.sftp.SftpException;
+import org.eclipse.jgit.annotations.NonNull;
+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.URIish;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An implementation of {@link RemoteSession} based on Apache MINA sshd.
+ *
+ * @since 5.2
+ */
+public class SshdSession implements RemoteSession {
+
+	private static final Logger LOG = LoggerFactory
+			.getLogger(SshdSession.class);
+
+	private final CopyOnWriteArrayList<SessionCloseListener> listeners = new CopyOnWriteArrayList<>();
+
+	private final URIish uri;
+
+	private SshClient client;
+
+	private ClientSession session;
+
+	SshdSession(URIish uri, Supplier<SshClient> clientFactory) {
+		this.uri = uri;
+		this.client = clientFactory.get();
+	}
+
+	void connect(Duration timeout) throws IOException {
+		if (!client.isStarted()) {
+			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());
+		} catch (IOException e) {
+			disconnect(e);
+			throw e;
+		}
+	}
+
+	/**
+	 * Adds a {@link SessionCloseListener} to this session. Has no effect if the
+	 * given {@code listener} is already registered with this session.
+	 *
+	 * @param listener
+	 *            to add
+	 */
+	public void addCloseListener(@NonNull SessionCloseListener listener) {
+		listeners.addIfAbsent(listener);
+	}
+
+	/**
+	 * Removes the given {@code listener}; has no effect if the listener is not
+	 * currently registered with this session.
+	 *
+	 * @param listener
+	 *            to remove
+	 */
+	public void removeCloseListener(@NonNull SessionCloseListener listener) {
+		listeners.remove(listener);
+	}
+
+	private void notifyCloseListeners() {
+		for (SessionCloseListener l : listeners) {
+			try {
+				l.sessionClosed(this);
+			} catch (RuntimeException e) {
+				LOG.warn(SshdText.get().closeListenerFailed, e);
+			}
+		}
+	}
+
+	@Override
+	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) {
+				exec.open().verify();
+			} else {
+				long start = System.nanoTime();
+				exec.open().verify(timeoutMillis);
+				timeoutMillis -= TimeUnit.NANOSECONDS
+						.toMillis(System.nanoTime() - start);
+			}
+		} catch (IOException e) {
+			exec.close(true);
+			throw e;
+		} catch (RuntimeException e) {
+			exec.close(true);
+			throw 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);
+	}
+
+	/**
+	 * Obtain an {@link FtpChannel} to perform SFTP operations in this
+	 * {@link SshdSession}.
+	 */
+	@Override
+	@NonNull
+	public FtpChannel getFtpChannel() {
+		return new SshdFtpChannel();
+	}
+
+	@Override
+	public void disconnect() {
+		disconnect(null);
+	}
+
+	private void disconnect(Throwable reason) {
+		try {
+			if (session != null) {
+				session.close();
+				session = null;
+			}
+		} catch (IOException e) {
+			if (reason != null) {
+				reason.addSuppressed(e);
+			} else {
+				LOG.error(SshdText.get().sessionCloseFailed, e);
+			}
+		} finally {
+			client.stop();
+			client = null;
+		}
+	}
+
+	private static class SshdExecProcess extends Process {
+
+		private final ChannelExec channel;
+
+		private final long timeoutMillis;
+
+		private final String commandName;
+
+		public SshdExecProcess(ChannelExec channel, String commandName,
+				long timeoutMillis) {
+			this.channel = channel;
+			this.timeoutMillis = timeoutMillis > 0 ? timeoutMillis : -1L;
+			this.commandName = commandName;
+		}
+
+		@Override
+		public OutputStream getOutputStream() {
+			return channel.getInvertedIn();
+		}
+
+		@Override
+		public InputStream getInputStream() {
+			return channel.getInvertedOut();
+		}
+
+		@Override
+		public InputStream getErrorStream() {
+			return channel.getInvertedErr();
+		}
+
+		@Override
+		public int waitFor() throws InterruptedException {
+			if (waitFor(timeoutMillis, TimeUnit.MILLISECONDS)) {
+				return exitValue();
+			}
+			return -1;
+		}
+
+		@Override
+		public boolean waitFor(long timeout, TimeUnit unit)
+				throws InterruptedException {
+			long millis = timeout >= 0 ? unit.toMillis(timeout) : -1L;
+			return channel
+					.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), millis)
+					.contains(ClientChannelEvent.CLOSED);
+		}
+
+		@Override
+		public int exitValue() {
+			Integer exitCode = channel.getExitStatus();
+			if (exitCode == null) {
+				throw new IllegalThreadStateException(
+						format(SshdText.get().sshProcessStillRunning,
+								commandName));
+			}
+			return exitCode.intValue();
+		}
+
+		@Override
+		public void destroy() {
+			if (channel.isOpen()) {
+				channel.close(true);
+			}
+		}
+	}
+
+	/**
+	 * Helper interface like {@link Supplier}, but possibly raising an
+	 * {@link IOException}.
+	 *
+	 * @param <T>
+	 *            return type
+	 */
+	@FunctionalInterface
+	private interface FtpOperation<T> {
+
+		T call() throws IOException;
+
+	}
+
+	private class SshdFtpChannel implements FtpChannel {
+
+		private SftpClient ftp;
+
+		/** Current working directory. */
+		private String cwd = ""; //$NON-NLS-1$
+
+		@Override
+		public void connect(int timeout, TimeUnit unit) throws IOException {
+			if (timeout <= 0) {
+				session.getProperties().put(
+						SftpClient.SFTP_CHANNEL_OPEN_TIMEOUT,
+						Long.valueOf(Long.MAX_VALUE));
+			} else {
+				session.getProperties().put(
+						SftpClient.SFTP_CHANNEL_OPEN_TIMEOUT,
+						Long.valueOf(unit.toMillis(timeout)));
+			}
+			ftp = SftpClientFactory.instance().createSftpClient(session);
+			try {
+				cd(cwd);
+			} catch (IOException e) {
+				ftp.close();
+			}
+		}
+
+		@Override
+		public void disconnect() {
+			try {
+				ftp.close();
+			} catch (IOException e) {
+				LOG.error(SshdText.get().ftpCloseFailed, e);
+			}
+		}
+
+		@Override
+		public boolean isConnected() {
+			return session.isAuthenticated() && ftp.isOpen();
+		}
+
+		private String absolute(String path) {
+			if (path.isEmpty()) {
+				return cwd;
+			}
+			// Note: there is no path injection vulnerability here. If
+			// path has too many ".." components, we rely on the server
+			// catching it and returning an error.
+			if (path.charAt(0) != '/') {
+				if (cwd.charAt(cwd.length() - 1) == '/') {
+					return cwd + path;
+				} else {
+					return cwd + '/' + path;
+				}
+			}
+			return path;
+		}
+
+		private <T> T map(FtpOperation<T> op) throws IOException {
+			try {
+				return op.call();
+			} catch (IOException e) {
+				if (e instanceof SftpException) {
+					throw new FtpChannel.FtpException(e.getLocalizedMessage(),
+							((SftpException) e).getStatus(), e);
+				}
+				throw e;
+			}
+		}
+
+		@Override
+		public void cd(String path) throws IOException {
+			cwd = map(() -> ftp.canonicalPath(absolute(path)));
+			if (cwd.isEmpty()) {
+				cwd += '/';
+			}
+		}
+
+		@Override
+		public String pwd() throws IOException {
+			return cwd;
+		}
+
+		@Override
+		public Collection<DirEntry> ls(String path) throws IOException {
+			return map(() -> {
+				List<DirEntry> result = new ArrayList<>();
+				try (CloseableHandle handle = ftp.openDir(absolute(path))) {
+					AtomicReference<Boolean> atEnd = new AtomicReference<>(
+							Boolean.FALSE);
+					while (!atEnd.get().booleanValue()) {
+						List<SftpClient.DirEntry> chunk = ftp.readDir(handle,
+								atEnd);
+						if (chunk == null) {
+							break;
+						}
+						for (SftpClient.DirEntry remote : chunk) {
+							result.add(new DirEntry() {
+
+								@Override
+								public String getFilename() {
+									return remote.getFilename();
+								}
+
+								@Override
+								public long getModifiedTime() {
+									return remote.getAttributes()
+											.getModifyTime().toMillis();
+								}
+
+								@Override
+								public boolean isDirectory() {
+									return remote.getAttributes().isDirectory();
+								}
+
+							});
+						}
+					}
+				}
+				return result;
+			});
+		}
+
+		@Override
+		public void rmdir(String path) throws IOException {
+			map(() -> {
+				ftp.rmdir(absolute(path));
+				return null;
+			});
+
+		}
+
+		@Override
+		public void mkdir(String path) throws IOException {
+			map(() -> {
+				ftp.mkdir(absolute(path));
+				return null;
+			});
+		}
+
+		@Override
+		public InputStream get(String path) throws IOException {
+			return map(() -> ftp.read(absolute(path)));
+		}
+
+		@Override
+		public OutputStream put(String path) throws IOException {
+			return map(() -> ftp.write(absolute(path)));
+		}
+
+		@Override
+		public void rm(String path) throws IOException {
+			map(() -> {
+				ftp.remove(absolute(path));
+				return null;
+			});
+		}
+
+		@Override
+		public void rename(String from, String to) throws IOException {
+			map(() -> {
+				String src = absolute(from);
+				String dest = absolute(to);
+				try {
+					ftp.rename(src, dest, CopyMode.Atomic, CopyMode.Overwrite);
+				} catch (UnsupportedOperationException e) {
+					// Older server cannot do POSIX rename...
+					if (!src.equals(dest)) {
+						delete(dest);
+						ftp.rename(src, dest);
+					}
+				}
+				return null;
+			});
+		}
+	}
+}
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
new file mode 100644
index 0000000..4ec6f22
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java
@@ -0,0 +1,499 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.transport.sshd;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
+
+import org.apache.sshd.client.ClientBuilder;
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.auth.UserAuth;
+import org.apache.sshd.client.auth.keyboard.UserAuthKeyboardInteractiveFactory;
+import org.apache.sshd.client.config.hosts.HostConfigEntryResolver;
+import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.compression.BuiltinCompressions;
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.internal.transport.sshd.CachingKeyPairProvider;
+import org.eclipse.jgit.internal.transport.sshd.GssApiWithMicAuthFactory;
+import org.eclipse.jgit.internal.transport.sshd.JGitPasswordAuthFactory;
+import org.eclipse.jgit.internal.transport.sshd.JGitPublicKeyAuthFactory;
+import org.eclipse.jgit.internal.transport.sshd.JGitSshClient;
+import org.eclipse.jgit.internal.transport.sshd.JGitSshConfig;
+import org.eclipse.jgit.internal.transport.sshd.JGitUserInteraction;
+import org.eclipse.jgit.internal.transport.sshd.OpenSshServerKeyVerifier;
+import org.eclipse.jgit.internal.transport.sshd.PasswordProviderWrapper;
+import org.eclipse.jgit.internal.transport.sshd.SshdText;
+import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.SshConstants;
+import org.eclipse.jgit.transport.SshSessionFactory;
+import org.eclipse.jgit.transport.URIish;
+import org.eclipse.jgit.util.FS;
+
+/**
+ * A {@link SshSessionFactory} that uses Apache MINA sshd.
+ *
+ * @since 5.2
+ */
+public class SshdSessionFactory extends SshSessionFactory implements Closeable {
+
+	private final AtomicBoolean closing = new AtomicBoolean();
+
+	private final Set<SshdSession> sessions = new HashSet<>();
+
+	private final Map<Tuple, HostConfigEntryResolver> defaultHostConfigEntryResolver = new ConcurrentHashMap<>();
+
+	private final Map<Tuple, ServerKeyVerifier> defaultServerKeyVerifier = new ConcurrentHashMap<>();
+
+	private final Map<Tuple, FileKeyPairProvider> defaultKeys = new ConcurrentHashMap<>();
+
+	private final KeyCache keyCache;
+
+	private final ProxyDataFactory proxies;
+
+	private File sshDirectory;
+
+	private File homeDirectory;
+
+	/**
+	 * Creates a new {@link SshdSessionFactory} without key cache and a
+	 * {@link DefaultProxyDataFactory}.
+	 */
+	public SshdSessionFactory() {
+		this(null, new DefaultProxyDataFactory());
+	}
+
+	/**
+	 * Creates a new {@link SshdSessionFactory} using the given {@link KeyCache}
+	 * and {@link ProxyDataFactory}. The {@code keyCache} is used for all sessions
+	 * created through this session factory; cached keys are destroyed when the
+	 * session factory is {@link #close() closed}.
+	 * <p>
+	 * Caching ssh keys in memory for an extended period of time is generally
+	 * considered bad practice, but there may be circumstances where using a
+	 * {@link KeyCache} is still the right choice, for instance to avoid that a
+	 * user gets prompted several times for the same password for the same key.
+	 * In general, however, it is preferable <em>not</em> to use a key cache but
+	 * to use a {@link #createKeyPasswordProvider(CredentialsProvider)
+	 * KeyPasswordProvider} that has access to some secure storage and can save
+	 * and retrieve passwords from there without user interaction. Another
+	 * approach is to use an ssh agent.
+	 * </p>
+	 * <p>
+	 * Note that the underlying ssh library (Apache MINA sshd) may or may not
+	 * keep ssh keys in memory for unspecified periods of time irrespective of
+	 * the use of a {@link KeyCache}.
+	 * </p>
+	 *
+	 * @param keyCache
+	 *            {@link KeyCache} to use for caching ssh keys, or {@code null}
+	 *            to not use a key cache
+	 * @param proxies
+	 *            {@link ProxyDataFactory} to use, or {@code null} to not use a
+	 *            proxy database (in which case connections through proxies will
+	 *            not be possible)
+	 */
+	public SshdSessionFactory(KeyCache keyCache, ProxyDataFactory proxies) {
+		super();
+		this.keyCache = keyCache;
+		this.proxies = proxies;
+	}
+
+	/** A simple general map key. */
+	private static final class Tuple {
+		private Object[] objects;
+
+		public Tuple(Object... objects) {
+			this.objects = objects;
+		}
+
+		@Override
+		public boolean equals(Object obj) {
+			if (obj == this) {
+				return true;
+			}
+			if (obj != null && obj.getClass() == Tuple.class) {
+				Tuple other = (Tuple) obj;
+				return Arrays.equals(objects, other.objects);
+			}
+			return false;
+		}
+
+		@Override
+		public int hashCode() {
+			return Arrays.hashCode(objects);
+		}
+	}
+
+	// We can't really use a single client. Clients need to be stopped
+	// properly, and we don't really know when to do that. Instead we use
+	// a dedicated SshClient instance per session. We need a bit of caching to
+	// avoid re-loading the ssh config and keys repeatedly.
+
+	@Override
+	public SshdSession getSession(URIish uri,
+			CredentialsProvider credentialsProvider, FS fs, int tms)
+			throws TransportException {
+		SshdSession session = null;
+		try {
+			session = new SshdSession(uri, () -> {
+				File home = getHomeDirectory();
+				if (home == null) {
+					// Always use the detected filesystem for the user home!
+					// It makes no sense to have different "user home"
+					// directories depending on what file system a repository
+					// is.
+					home = FS.DETECTED.userHome();
+				}
+				File sshDir = getSshDirectory();
+				if (sshDir == null) {
+					sshDir = new File(home, SshConstants.SSH_DIR);
+				}
+				HostConfigEntryResolver configFile = getHostConfigEntryResolver(
+						home, sshDir);
+				KeyPairProvider defaultKeysProvider = getDefaultKeysProvider(
+						sshDir);
+				KeyPasswordProvider passphrases = createKeyPasswordProvider(
+						credentialsProvider);
+				SshClient client = ClientBuilder.builder()
+						.factory(JGitSshClient::new)
+						.filePasswordProvider(
+								createFilePasswordProvider(passphrases))
+						.hostConfigEntryResolver(configFile)
+						.serverKeyVerifier(getServerKeyVerifier(home, sshDir))
+						.compressionFactories(
+								new ArrayList<>(BuiltinCompressions.VALUES))
+						.build();
+				client.setUserInteraction(
+						new JGitUserInteraction(credentialsProvider));
+				client.setUserAuthFactories(getUserAuthFactories());
+				client.setKeyPairProvider(defaultKeysProvider);
+				// JGit-specific things:
+				JGitSshClient jgitClient = (JGitSshClient) client;
+				jgitClient.setKeyCache(getKeyCache());
+				jgitClient.setCredentialsProvider(credentialsProvider);
+				jgitClient.setProxyDatabase(proxies);
+				String defaultAuths = getDefaultPreferredAuthentications();
+				if (defaultAuths != null) {
+					jgitClient.setAttribute(
+							JGitSshClient.PREFERRED_AUTHENTICATIONS,
+							defaultAuths);
+				}
+				// Other things?
+				return client;
+			});
+			session.addCloseListener(s -> unregister(s));
+			register(session);
+			session.connect(Duration.ofMillis(tms));
+			return session;
+		} catch (Exception e) {
+			unregister(session);
+			throw new TransportException(uri, e.getMessage(), e);
+		}
+	}
+
+	@Override
+	public void close() {
+		closing.set(true);
+		boolean cleanKeys = false;
+		synchronized (this) {
+			cleanKeys = sessions.isEmpty();
+		}
+		if (cleanKeys) {
+			KeyCache cache = getKeyCache();
+			if (cache != null) {
+				cache.close();
+			}
+		}
+	}
+
+	private void register(SshdSession newSession) throws IOException {
+		if (newSession == null) {
+			return;
+		}
+		if (closing.get()) {
+			throw new IOException(SshdText.get().sshClosingDown);
+		}
+		synchronized (this) {
+			sessions.add(newSession);
+		}
+	}
+
+	private void unregister(SshdSession oldSession) {
+		boolean cleanKeys = false;
+		synchronized (this) {
+			sessions.remove(oldSession);
+			cleanKeys = closing.get() && sessions.isEmpty();
+		}
+		if (cleanKeys) {
+			KeyCache cache = getKeyCache();
+			if (cache != null) {
+				cache.close();
+			}
+		}
+	}
+
+	/**
+	 * Set a global directory to use as the user's home directory
+	 *
+	 * @param homeDir
+	 *            to use
+	 */
+	public void setHomeDirectory(@NonNull File homeDir) {
+		if (homeDir.isAbsolute()) {
+			homeDirectory = homeDir;
+		} else {
+			homeDirectory = homeDir.getAbsoluteFile();
+		}
+	}
+
+	/**
+	 * Retrieves the global user home directory
+	 *
+	 * @return the directory, or {@code null} if not set
+	 */
+	public File getHomeDirectory() {
+		return homeDirectory;
+	}
+
+	/**
+	 * Set a global directory to use as the .ssh directory
+	 *
+	 * @param sshDir
+	 *            to use
+	 */
+	public void setSshDirectory(@NonNull File sshDir) {
+		if (sshDir.isAbsolute()) {
+			sshDirectory = sshDir;
+		} else {
+			sshDirectory = sshDir.getAbsoluteFile();
+		}
+	}
+
+	/**
+	 * Retrieves the global .ssh directory
+	 *
+	 * @return the directory, or {@code null} if not set
+	 */
+	public File getSshDirectory() {
+		return sshDirectory;
+	}
+
+	/**
+	 * Obtain a {@link HostConfigEntryResolver} to read the ssh config file and
+	 * to determine host entries for connections.
+	 *
+	 * @param homeDir
+	 *            home directory to use for ~ replacement
+	 * @param sshDir
+	 *            to use for looking for the config file
+	 * @return the resolver
+	 */
+	@NonNull
+	private HostConfigEntryResolver getHostConfigEntryResolver(
+			@NonNull File homeDir, @NonNull File sshDir) {
+		return defaultHostConfigEntryResolver.computeIfAbsent(
+				new Tuple(homeDir, sshDir),
+				t -> new JGitSshConfig(homeDir,
+						new File(sshDir, SshConstants.CONFIG),
+						getLocalUserName()));
+	}
+
+	/**
+	 * Obtain a {@link ServerKeyVerifier} to read known_hosts files and to
+	 * verify server host keys. The default implementation returns a
+	 * {@link ServerKeyVerifier} that recognizes the two openssh standard files
+	 * {@code ~/.ssh/known_hosts} and {@code ~/.ssh/known_hosts2} as well as any
+	 * files configured via the {@code UserKnownHostsFile} option in the ssh
+	 * config file.
+	 *
+	 * @param homeDir
+	 *            home directory to use for ~ replacement
+	 * @param sshDir
+	 *            representing ~/.ssh/
+	 * @return the resolver
+	 */
+	@NonNull
+	private ServerKeyVerifier getServerKeyVerifier(@NonNull File homeDir,
+			@NonNull File sshDir) {
+		return defaultServerKeyVerifier.computeIfAbsent(
+				new Tuple(homeDir, sshDir),
+				t -> new OpenSshServerKeyVerifier(true,
+						getDefaultKnownHostsFiles(sshDir)));
+	}
+
+	/**
+	 * Gets the list of default user known hosts files. The default returns
+	 * ~/.ssh/known_hosts and ~/.ssh/known_hosts2. The ssh config
+	 * {@code UserKnownHostsFile} overrides this default.
+	 *
+	 * @param sshDir
+	 * @return the possibly empty list of default known host file paths.
+	 */
+	@NonNull
+	protected List<Path> getDefaultKnownHostsFiles(@NonNull File sshDir) {
+		return Arrays.asList(sshDir.toPath().resolve(SshConstants.KNOWN_HOSTS),
+				sshDir.toPath().resolve(SshConstants.KNOWN_HOSTS + '2'));
+	}
+
+	/**
+	 * Determines a {@link KeyPairProvider} to use to load the default keys.
+	 *
+	 * @param sshDir
+	 *            to look in for keys
+	 * @return the {@link KeyPairProvider}
+	 */
+	@NonNull
+	private KeyPairProvider getDefaultKeysProvider(@NonNull File sshDir) {
+		return defaultKeys.computeIfAbsent(new Tuple(sshDir),
+				t -> new CachingKeyPairProvider(getDefaultIdentities(sshDir),
+						getKeyCache()));
+	}
+
+	/**
+	 * Gets a list of default identities, i.e., private key files that shall
+	 * always be tried for public key authentication. Typically those are
+	 * ~/.ssh/id_dsa, ~/.ssh/id_rsa, and so on. The default implementation
+	 * returns the files defined in {@link SshConstants#DEFAULT_IDENTITIES}.
+	 *
+	 * @param sshDir
+	 *            the directory that represents ~/.ssh/
+	 * @return a possibly empty list of paths containing default identities
+	 *         (private keys)
+	 */
+	@NonNull
+	protected List<Path> getDefaultIdentities(@NonNull File sshDir) {
+		return Arrays
+				.asList(SshConstants.DEFAULT_IDENTITIES).stream()
+				.map(s -> new File(sshDir, s).toPath()).filter(Files::exists)
+				.collect(Collectors.toList());
+	}
+
+	/**
+	 * Obtains the {@link KeyCache} to use to cache loaded keys.
+	 *
+	 * @return the {@link KeyCache}, or {@code null} if none.
+	 */
+	protected final KeyCache getKeyCache() {
+		return keyCache;
+	}
+
+	/**
+	 * Creates a {@link KeyPasswordProvider} for a new session.
+	 *
+	 * @param provider
+	 *            the {@link CredentialsProvider} to delegate to for user
+	 *            interactions
+	 * @return a new {@link KeyPasswordProvider}
+	 */
+	@NonNull
+	protected KeyPasswordProvider createKeyPasswordProvider(
+			CredentialsProvider provider) {
+		return new IdentityPasswordProvider(provider);
+	}
+
+	/**
+	 * Creates a {@link FilePasswordProvider} for a new session.
+	 *
+	 * @param provider
+	 *            the {@link KeyPasswordProvider} to delegate to
+	 * @return a new {@link FilePasswordProvider}
+	 */
+	@NonNull
+	private FilePasswordProvider createFilePasswordProvider(
+			KeyPasswordProvider provider) {
+		return new PasswordProviderWrapper(provider);
+	}
+
+	/**
+	 * Gets the user authentication mechanisms (or rather, factories for them).
+	 * By default this returns gssapi-with-mic, public-key, password, and
+	 * keyboard-interactive, in that order. The order is only significant if the
+	 * ssh config does <em>not</em> set {@code PreferredAuthentications}; if it
+	 * is set, the order defined there will be taken.
+	 *
+	 * @return the non-empty list of factories.
+	 */
+	@NonNull
+	private List<NamedFactory<UserAuth>> getUserAuthFactories() {
+		// About the order of password and keyboard-interactive, see upstream
+		// bug https://issues.apache.org/jira/projects/SSHD/issues/SSHD-866 .
+		// Password auth doesn't have this problem.
+		return Collections.unmodifiableList(
+				Arrays.asList(GssApiWithMicAuthFactory.INSTANCE,
+						JGitPublicKeyAuthFactory.INSTANCE,
+						JGitPasswordAuthFactory.INSTANCE,
+						UserAuthKeyboardInteractiveFactory.INSTANCE));
+	}
+
+	/**
+	 * Gets the list of default preferred authentication mechanisms. If
+	 * {@code null} is returned the openssh default list will be in effect. If
+	 * the ssh config defines {@code PreferredAuthentications} the value from
+	 * the ssh config takes precedence.
+	 *
+	 * @return a comma-separated list of algorithm names, or {@code null} if
+	 *         none
+	 */
+	protected String getDefaultPreferredAuthentications() {
+		return null;
+	}
+}
diff --git a/org.eclipse.jgit.test/.classpath b/org.eclipse.jgit.test/.classpath
index 84b5052..7cc18cc 100644
--- a/org.eclipse.jgit.test/.classpath
+++ b/org.eclipse.jgit.test/.classpath
@@ -1,9 +1,22 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-	<classpathentry excluding="**/*.idx|**/*.pack" kind="src" path="tst"/>
+	<classpathentry excluding="**/*.idx|**/*.pack" kind="src" path="tst" output="bin-tst">
+		<attributes>
+			<attribute name="test" value="true"/>
+		</attributes>
+	</classpathentry>
 	<classpathentry kind="src" path="src"/>
-	<classpathentry kind="src" path="tst-rsrc"/>
-	<classpathentry kind="src" path="exttst"/>
+	<classpathentry kind="src" path="tst-rsrc" output="bin-tst">
+		<attributes>
+			<attribute name="test" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="src" path="exttst" output="bin-tst">
+		<attributes>
+			<attribute name="test" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="src" path="resources"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
 	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
 	<classpathentry kind="output" path="bin"/>
diff --git a/org.eclipse.jgit.test/.gitignore b/org.eclipse.jgit.test/.gitignore
index 934e0e0..561cf84 100644
--- a/org.eclipse.jgit.test/.gitignore
+++ b/org.eclipse.jgit.test/.gitignore
@@ -1,2 +1,3 @@
 /bin
 /target
+/bin-tst/
diff --git a/org.eclipse.jgit.test/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.test/.settings/org.eclipse.jdt.core.prefs
index 794592d..2ca78ff 100644
--- a/org.eclipse.jgit.test/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.test/.settings/org.eclipse.jdt.core.prefs
@@ -91,7 +91,7 @@
 org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
 org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
 org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning
-org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error
 org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
diff --git a/org.eclipse.jgit.test/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.test/.settings/org.eclipse.mylyn.team.ui.prefs
index 0cba949..2fca432 100644
--- a/org.eclipse.jgit.test/.settings/org.eclipse.mylyn.team.ui.prefs
+++ b/org.eclipse.jgit.test/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -1,3 +1,3 @@
 #Tue Jul 19 20:11:28 CEST 2011
-commit.comment.template=${task.description} \n\nBug\: ${task.key}
+commit.comment.template=${task.description}\n\nBug\: ${task.key}
 eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.test/BUILD b/org.eclipse.jgit.test/BUILD
index bbda838..0b18e5e 100644
--- a/org.eclipse.jgit.test/BUILD
+++ b/org.eclipse.jgit.test/BUILD
@@ -6,7 +6,10 @@
 
 PKG = "tst/org/eclipse/jgit/"
 
-HELPERS = glob(["src/**/*.java"]) + [PKG + c for c in [
+HELPERS = glob(
+    ["src/**/*.java"],
+    exclude = ["src/org/eclipse/jgit/transport/ssh/*.java"],
+) + [PKG + c for c in [
     "api/AbstractRemoteCommandTest.java",
     "diff/AbstractDiffTestCase.java",
     "internal/storage/file/GcTestCase.java",
@@ -18,6 +21,7 @@
     "nls/NonTranslatedBundle.java",
     "revwalk/RevQueueTestCase.java",
     "revwalk/RevWalkTestCase.java",
+    "transport/ObjectIdMatcher.java",
     "transport/SpiTransport.java",
     "treewalk/FileTreeIteratorWithTimeControl.java",
     "treewalk/filter/AlwaysCloneTreeFilter.java",
@@ -31,6 +35,8 @@
     PKG + "lib/sorttest.gitindex.dat",
 ]
 
+RESOURCES = glob(["resources/**"])
+
 tests(glob(
     ["tst/**/*.java"],
     exclude = HELPERS + DATA,
@@ -42,12 +48,34 @@
     srcs = HELPERS,
     resources = DATA,
     deps = [
+        "//lib:jsch",
         "//lib:junit",
+        "//lib:mockito",
         "//org.eclipse.jgit:jgit",
         "//org.eclipse.jgit.junit:junit",
     ],
 )
 
+java_library(
+    name = "sshd-helpers",
+    testonly = 1,
+    srcs = glob(["src/org/eclipse/jgit/transport/ssh/*.java"]),
+    resource_strip_prefix = "org.eclipse.jgit.test/resources",
+    resources = RESOURCES,
+    visibility = [
+        "//org.eclipse.jgit.ssh.apache.test:__pkg__",
+    ],
+    deps = [
+        "//lib:jsch",
+        "//lib:junit",
+        "//lib:sshd-core",
+        "//lib:sshd-sftp",
+        "//org.eclipse.jgit:jgit",
+        "//org.eclipse.jgit.junit:junit",
+        "//org.eclipse.jgit.junit.ssh:junit-ssh",
+    ],
+)
+
 java_import(
     name = "tst_rsrc",
     jars = [":tst_rsrc_jar"],
diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
index 69ea99b..55ccecf 100644
--- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
@@ -3,63 +3,73 @@
 Bundle-Name: %plugin_name
 Automatic-Module-Name: org.eclipse.jgit.test
 Bundle-SymbolicName: org.eclipse.jgit.test
-Bundle-Version: 5.1.9.qualifier
+Bundle-Version: 5.2.3.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)",
  com.jcraft.jsch;version="[0.1.54,0.2.0)",
- org.eclipse.jgit.api;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.api.errors;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.attributes;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.awtui;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.blame;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.diff;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.dircache;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.errors;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.events;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.fnmatch;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.gitrepo;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.hooks;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.ignore;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.ignore.internal;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.internal;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.internal.fsck;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.internal.storage.io;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.internal.storage.pack;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.internal.storage.reftable;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.internal.storage.reftree;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.junit;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lfs;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lib;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.merge;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.nls;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.notes;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.patch;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.pgm;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.pgm.internal;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.revplot;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.revwalk;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.revwalk.filter;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.storage.file;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.storage.pack;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.submodule;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.transport;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.transport.http;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.transport.resolver;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.treewalk;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.treewalk.filter;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.util;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.util.io;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.util.sha1;version="[5.1.9,5.2.0)",
+ net.bytebuddy.dynamic.loading;version="[1.7.0,2.0.0)",
+ org.eclipse.jgit.annotations;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.api;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.api.errors;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.attributes;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.awtui;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.blame;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.diff;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.dircache;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.errors;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.events;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.fnmatch;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.gitrepo;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.hooks;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.ignore;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.ignore.internal;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal.fsck;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal.storage.io;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal.storage.reftable;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal.storage.reftree;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.internal.transport.parser;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.junit;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.junit.ssh;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lfs;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lib;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.merge;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.nls;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.notes;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.patch;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.pgm;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.pgm.internal;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.revplot;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.revwalk;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.revwalk.filter;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.storage.file;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.storage.pack;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.submodule;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.transport;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.transport.http;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.transport.resolver;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.treewalk;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.treewalk.filter;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.util;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.util.io;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.util.sha1;version="[5.2.3,5.3.0)",
  org.junit;version="[4.12,5.0.0)",
  org.junit.experimental.theories;version="[4.12,5.0.0)",
  org.junit.rules;version="[4.12,5.0.0)",
  org.junit.runner;version="[4.12,5.0.0)",
  org.junit.runners;version="[4.12,5.0.0)",
+ org.mockito;version="[2.13.0,3.0.0)",
+ org.mockito.invocation;version="[2.13.0,3.0.0)",
+ org.mockito.junit;version="[2.13.0,3.0.0)",
+ org.mockito.stubbing;version="[2.13.0,3.0.0)",
+ org.objenesis;version="[2.6.0,3.0.0)",
  org.slf4j;version="[1.7.0,2.0.0)"
 Require-Bundle: org.hamcrest.core;bundle-version="[1.1.0,2.0.0)",
  org.hamcrest.library;bundle-version="[1.1.0,2.0.0)"
+Export-Package: org.eclipse.jgit.transport.ssh;version="5.2.3";x-friends:="org.eclipse.jgit.ssh.apache.test"
diff --git a/org.eclipse.jgit.test/build.properties b/org.eclipse.jgit.test/build.properties
index e7b3b99..78c8f55 100644
--- a/org.eclipse.jgit.test/build.properties
+++ b/org.eclipse.jgit.test/build.properties
@@ -1,9 +1,12 @@
 source.. = tst/,\
            tst-rsrc/,\
            exttst/,\
-           src/
+           src/,\
+           resources/
 bin.includes = META-INF/,\
                .,\
-               plugin.properties
+               plugin.properties,\
+               bin-tst/,\
+               bin/
 additional.bundles = org.apache.log4j,\
                      org.slf4j.impl.log4j12
diff --git a/org.eclipse.jgit.test/pom.xml b/org.eclipse.jgit.test/pom.xml
index 7a4d580..6b19eb3 100644
--- a/org.eclipse.jgit.test/pom.xml
+++ b/org.eclipse.jgit.test/pom.xml
@@ -52,7 +52,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>5.1.9-SNAPSHOT</version>
+    <version>5.2.3-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.test</artifactId>
@@ -73,12 +73,10 @@
     </dependency>
 
     <!-- Optional security provider for encryption tests. -->
-    <!-- See https://dev.eclipse.org/ipzilla/show_bug.cgi?id=9554 -->
-    <!-- See https://bugs.eclipse.org/bugs/show_bug.cgi?id=467064 -->
     <dependency>
       <groupId>org.bouncycastle</groupId>
       <artifactId>bcprov-jdk15on</artifactId>
-      <version>1.52</version>
+      <version>1.59</version>
       <scope>test</scope>
      </dependency>
 
@@ -90,6 +88,12 @@
     </dependency>
 
     <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <version>2.13.0</version>
+    </dependency>
+
+    <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit</artifactId>
       <version>${project.version}</version>
@@ -103,6 +107,12 @@
 
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit.junit.ssh</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.ui</artifactId>
       <version>${project.version}</version>
     </dependency>
@@ -142,6 +152,18 @@
       </testResource>
     </testResources>
 
+    <resources>
+      <resource>
+        <directory>.</directory>
+        <includes>
+          <include>plugin.properties</include>
+        </includes>
+      </resource>
+      <resource>
+        <directory>resources/</directory>
+      </resource>
+    </resources>
+
     <plugins>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
@@ -157,7 +179,7 @@
       <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
         <configuration>
-          <argLine>@{argLine} -Xmx768m -Dfile.encoding=UTF-8 -Djava.io.tmpdir=${project.build.directory}</argLine>
+          <argLine>-Xmx1024m -Dfile.encoding=UTF-8 -Djava.io.tmpdir=${project.build.directory}</argLine>
           <includes>
             <include>**/*Test.java</include>
             <include>**/*Tests.java</include>
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_dsa b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_dsa
new file mode 100644
index 0000000..f097516
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_dsa
@@ -0,0 +1,12 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBuwIBAAKBgQC+mJEX/XBloWhNM+BEuoh5z+EAuZfVyJ8cHNKlQmC1sWrENKGh
+P8ZhzWeHW0A7JnvTQgMqW6yD4mDzCpbR1wEz5KeXAphEjCGPnRik7Q4RjpZTd6Nq
+nNF/CYYGYuwR7ZGUPITTpKJWgX6NkEk+a4tvTWP7xfxOq5iKIspFEhEOlQIVAIBi
+TdAR8M2twrXZdspBjdJprjDXAoGAOrRYdXRHhpsOewIi9GQah0lde7AVrmZawK9Z
+BwhDUagL58gS8PvcsNNVhS2dKEX45pqZmgayt2UEE/5bke3+CdZtStDsezBYMu8P
+I/0qjOULhl7xLJT5ayCIN2ZuvcH8vtqH89fXgZkIz0c68AzY1ZFjJPc+TdE0puI9
+3mMVRaoCgYEAslyMZiOwYA3oiFMQTJEphKdgejWsjqQ9LoKppfZ3d4Jj1V3tgI1s
+/wHfoneUUrUwM+sMHZKXbBDLWWQUOSIxDYcXKDkbZ1FlmhvJR+45D2LyLKjEnjVD
+lQCwYly4P26zXqciZS7k3H/DjiHtAPUeoHm9IYb1A03K8Bd/xW0guMcCFFeUfQeX
+3mFPCfKJ5uXMjkPUqIo/
+-----END DSA PRIVATE KEY-----
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_dsa.pub b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_dsa.pub
new file mode 100644
index 0000000..6766853
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_dsa.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAL6YkRf9cGWhaE0z4ES6iHnP4QC5l9XInxwc0qVCYLWxasQ0oaE/xmHNZ4dbQDsme9NCAypbrIPiYPMKltHXATPkp5cCmESMIY+dGKTtDhGOllN3o2qc0X8JhgZi7BHtkZQ8hNOkolaBfo2QST5ri29NY/vF/E6rmIoiykUSEQ6VAAAAFQCAYk3QEfDNrcK12XbKQY3Saa4w1wAAAIA6tFh1dEeGmw57AiL0ZBqHSV17sBWuZlrAr1kHCENRqAvnyBLw+9yw01WFLZ0oRfjmmpmaBrK3ZQQT/luR7f4J1m1K0Ox7MFgy7w8j/SqM5QuGXvEslPlrIIg3Zm69wfy+2ofz19eBmQjPRzrwDNjVkWMk9z5N0TSm4j3eYxVFqgAAAIEAslyMZiOwYA3oiFMQTJEphKdgejWsjqQ9LoKppfZ3d4Jj1V3tgI1s/wHfoneUUrUwM+sMHZKXbBDLWWQUOSIxDYcXKDkbZ1FlmhvJR+45D2LyLKjEnjVDlQCwYly4P26zXqciZS7k3H/DjiHtAPUeoHm9IYb1A03K8Bd/xW0guMc= testuser
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_dsa_testpass b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_dsa_testpass
new file mode 100644
index 0000000..375d38f
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_dsa_testpass
@@ -0,0 +1,15 @@
+-----BEGIN DSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,EBB6ACAA4F1FC558865344E3C2B91A5F
+
+CWMAq20YBO8ueHnmQ7IaKa7ISEvNbwbzqoBIxor6TZYSU3JvlIf5AL2UvGpMJDk1
+fyROdCjdVAeWKQC0peU54D3YnD3am4gZlrclPMjMRnjBmqO+vnU7bTudIt/8y6vg
+gmHZki0/aceQ6QvGwGrxBezBPaK4Bc926lePujHHE/PbtuQgkBw7rhIBGKVuy0qN
+sFbC4AGnYl5tudy5RLvCcpQvpDCjnYAfGQVimRYSOsaOwTEBvsnQFUH1pqQAYLC4
+Capo1yj6Q0smzwsGoyFSvmPkyzLbMTT42m+M48gc5nuaOkbU5absqOb8cQgRVmWB
+W1HnpufqGtyF6vBK+qlzg157bhQDYMwZuubX+IrTRL67djBiSIpiRDZduJavT3zq
+iSrRGSnjnkhp4NxtJJjprDQe4VAZEccN5GWPjClbogjpsG+fmTJiNDMI88L11DrV
+Vjeaxsql31iur/xGwvmBYd+/V+Nu4v7kA4XViO/3ZIpqi8qvQ3si5hbALSX0OPnm
+9q0eMp9qfmzPvbmysq2BEenBaZDwEWYTYpcF23pjwc1EvmfP8EAYT+xH95ZhxVmc
+Sujq0VyGeIhy7+gRHZo2Fg==
+-----END DSA PRIVATE KEY-----
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_dsa_testpass.pub b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_dsa_testpass.pub
new file mode 100644
index 0000000..6766853
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_dsa_testpass.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAL6YkRf9cGWhaE0z4ES6iHnP4QC5l9XInxwc0qVCYLWxasQ0oaE/xmHNZ4dbQDsme9NCAypbrIPiYPMKltHXATPkp5cCmESMIY+dGKTtDhGOllN3o2qc0X8JhgZi7BHtkZQ8hNOkolaBfo2QST5ri29NY/vF/E6rmIoiykUSEQ6VAAAAFQCAYk3QEfDNrcK12XbKQY3Saa4w1wAAAIA6tFh1dEeGmw57AiL0ZBqHSV17sBWuZlrAr1kHCENRqAvnyBLw+9yw01WFLZ0oRfjmmpmaBrK3ZQQT/luR7f4J1m1K0Ox7MFgy7w8j/SqM5QuGXvEslPlrIIg3Zm69wfy+2ofz19eBmQjPRzrwDNjVkWMk9z5N0TSm4j3eYxVFqgAAAIEAslyMZiOwYA3oiFMQTJEphKdgejWsjqQ9LoKppfZ3d4Jj1V3tgI1s/wHfoneUUrUwM+sMHZKXbBDLWWQUOSIxDYcXKDkbZ1FlmhvJR+45D2LyLKjEnjVDlQCwYly4P26zXqciZS7k3H/DjiHtAPUeoHm9IYb1A03K8Bd/xW0guMc= testuser
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_256 b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_256
new file mode 100644
index 0000000..8a4c864
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_256
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIAqXVKoLNr7/wNluxmGZnZmJCD/5h06ptAICRk+8FIjfoAoGCCqGSM49
+AwEHoUQDQgAEoQHTUWwu3nJnCHeSv3YE59UxfuGNjAXLzK0MjDwoXt6/qePjjKAQ
+ehHdAIYQHr9zYJu5SA5b86HL5glqjcy+Pg==
+-----END EC PRIVATE KEY-----
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_256.pub b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_256.pub
new file mode 100644
index 0000000..43540ec
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_256.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKEB01FsLt5yZwh3kr92BOfVMX7hjYwFy8ytDIw8KF7ev6nj44ygEHoR3QCGEB6/c2CbuUgOW/Ohy+YJao3Mvj4= testuser
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_256_testpass b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_256_testpass
new file mode 100644
index 0000000..b767c8e
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_256_testpass
@@ -0,0 +1,8 @@
+-----BEGIN EC PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,86940587F5C93441B585F469FF31AC06
+
+LaIyzOCeBPJA6OkFOFnFfVorYO+Rm1g5QpvqEcFZ+FCuEvhMZN00NMZ5hHKvwQLt
+XSK5Se8MUD+e6qFH/ZcoYTixUqYjYJlOkxJzKaXg5nM82wQHa1LqQqcL4IDrJmzv
+qJbCLtl6XOfkQQUA6gezqhtiNYWLDZIPfZ0dsaIB/fU=
+-----END EC PRIVATE KEY-----
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_256_testpass.pub b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_256_testpass.pub
new file mode 100644
index 0000000..43540ec
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_256_testpass.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKEB01FsLt5yZwh3kr92BOfVMX7hjYwFy8ytDIw8KF7ev6nj44ygEHoR3QCGEB6/c2CbuUgOW/Ohy+YJao3Mvj4= testuser
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_384 b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_384
new file mode 100644
index 0000000..dc2ac86
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_384
@@ -0,0 +1,6 @@
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDAgAgPcgkPaitxOrphrrLe+am0eUhYi346UUTnb5WZL3164MEjFByd9
+Egv6KwB4hCqgBwYFK4EEACKhZANiAAQhJrJ+vJLbkbd9C1he+4XuxaOyZ1IqYJqz
+PZCXcKkIlgy+0I07RAxRUd75GHKc4ViyUnLq5odV25H6FNzHJHO7ifE4H6jrEpA/
+UL6LkfZReYZ4sNmeQI7MBXm2IXQsIZ4=
+-----END EC PRIVATE KEY-----
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_384.pub b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_384.pub
new file mode 100644
index 0000000..3e813a5
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_384.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBCEmsn68ktuRt30LWF77he7Fo7JnUipgmrM9kJdwqQiWDL7QjTtEDFFR3vkYcpzhWLJScurmh1XbkfoU3Mckc7uJ8TgfqOsSkD9QvouR9lF5hniw2Z5AjswFebYhdCwhng== testuser
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_384_testpass b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_384_testpass
new file mode 100644
index 0000000..06032d0
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_384_testpass
@@ -0,0 +1,9 @@
+-----BEGIN EC PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,80B1C4D6D9B45690A07B9886050C63A7
+
+WxS7EGs77p1aPZuxXW0G/yTFKAh4M30AaeGQBPjDR/HTAmPJe3irDH56fdmGhY4+
+zBT+6X1VppB+UqB0nJ/qHq7FeA37eJPXJnuskPh2BzLlBaVhmEnzZylEW33gzAuH
+XzC/Z2OjdWRjn+rBXM5fwo9IIC0WzTNpBokdeMo8tpnPzGTlsTFeyVgMZJ3wOlCO
+4ItX9ddY5P+MrLzWP672IyZZqAQGfLec4YoJ286wpHY=
+-----END EC PRIVATE KEY-----
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_384_testpass.pub b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_384_testpass.pub
new file mode 100644
index 0000000..3e813a5
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_384_testpass.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBCEmsn68ktuRt30LWF77he7Fo7JnUipgmrM9kJdwqQiWDL7QjTtEDFFR3vkYcpzhWLJScurmh1XbkfoU3Mckc7uJ8TgfqOsSkD9QvouR9lF5hniw2Z5AjswFebYhdCwhng== testuser
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_521 b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_521
new file mode 100644
index 0000000..c28151e
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_521
@@ -0,0 +1,7 @@
+-----BEGIN EC PRIVATE KEY-----
+MIHcAgEBBEIB4zI/MCFSfJ0wfyLwZPxG1vP2o3fF7fEuOTpK+fxbDHKYz6r4bNv3
+HkPQEVTIAqDl7r5Ebcx0BMeYr9oe69tPZIigBwYFK4EEACOhgYkDgYYABAChltEM
+zT8dXwIhQD2iuy7QbaBkhWMhpFaxztvzSQqoTZvBgBsOmSr9frFA93lSQoHD1Bge
+wuwBkNGm9lRcw0tEgABqifONkj07Qj2847MKS1iiVu1sHh7Ys3YimyfJc+nZRNi+
+W03nkcdvWd6PP8y/VENoV7+BtIO9txj8Dt5LYOtFgw==
+-----END EC PRIVATE KEY-----
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_521.pub b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_521.pub
new file mode 100644
index 0000000..9bac1e8
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_521.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAChltEMzT8dXwIhQD2iuy7QbaBkhWMhpFaxztvzSQqoTZvBgBsOmSr9frFA93lSQoHD1BgewuwBkNGm9lRcw0tEgABqifONkj07Qj2847MKS1iiVu1sHh7Ys3YimyfJc+nZRNi+W03nkcdvWd6PP8y/VENoV7+BtIO9txj8Dt5LYOtFgw== testuser
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_521_testpass b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_521_testpass
new file mode 100644
index 0000000..c1c1bba
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_521_testpass
@@ -0,0 +1,10 @@
+-----BEGIN EC PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,7070032284B3C310353B8C352AB2D8CE
+
+UBgXTwobcLX1VFtQaLNiwwVzdN1+TlmhSRCnU+kv2EpunXxfvyOVS1mZTam9NyhE
+O0Mc7REi5hDHp8UYM7MP+wrwK+QM3D2Vm2/Rh0+acd4Gu2XGACJHWXGIyKwNsU0R
+ZddusHIi+979sHw3vSUFCvuDwc9YZBoujpzls7NYEWXiAVv6wd1RCtAynkBk/uvc
+1F7iHLuRttejBPvrb/a2AxY0pFpCuCVmGjuiS5bfVWBj7xLEplqdU6/95rd9pRwx
+e2uRlU0AFiQGNPStfhjgfCWnmf+aX3vAgVqkLMYKYQE=
+-----END EC PRIVATE KEY-----
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_521_testpass.pub b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_521_testpass.pub
new file mode 100644
index 0000000..9bac1e8
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ecdsa_521_testpass.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAChltEMzT8dXwIhQD2iuy7QbaBkhWMhpFaxztvzSQqoTZvBgBsOmSr9frFA93lSQoHD1BgewuwBkNGm9lRcw0tEgABqifONkj07Qj2847MKS1iiVu1sHh7Ys3YimyfJc+nZRNi+W03nkcdvWd6PP8y/VENoV7+BtIO9txj8Dt5LYOtFgw== testuser
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ed25519 b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ed25519
new file mode 100644
index 0000000..02afa54
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ed25519
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACBIJlrW8XB46iAVY0XqbjYKG8wJ95iILxOb5ONQhFBvPQAAAJC8jORLvIzk
+SwAAAAtzc2gtZWQyNTUxOQAAACBIJlrW8XB46iAVY0XqbjYKG8wJ95iILxOb5ONQhFBvPQ
+AAAECjklggj+glO2K60Ptg+aXYGBdvXtk9TQnKINhrEIxW9UgmWtbxcHjqIBVjRepuNgob
+zAn3mIgvE5vk41CEUG89AAAACHRlc3R1c2VyAQIDBAU=
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ed25519.pub b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ed25519.pub
new file mode 100644
index 0000000..7857db5
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ed25519.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEgmWtbxcHjqIBVjRepuNgobzAn3mIgvE5vk41CEUG89 testuser
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ed25519_testpass b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ed25519_testpass
new file mode 100644
index 0000000..7ad4a77
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ed25519_testpass
@@ -0,0 +1,8 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABA4hLhtuV
+MNBBC+j45F4KFcAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIEgmWtbxcHjqIBVj
+RepuNgobzAn3mIgvE5vk41CEUG89AAAAkPH343T+NbHb05J/6CHnF9h7C11LJDHe2x9+HC
+dNB50fP9M+KJ/cC5cqIeHm8y0fg+wX2WLlJPjNVoSd5MciWCfUWO0k32ciVpoyrGCz5Gh6
+axKVVY42QjdgO0S2QxWClnAuMdkVdl2ke/PcGp4yqTTIruAAB0m3d0jZdKNT1Vziww0rQB
++DOo7xQ9Tx99U+rA==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ed25519_testpass.pub b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ed25519_testpass.pub
new file mode 100644
index 0000000..7857db5
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ed25519_testpass.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEgmWtbxcHjqIBVjRepuNgobzAn3mIgvE5vk41CEUG89 testuser
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_1024 b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_1024
new file mode 100644
index 0000000..0b40367
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_1024
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDGfj0Jmqj+CUb+WdFrlkRV49TJtNzvvMb/nX20zqgGm50cOIYr
+MzfFpSQN630pXeAidIgiV/PWAsipntQfSWPRG+RpB/wMKHVUNPJCJkjjRFEa56Yx
+gAhgNwF511K13x4p2tEN0r6wsfw1nos9VoO8XDBAu3lellAgBdufyCt8vwIDAQAB
+AoGBAKU+bNP1BGDQGmEfJv+5DlSuofP19MREVSpx0zfVnv45SFc5G0EVl4Wb0GMi
+O4VXmIM2nipxLBZrJOBI0HDnaQcx1zQR6tpvBO7BbAU0sflOvUDldUStTnz3TTQW
+2ECm2y8bsArNqkeLndqis3ICmYL1budhDdUYYcqv10IlbjPJAkEA6yE0zduCE2wM
+Ob7lcqiQCOiXeZ0KijHTmSZV4Fn4HRbp+XuxUpjSWFaoDTO0bncGNE+JYjywe64V
+XvEORb1hTQJBANgcjEoCrUFY7VYWx3f1tpN0Q6jwwcj67Sd+ysaZNgghTPU32GTa
+auGQFv+tifUQMyyVrhAfZ6s8myKOH5SWUDsCQGVvqOkaRq58UXXkDfZ+E81UEm0L
+u81Mm52ZdTjZd3mNNhlELIaWmUA0+kDfynpRbOLKYVl5FyX0PxH7ao3Zmo0CQFpL
++1YFLk0KkggRdoCp+wI7ZvXUurN2HNcOxD0c0RWujFA9aD4jgNsEcIeeA/GQNkGf
+vN3hsVg793oFti5Ia/cCQAubCMvRqFTyXUBervPVC0kibO3OwYt2xN/7lQXAVSfm
+nRwV/46trioV3rMF84hpOk/46Qe5hqbWyQnL+dZljpY=
+-----END RSA PRIVATE KEY-----
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_1024.pub b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_1024.pub
new file mode 100644
index 0000000..4aded97
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_1024.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDGfj0Jmqj+CUb+WdFrlkRV49TJtNzvvMb/nX20zqgGm50cOIYrMzfFpSQN630pXeAidIgiV/PWAsipntQfSWPRG+RpB/wMKHVUNPJCJkjjRFEa56YxgAhgNwF511K13x4p2tEN0r6wsfw1nos9VoO8XDBAu3lellAgBdufyCt8vw== testuser
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_1024_testpass b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_1024_testpass
new file mode 100644
index 0000000..0b66dc0
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_1024_testpass
@@ -0,0 +1,18 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,4B8025AB7456E0A2B48408407C6E3FF4
+
+B9gztX+5QQPqMR/79eJHxjNdo9baoKjfWY+Ye7t1h7ucOPMCEXRSP8FwPwBfbzQh
+6W1AHOfiDCHTzArDRG9SXrFfRlU+8o5ffs/TStTNqde/AXJeNuM3pwbmqKV1m9oY
+oWelabmGtNUvGMAHMFm/2uk4BgS9Kjv71KnJg0cQQfIiPKTPBncJe/R5mf6O12rB
+ByOrrlDmjtgveZZsgggEZbU9Y9DYiHZp6yT0JepxIWNImQ/A9EeUPTQheVB2ECT6
+DLUOwRfyFhdvsfD2eXLK+u7T47keFny3rIfm1e8HC1y3X+T/nFxKGoShecx1NmEL
+HMgOKyFSwGSZh5jxE66dSQoc+rRZhCWSyPJEb9cjwp8JLON8oH3Yg+PIXYJhMFK+
+nghAIVXp3/H+cYXMN27j21cRGC7ePuF3YX242Gr+LSj42Wf4qCMTyvWur8WrSe6U
+iyrWJ8+w2J7O7rRHGM8v+GYGaiX1qIXFheM/774vsDmjuueOhkjiqs254gaap8xk
+LcJUuqJU2AL21+eW+R+EG3Rl/AbMIaQ4GFDpHfgEmmvVVoOvJunNQkDIP9JzKczO
+g7cN/EYLUC2TcdmNaiunB8RhXMiaTqw4kYJEzy4lsxk/xjubC7vlQKTvtnWCpob+
+WpHX/2FBdPPULt38AIk4HQq7vKvKw9TmvGeOvQmCUun7eCFFhxKrwNKO5YCXAHvs
+fv7JNGfrST4jwbqCvamuk+XTf0GkgJN83G7DT04EIzee6wwai/NRDybgYptJsj9G
+6wBpKH15BtkktuUzM1MCt5+T6Ccsg+d6xE6eStimwDxkXCjvgz/KlS+sPKe7uS4h
+-----END RSA PRIVATE KEY-----
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_1024_testpass.pub b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_1024_testpass.pub
new file mode 100644
index 0000000..4aded97
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_1024_testpass.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDGfj0Jmqj+CUb+WdFrlkRV49TJtNzvvMb/nX20zqgGm50cOIYrMzfFpSQN630pXeAidIgiV/PWAsipntQfSWPRG+RpB/wMKHVUNPJCJkjjRFEa56YxgAhgNwF511K13x4p2tEN0r6wsfw1nos9VoO8XDBAu3lellAgBdufyCt8vw== testuser
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_2048 b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_2048
new file mode 100644
index 0000000..a2d7d62
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_2048
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA1sFEWiIp8SVO8/sDhKJ67O7tQdtDwsqWi9Fm238tAuy26OH+
+ylireX/qVcndU8Yr0qYShcwloEUaeNe77VgffZa2ZIUee75u8u+WGCYjea4RQ1bZ
+tDcioWkxl+xYfVuuKaA8CQn47XUdyoA/5P3DpDhaJl8KevaYupJNHOo9Lt2E5dVT
+93OksZBOQ6E3nNlsefP/hnFByiczlde2GIXP2sWLoxsiVsbI+CLeGtxQxPubX9yu
+vWrl/nv/yERR5ZBOEVY5N2+1BdT7DvOIMg2q60FpJv6zZpQi7Ov1iONMVafytIRW
+Ma2rPkpS83Ebxh5c92T3rgLUf5DcZjKvBgxtpwIDAQABAoIBAHeDZv6iNKU3FhFB
+iFuv8Kka7n7P/43QIKf/CTbuN6aBBenkm18QqZ0cStUjWkDc8FZyhaxgSDBBRNIr
+fTJA8IV78lVOoABNooEgRG98ChIVhRXsp4tbg7JAUJEzvqtE8k/IFKETI61CmCmx
+5d0SPGaP1du02KhFxAlQkgmdch85st+tRFv5GZXqiKbR6QlNaJgIXIoOlykVvnz6
+rnl6Q1SDutBOKGC8xFrDzFI8KxLFe3RFQxtHtsLRPcrrpukNSHICTMO4jtGXSZ12
+9Zh29ZtkouCDk+b176dGrJKfIBbxXtBGVXtkuo7rj8EWVWrJiiYbL2hcWD+Pw1VL
+0GWkaEECgYEA+RrE4nVkfdJ0Zgx+sACQqs4uKi/JuFHU69JnO7RB2lDwzQbIPKl7
+nn0ExJ4V9m035/3mqKReBIyMIjIhwXgLFiakNO/+GAWa4ycRMB3pV8WaVFCnWZEF
+oLRg1ukoLs01TfOszcux831n8zmPlz/NLTTkC26O3WXsVmnCSlPXd1MCgYEA3LMW
+B8ONEDFYACB8xKA5zn3jrKq/yVFfiQzEO87zSkgG1mQbsb5T8jggWiIHVyZKQUSk
+8ZkrwBKW+LwyRik1lVwawALmcvvN3VyCW5BukniErAUu8jb3+R2aFdrjzpiNqzMF
+M18BPDElirTXMjJusC7z/0I7+gyAu9ttYJY1id0CgYBqjIiqVIwnRV2ESNPndFZs
+uMQGR2qA7H+mXtjJMND6EKTvDXeYeuXlZJQlhXjfbtf64x9GAwgz6eoGtmq51h7n
+2p9iBUUqATu+7Xbsnd6xLFRWvCjYpq9BjeXeBtypKB0kupWvcPEstPdBkd1ZVHDu
+ZTElsqRpDq+IRrRUFoiTAQKBgQCxTGmRWSa08H8asv6o03M9EONbrlyedXHDXu8y
+gQHwFcbwasHY2+cCetZ6skWlXIxgvK2prXx5NDX2ovHcbXSvhauzv2C01NdAUvYi
+avh5ULp8mzlouoIhrgdAMXW7XdDJzRYLe/I5Ed5v/PG4UM2dWksIMISQT4UH5bKL
+2oAuPQKBgCQaJ2oc5qE/f6MiL0XfGSdY26gOZcVrm9L1XKXtyHkfj4xWYQ58DSYa
+vNZH3fGyfR+q7g1WgUmLib5etOjUjbVYRjIEov8xLA41UZZLNGRLc4VzgeCT73CW
+YvbxeN93fL0tgvKeyNVzIsWRazHMo+aQodlXvpPckHXYxYHS93W+
+-----END RSA PRIVATE KEY-----
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_2048.pub b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_2048.pub
new file mode 100644
index 0000000..b787e364
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_2048.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDWwURaIinxJU7z+wOEonrs7u1B20PCypaL0Wbbfy0C7Lbo4f7KWKt5f+pVyd1TxivSphKFzCWgRRp417vtWB99lrZkhR57vm7y75YYJiN5rhFDVtm0NyKhaTGX7Fh9W64poDwJCfjtdR3KgD/k/cOkOFomXwp69pi6kk0c6j0u3YTl1VP3c6SxkE5DoTec2Wx58/+GcUHKJzOV17YYhc/axYujGyJWxsj4It4a3FDE+5tf3K69auX+e//IRFHlkE4RVjk3b7UF1PsO84gyDarrQWkm/rNmlCLs6/WI40xVp/K0hFYxras+SlLzcRvGHlz3ZPeuAtR/kNxmMq8GDG2n testuser
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_2048_testpass b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_2048_testpass
new file mode 100644
index 0000000..7b3a3f4
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_2048_testpass
@@ -0,0 +1,30 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,DED10D02EF74A02F24F46AB44A84F4B3
+
+DL0M2dNu7cXNLE6KGPqEt3pSKiQw6ajlxYaaXyyAwvpZB0Pv44HUjXfhMONs5FH5
+rDjz1RSYDRdMF/h6FtltdEEareXwMtRTvP2wb0gsQKiYS5M9WeebM3TY55JmwS1U
+hhrPrEaP6hs6WEy9xp9DVxJN2y1MA5iss7M+fQ4/C6QeSp9On6bgCEvNPwdMTS4A
+3sLp+yzRvrefQmSi2SWbJoYlChitOMdc84iDJXDo951QQLX75GqMm41fFLHHrcTO
+7v/k/D7p/KLlNf43Ru+2yPNE7qyEK0pDSjvPnjPykIa6SWq3Qx2DnVdtZ7bWF8LA
+B349QmuE1r/YYHNvWnp0/5SztivJk3NMeTT29PIiZoHioo53Vtru6RcXYMOvHbh1
+maioVkgRl5gkhLC86o4V+3hiJQNrVCWMuT+lxLY2Tt6bFXulbf3WH69AEAFW4S4a
+e7zH4fwvkSwz+bFxg9B+Yynv42ke1a+tvDI+aDvsMmv9JUCy6G4Te+isXYxLdtT0
+nyqJ+wwP53AWS8gOvoUXzxxsEchTDtQnBQMWuSHEdFrk3OLGykNN6vZaxUROxpJf
+vcPl7JniWGhzDzUdHh0AQbLxXoZlv4YU1uO/+1OnrvIkuO5DCDg8v2sTFRW6sgiU
+JXm3QPJiU/bu3/FJ4XCU75cTcunZMXsL7TY9mURq7Y5FxcByuvSL2nlA7KfROTVq
+I6w+Ej+r99C1u0G63sk5b99Pm4cb2+V/sr7pslqlU9Yw1Z5hw55ibih04CiWZAhJ
+Az7s8ho4dY9E1n/XJSe26p14RPYU+w7WZuN6Xb04t3+BhF4Ubbsdn6F3lAVOrrWH
+6xNoncmIEYdfdcI089UPpV4/bIpdakXRIbaLmpshyU6aIRUXqYkzwduXcHUrxgq3
+1QCZHNvq1+9i5Wqj8JP8cZrq9YVldOeXdIIsm1SSepbDQ7820d5T4Dk6cj85BXYC
+6t12UNZ5mhzTvIAqbR3Who53jQ8cY0MSVXR6Jd6vPih2OhAnccnuJmRCNNJkL4mg
+pVcsSgYjoUx+w6Ou1muCIkkGpdEhLLwEnKFc0HUmPBToRqgiB1Aec+7oMv62XhXe
+yA26/dpT6N6SWYKN7MyDWUe2ilkmjXI+JrPCH+/w4FXh+GKafOn8XlcBnRWHVBEX
+ZQfYLckd1j9B6p7By7ed2H+8FxZLz3gthcSxRG89IP/EQImY/e9A3aoLrFX6C/W0
+Gd6JrIvzC2bZCvrq+VTYs3101j1xe6ZDJnq68HokjpG8P9DlFYDOpRetCjR7TuqN
+I5s606KAsGkt/jfbSNUMIEtuM0AC75m3TTJeWdfYh/PVYevUC+pUoreJ0ZsttQ2i
+D550sAAzU7PCzZQsDF1i2jv/YZ0wXz7+C7YFiGNmb3HmXH0Lb2HISJR5UL+x+hHY
+RArXtVubqjFz179pawzI0n03Z1OXiHolwer7C+Twmarv7SPe8rMU3HcHP25JeTAW
+mo0PxNGG3yQPlRZWpPz8LEWGo+fDqfA4kbqy4+Pvo7B8YFIQyE9QG+oBv+/7uqMU
+UOs1ZqsmvEUmvWMeQnWsjETmHKucbmTBm8ktsesb3sCKfY/pf8hAHbO6+9J3ebYf
+-----END RSA PRIVATE KEY-----
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_2048_testpass.pub b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_2048_testpass.pub
new file mode 100644
index 0000000..b787e364
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_2048_testpass.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDWwURaIinxJU7z+wOEonrs7u1B20PCypaL0Wbbfy0C7Lbo4f7KWKt5f+pVyd1TxivSphKFzCWgRRp417vtWB99lrZkhR57vm7y75YYJiN5rhFDVtm0NyKhaTGX7Fh9W64poDwJCfjtdR3KgD/k/cOkOFomXwp69pi6kk0c6j0u3YTl1VP3c6SxkE5DoTec2Wx58/+GcUHKJzOV17YYhc/axYujGyJWxsj4It4a3FDE+5tf3K69auX+e//IRFHlkE4RVjk3b7UF1PsO84gyDarrQWkm/rNmlCLs6/WI40xVp/K0hFYxras+SlLzcRvGHlz3ZPeuAtR/kNxmMq8GDG2n testuser
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_3072 b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_3072
new file mode 100644
index 0000000..10d622c
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_3072
@@ -0,0 +1,39 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIG5AIBAAKCAYEA1M84bePnK6cR9Ei/H0S36QhdfUl0qUIZXrHNvS9i/npTZdN1
+mCzWxeNHm0YJQWpn9AqPZGG/dPGt3CQEL52TKXawY/0ks+4p0JJ9260oqVBFJrXE
+5latVQCdIZ1GR2iJL3kZLXHXSkURygEL9aBfOEUSmC4SkNY0LOGuwMZ2TyXiFWHL
+Y9le1DU2UMbfk65+6LgzU+FKzO4sg/zZD3oB9A+n+ozSZv/YEMuPvUAboMUJru/u
+c6D5UxhwJ6GSKNkSt3xJUKnsohkCbRAq/ansvVJqEsgZc+oKVFidLPPz9rLjoEl3
+w+cUlM0TbbXaqtFXCoE8S6CAJG/G5Rkfrw7bUUkjGbYrVqjR1W32dg3txzZMVojI
+zolB5LWtsbZY620b+hHk7Vh+F3Vw2yinGNrPDVnVMwB+pRCsPkWSvlLvpR4C7xqq
+iEucB1eFqwWSVhfDgzkvtTiaMJ7M7YunJ5pFWjNd0yLZNgIa5SESzrn564wwjcwB
+bMVifimMp3pvFBbrAgMBAAECggGBAIGcT8cGFiaNE69Pmy/FH6nLUX1b/rSTsHXv
+HtpJgSZyhFaxKp7rOEe//D3CsyJnVzbYM6s0qXHlPDmmqfICK74GLrpHVFJODKOe
+hQ8FcI1meSdxb6HGSr1JqWnuqv4U2fDS9ZWrDy+Jz6LTbmBEM5pG32NWNDKIc7Ce
+J1v7w9TCwua48DI5Ert2SUV7SnJcxaihf4ln3rHfobcliWIWshfebTV5DTB0RDk+
+caYW5HzPZO1p7jX4ZcHJUY2hpy4/vjwHLNLhXBV75bkZwXZJGaITD+uDAbQIAb8g
+T401x/+YZlNWP1kK1zu5Mo9cCl5o4o4oK3FyLKUoCXyYrahTfmbHgVc/toiJ0F91
+BMUWkunpz+B9GcKPUkBmu0xGTominFmz1ZW/etpufJcDt8B42kcoDoOsQMl2B2CT
+zW7Bo+R3hFD80I4tIAtlFiKuKwKlRBF+E405yTxjlUwefczshWSSeppPZfxFwSQc
+3Q07RB0MepIZ0w2RqVsG1rkq/GPaqQKBwQD/wAapoc02U034Id2ny3HALiV1u/io
+Ve9r7oq02oltrRa9bUydLF9jknInl2p64R3x19JwIgtBK/AwDb6KKnrVFfJhw80W
+TlyVvls0b0jmohhYcn/5EY/ROg3ex4eySJIvJYZDLDWNAToMWLr6STBFXQdaYHIf
+BleOsyO/ARoiKZdtJB5Foes/GFwdIo5tgJgfZXw3mBrcF+UVIhyd9VRotg2ltIHX
+UvbF0vanm+nN77g0dPAYz+p7IYQbguZANqUCgcEA1QRz/6KErIfjly00Dx3hMf+T
+YCTe2Z0IyDex9b15tsF1C/sFJ7P3HUel198Fon86Wmc6OCxhfAHyhoTYdpaCwgGp
+2rVRd6flkABW+4koi8Kr0qXOnjAe54LcXlrZgo7/iDUjEMjCXKOkirXW8L1Uqk2X
+nuXJq3Vp7iexBaZCRe7Q2kPcV03QXA3r5sph67SsjJWrEVSll7/XaL1RBiTDRXHZ
+1aQpnf0DQnvdHnnqrwewbrUxcEBPVq7faoWPizJPAoHBALdNbnkOWwLg2jVKMJAf
+JLxVVsv3mdUtIpj9M7VEHNPbBz1lpU/RidzYDbGKuOqxhsDbqxxrih1/3HrUnwhw
+QfGP9VVU/R1LtNguwzflux5yd3iNOGPPzoBrV52g7QU/NmdMQdrLSOZzRqOqxPi2
+lD5i2u5Pyfuqk/7XLnur0otBvCKhjIDj+LQURZEsP2EElgOKvWkrP7UX+z0WYeRk
+/ca/FTD7G0S1VeGbvuWKvhy4ABK47Y0bGDiAYStGurizcQKBwBTj0ehg7Lfqv6QE
+t9U/reT0VmSYWQ5oOwM/iwE5aqVEhZD+Nfw1xuclLptj8K6F4ZgaBXiayZiarEkK
+4BuJGRujhB/BplKgsX+UuPMD+WjzV1xaDFAxEebMS4YpTKlkEqUt6NlthroFBk7g
+FEsZliL5ZwQbLtLUueW0GMUgD+HB0NOG0iXxqJxOdTL15/Jwjnde+h7B+VdPZfWM
+k1SR6GB4EM/FwJsQw/ASK5YgiKZPj7rbpBSJCf7LOXe9z1zsOwKBwFZ3GdC9arW+
+AFvXk0TuF5xjq0WuTDmEJn8PI5HPAajyeNoAnp9xwUpMnklfT6uk5ZWKQUJszbtm
+IFaNUDXwOlE/S7Zf8FXQsoUz7koCs/IGKBBdRwK+Hh4e89Qme3nOU8I66DWxeohF
+t0zuJJaVCUdJdEW6HbOdS9/J/zzIPeL2kQU+lvD7FfmN0ynFcGi9M8O6dEl/2L/0
+FmI9bz1F+bExm39yFXnY4lsK/gTVdkjyeEK7T6Fg9PFCqxhqh0lyww==
+-----END RSA PRIVATE KEY-----
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_3072.pub b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_3072.pub
new file mode 100644
index 0000000..686d3b0
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_3072.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDUzzht4+crpxH0SL8fRLfpCF19SXSpQhlesc29L2L+elNl03WYLNbF40ebRglBamf0Co9kYb908a3cJAQvnZMpdrBj/SSz7inQkn3brSipUEUmtcTmVq1VAJ0hnUZHaIkveRktcddKRRHKAQv1oF84RRKYLhKQ1jQs4a7AxnZPJeIVYctj2V7UNTZQxt+Trn7ouDNT4UrM7iyD/NkPegH0D6f6jNJm/9gQy4+9QBugxQmu7+5zoPlTGHAnoZIo2RK3fElQqeyiGQJtECr9qey9UmoSyBlz6gpUWJ0s8/P2suOgSXfD5xSUzRNttdqq0VcKgTxLoIAkb8blGR+vDttRSSMZtitWqNHVbfZ2De3HNkxWiMjOiUHkta2xtljrbRv6EeTtWH4XdXDbKKcY2s8NWdUzAH6lEKw+RZK+Uu+lHgLvGqqIS5wHV4WrBZJWF8ODOS+1OJownszti6cnmkVaM13TItk2AhrlIRLOufnrjDCNzAFsxWJ+KYynem8UFus= testuser
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_3072_testpass b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_3072_testpass
new file mode 100644
index 0000000..353a24c
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_3072_testpass
@@ -0,0 +1,42 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,040847DD0487D72145EB88CB09486EB0
+
+2vC15lwRqJvaSU+yYCmqerJft8dqlrx9EK3gW4WtMW6C4ebqlj5DkIthSvJLgF6O
+wufFV0hgmEeOhLEIrdJc/FTeD6VsSBYHTttoMeQ0Yb0eETnLhSbFO+9NRvPBpT4/
+EsBozu1m/fnv14qbXtgiX9d3zRR5Il9Q/TP9/MO25QO0/7SLHn8ar255piZobBQ4
+xqW26UywI9pUMjcfgroE4PYZTqTPY8xGFBeOIXBGuw3m4geKcOMbiPehB2o7gZDJ
+iC2conFycbi0xUBYytnRO4BboB1PhFnh4CXFqAkJycWj20Q2iFVliEXEey+Qyd4m
+vu9Hr1sp+35kByS5uQ7UfDgBcoo25JKz3HIcqFrSzJ3cwRuRrj27eydojR12o4FI
+Cd06GTMq6khN3lovVUaQWlE1MLUpT9zT0rLzJylZ7fgHi3cTZ9n5Nr70vX8+pvFA
+mzQ/53nvXQkiKfyUWV1aVypNsl0kYEM9+6uLyknyUPmLDOGxwAz5bS2xp6J7BKku
+PojN6NHChyqndHArpR6EUx8RYCQV7PL0EPCSVlyiscetNBfTe9+BzCbPisorukQT
+EweviRMUmW/pdr4zPuMwfZQSzRGYZ+19sIfV/VsRvgYvTUqUZ4mvWQbyiGpeLoM9
+W/bAJrqJBgfMISw4n+j3oVd0HJULWxktZGD8grLsmeh3Yjk5TCXcv6dH5OGx66nR
+ATMjEinVcwop+z5RdlaP48Lw7/FfaWTiOln9O9DMT1pjbyO01qXHCKvo+TnSYryK
+SqqaomMm7vMQMytxPPZGuiSCKpaIWwfMLIzreFw2LdvzGEF3wX/SBW+8g30hwyfq
+YKrP3ZXe1g56oRqU8S2dB69rkap4nljj4HSXvIr/7XNQpkKlJX8yOAncGUcXfBaB
+kIytyAfX7Xfibk8uPnDFxL7JEmCMR78LP3jYLX1Icl7lLdbUFUfxb2WM6Fng5qyX
+Ffggcd7gucydjFNKR/KYlJVCIfxJTt9D1tGz9MT0sk7hSEIlIPieG2VkKEYKHbUj
+UHEwbPbeFxm9INyccBAdnCvqfJ5ppQKB9TrZliPeLclx52NlX+3gtBErneycyzOk
+oQmFtV+Bqg2hgH8TDLenGmG2xJsviuNTXeAjyZFLXkE1kFAPEKmz3Bys/tSJ+NTw
+mAQxRnZ14BmO11o+/3xrrA3FkxiZq6hVUOyUZ/rejkbMTXUb81kyJe5o0kgLnQ8p
+EJGi6tQl2z9YPQC9wWXO5ssu+Q+5MJ+H8YlvV6oc0nXUcLq9mgxPDPRBu33n79zq
+mKymh4jO5qTExqnC6lLOsw7YVsss91opBLPGO8nXtcRvtqiRGwI+2D9kUVHH4J9R
+dHXQaVXgUGxmhJFUxHEEckrT6NN923uY13R9Uw5Ifmh3XHob2hFQlbBP3GeiwfTI
+DlNxIEguXxuZddJD2Fg/vLn5KNzkCOlYcrvoa+eH2jzcLN94tLNjliOgX69eERdt
+qjz3x8Xoyh/bWcrdw7LZC7PtjwfLlkoubUVtOv++ZN4iR1XEjmEuyzibOUTQ8Ydz
+ZwUXchQKupTxEGgIJ6tl7NGXSjA/TT1KYQUgVil9Uv5zZbOZecFClFF+1Vcmuzgd
+hLzWG1DhZzvEAI3whQafUZf2BuyfYdnS2aKjVYR+k9dCTKAIz0MWOl29BC7/v1L8
+d7uqonqiVhwfHOjnUH0cD+QRM1i63+Luyo4c2WyCnQ7DFOfs5l+SwnQL1Lxu67F9
+7lGr2g0l721hBTaUKMETrTjNSz/OBURebumgMtr+45K5JCj8hJ2NFQUbmqkqhyf8
+f3niFJymhtywyUPafsodRbQhKMVg4TYVzQsRnpdsQ1IOFt3vcZnRNVuv0Y4bTXH0
+TjdwxAxtxtulvE6K7esXTQdElW+yH2Fkq2edHsxquf7PoMhBLV/myMPq+4inrLU0
+rr+Er/yYLZLdolld849WTtYdDB1GwcPQ6PmuBTpt5ccoFQDvK20U4uG2EswpVkoY
+YCWf9sUnGwZh9YE0h6Ag0IY13CeQL3dsiua0+xsVEOiAZ3Y6Mawb7W0VZPHo35Kh
+ettpfjDQUF3FA/J7hW0qa4soapbymbtlkOjdQMe3tOV28ElWe2ve/TmDvUtVVB8j
+y0vjRJtwkcONM3CUuOiJPHKFvKwUBAC+7VyvRy2lRPKYVZibIr98fyd6BXsP4tD1
+R9e+Me6Cq2UsC7ywii9DmkBqpSP8XBOMNdBzbDN9gPnQzGx8oXo2w3mZZlfJe9uK
+v09UMglCxrYBDw30MEfoF913crEofxrHRSzp17tFEB74M/r7OmeegSCD8Ud7twH1
+mpnZRlGanu2DQrEmhVpfJxjn7pHPmolJsQirFfVY6wCz5UQ7iXRV3LILnruVjpIZ
+-----END RSA PRIVATE KEY-----
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_3072_testpass.pub b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_3072_testpass.pub
new file mode 100644
index 0000000..686d3b0
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_3072_testpass.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDUzzht4+crpxH0SL8fRLfpCF19SXSpQhlesc29L2L+elNl03WYLNbF40ebRglBamf0Co9kYb908a3cJAQvnZMpdrBj/SSz7inQkn3brSipUEUmtcTmVq1VAJ0hnUZHaIkveRktcddKRRHKAQv1oF84RRKYLhKQ1jQs4a7AxnZPJeIVYctj2V7UNTZQxt+Trn7ouDNT4UrM7iyD/NkPegH0D6f6jNJm/9gQy4+9QBugxQmu7+5zoPlTGHAnoZIo2RK3fElQqeyiGQJtECr9qey9UmoSyBlz6gpUWJ0s8/P2suOgSXfD5xSUzRNttdqq0VcKgTxLoIAkb8blGR+vDttRSSMZtitWqNHVbfZ2De3HNkxWiMjOiUHkta2xtljrbRv6EeTtWH4XdXDbKKcY2s8NWdUzAH6lEKw+RZK+Uu+lHgLvGqqIS5wHV4WrBZJWF8ODOS+1OJownszti6cnmkVaM13TItk2AhrlIRLOufnrjDCNzAFsxWJ+KYynem8UFus= testuser
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_4096 b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_4096
new file mode 100644
index 0000000..1a10b38
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_4096
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKQIBAAKCAgEAwzbSXgR8dM/EU36T2lAKUoRlojKspPhKVfDt7N3prGAc2L6A
+P0y3G1HLLgKPK29S0Cydcqyl694ST+uu9qYzDLQlFQHbxIG76POmHXj92bF47lJU
+RNxi78hoEDnZWtDG0rsUCBD1I4z+tXjWV81pv3BqVg5ilR6uqNgv3RzXj2jL6Q+3
+zwXxeMw7jJ3Tuukhf50hlxblH4bBIOLuZyb4t8R4EyXmrAPupHaUZSiwbxaDrV+s
+gdu/7G8dnyB0dVL3AUNUEp7Wrh2PewnjgUcNQQmyJuB98wEP3+GrTsktixjIEmCd
+e/gfDsl5JxBzzbUFtlQ8JVOnn9JCQ2U37cRggsW3yojFxGCU+bJaXz0zSgPmfR/r
+T7oXgDKR76JZ2VSTGuAFrPcdRyErPg4PC6FtW0mNxn2S6RK28s6xpTDywEDu8ETh
+lKIXGnN9XDX26gYw56ZlmAaJQ6KQP/F0Akf8nRARzkPJtIa21iBrUHRXLF7YKnBw
+LyCUgTA3WSDgNdP9Ga7+6JC5gGPW5KGIKoK7SZY9LxNoV66iglp0nGEM27ZU1raw
+llwcJAzkbSaViD/vvrIiuz04s4+5K+rAhe8CU4UTBWUJgUvtTSV7d/SBfFLsQJI/
+W11n9+SCIbBCx9nON+xkMkMQVyrMPWoD+oYRx/wXGIO2qkkPeegGyb8oKYsCAwEA
+AQKCAgA7qPx/yUUz+e9ZSRzsonuVHmtlN7F1tYAaZciBFIz+pl0KjKXrMonfao76
+38NbleksQAZabpNC05qrHC9bqA1/+2o90lSU6MVB+3ywEzMZndiElVq1tNjzyT6s
+ftGDpLyu2IfVs0EH/WY2ldiD+v4viK6m4DyWsErWxUNTgyYJ6RAwiSI2ve0/asNk
+RTPZMriPJLmIUHHzwZ4ya8hKdCmdGAlOaM3nkkgTsT3G8LmDKdFSYiP5h+xO2OKn
+qCaPWKyukSIXkr2vds9L3gjOkKVnVAxDP2aepptwY6qUKH2nvgofO7HFml37ie1h
+1/BcVM+LGpFLIxbejEa+DCgcnWCU7VbWRSvU3TeV0uTdrGBhKSHLBMigyqtt4OTw
+QcWLd9zygDO02Jm9vlMO2D6WmI0medbgXPT+vwFBXvt6/Z2sNf2zW55qXn7yeFlu
+7/GiZFIlpH4jOw6U8uG6YV7YueXSaKmbeI9hSB4d8hrRqud0Ny7fu6m5+/GB8Q6q
+2cZ7mETvrNmISe4waD9xk4CP7NchM0LSU2RWP5VtZAHEM2iIYin27aI0GjdhEm8Q
+oc5fU+kGJdLiMZ7IaCp2tZZ16PLjtWXqdbCgqjmdp8jwtwLuMil9XAFHm22jbrnP
+/bFCnlNLcknH/csS0jVxZI+nunS9UgMZVCudvJ8lzY9LDlFUcQKCAQEA+b5tSOfC
+EVdVY5+9zvx2glvQRxqN/5fonMTZXK1qqVNcbxb5tQ9I5uBQCykg7HJ30ukgK00+
+qbGCc64l1XNu2dFFXKJbSOV/8Ts5vzfmgdwZoC+W4IwojRQmfyKCwfIsP8IwrBSp
+IlcO7LMkHCnlmRPPMSPeQ1NB/N3mnilz0I5KfihahziKccCTGBvpESD945qWqCrL
+ynHmuEyb1zvwU8Z4psrfiP/RosFjItVJpsQzeVS3CGrTFe0b4PzrIQo12wPXhUX+
+um2WMQYoBVZzcrRSIH31RY9PJ3avbPJC8RqGBAZov0Zv5KvpZcL1EeDfBn8leld/
+eCpqIheDiOdewwKCAQEAyBq0DF6Qhz5Rl7CJ7BxmaN/CbW4aHw9m9dfpNVqqi36Z
+ODfpb0sl40QnRLeWByfDj6BdhTBc3XXcIDVBjsstnnX1IAc3PZgzaONrmDaoIUfi
+GIROql5l86tMSjuW53eGze713z86GhvUv19r748asaKTepXgssaY7ppXCZ42dKt7
+0euXYyJSirMmO+A98wOtqamKf0X2FK/ZB7CyfhLFskHEVO2noojvZiJwAyz8zvm/
+GpOArbRTjEfg2Sqxk27CATVIVjVc4LBzsZc9mzLKVb+Cs/sZa72gy+gLmIM4ItID
++FPW8NbeZmVngiARJcIL4alxXXy+p/uXBILxhuLtmQKCAQEAzzlF3seGzPLFRGuo
+iBYNk27xa/5JsrnuZh4kKXUvWp5zxS2wNp8fI4sef5Q54Fe+uv97FNL8WruSfcAT
+XoBwi0XMoueIjPz440X8TYDpv/jMPpEeROWnRCBjLPyKuLjkJGdSEYb3LCpGlPqz
+zLaq7xBzy9dyNjTgPRw2nifRFEzs3K9JJogwv+BFbSzDf9X7NJ7xwUn5XNqT0Xqn
+mLkAWdMGC4esYTW7UavbQWzutvR3rYYwdUiGK9xZVJ8nznt1YmxWqRwCF9iUVctA
+6+Tm2FdtCc7Z9ETMLfeZ6fE+wGX8q1xSD9w3PeuzNx/ET3hiNjbL9y6g8ylmdTFD
+kBZDFwKCAQA2by0zgDYI1GcVwKyEUmV5egVGB4GLmYEEt6t1HCjwsYu0w2D5KZQw
+8sVL6DUj1SlZ1OIb7UAV7o3nJRWkZpkOVkBMaioY02KI0fTe/19VTlyvFq7fobZS
+RvMF7pfqd5VwR+USyfxgRdnmBWszS9aTJArCeisZ9vR7U/kBYMyniE6ymEgia5/Q
+o1NvTl0L0qBXWwuV+84pany7ntGvgiPNjh5+i/fiOyYEvrGB66cKFt5puF504m0n
+6BW+feK4nJSiB4CaEwIlDVsroFzd7z8jfGlt1IzhxkALuCAPaQLIViFGWGhMM+dk
+K4mw2FBR2SuqQ5HXQKwMvmAilgxmCS1hAoIBAQCykRU4k5qTxoNWfkYz9oYxsLUt
+FnyBoLxAzGrzM7F3fImVjetXoCow2xRxHnsD4dns7OdE3VbuJrbUDFdvzkEHBT/i
+MFJpaF/zrdnKA4hlQ3omccq+y0n1wLcG5LoHMoKoQQNHPO6G+Wf4uA4M9+p0ImH7
+ajEf/Rs+PC3cqKuvJdoFpSOseFNwAo5Vbc6N9nVgFfuaZ95puKgq9BzdCJnpK0Ss
+J1K4VmpE98jBMYiEAAVPBdLA01nBiAY+Nwdkh4VjAJ46E++5pofTm4xvYljxIoMl
+v7FbW0X6S4azOtIrGJ6EC2mziz07PA2Ad1zf7yPWilMfxC8mNIbS1pAmcVoy
+-----END RSA PRIVATE KEY-----
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_4096.pub b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_4096.pub
new file mode 100644
index 0000000..3c3c16f
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_4096.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDDNtJeBHx0z8RTfpPaUApShGWiMqyk+EpV8O3s3emsYBzYvoA/TLcbUcsuAo8rb1LQLJ1yrKXr3hJP6672pjMMtCUVAdvEgbvo86YdeP3ZsXjuUlRE3GLvyGgQOdla0MbSuxQIEPUjjP61eNZXzWm/cGpWDmKVHq6o2C/dHNePaMvpD7fPBfF4zDuMndO66SF/nSGXFuUfhsEg4u5nJvi3xHgTJeasA+6kdpRlKLBvFoOtX6yB27/sbx2fIHR1UvcBQ1QSntauHY97CeOBRw1BCbIm4H3zAQ/f4atOyS2LGMgSYJ17+B8OyXknEHPNtQW2VDwlU6ef0kJDZTftxGCCxbfKiMXEYJT5slpfPTNKA+Z9H+tPuheAMpHvolnZVJMa4AWs9x1HISs+Dg8LoW1bSY3GfZLpErbyzrGlMPLAQO7wROGUohcac31cNfbqBjDnpmWYBolDopA/8XQCR/ydEBHOQ8m0hrbWIGtQdFcsXtgqcHAvIJSBMDdZIOA10/0Zrv7okLmAY9bkoYgqgrtJlj0vE2hXrqKCWnScYQzbtlTWtrCWXBwkDORtJpWIP+++siK7PTizj7kr6sCF7wJThRMFZQmBS+1NJXt39IF8UuxAkj9bXWf35IIhsELH2c437GQyQxBXKsw9agP6hhHH/BcYg7aqSQ956AbJvygpiw== testuser
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_4096_testpass b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_4096_testpass
new file mode 100644
index 0000000..96e29fc
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_4096_testpass
@@ -0,0 +1,54 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,1EFAFB79DD5E78C98C5A2204D6747AF8
+
+p+WHiqnR+5M7mTVZH2xYA6TcpD5824tU0qCgcU0VdUx9Ikb4Mq7X9Y2by2jTXpDP
+9TN/XcUoaiEm/lAG+RESwFIFjMDe3kbWqv6IFw2GAsvwzeQ3HTjqke1MSpmcoRwA
+vUgHXMl1wK/SQaJIrr0P7aiSt02Zu/hWCZg19rZLLYREC27oLFhgpVsB1HsNzmvt
+au3RaPAkiZ78RpTz5ynSWawTUEqXuL0ctaivvmCnIoThy72gw5RQqw0GmkGEv/lT
+uWZHxqXj+dZggeOvq8G3xNS+eoub/OFrH5t4+5zJB9P8f28vwlsGCYe25dH0oH+K
+2Mhhnp4RNjsJ+YaqkTVjpJrMddz0WUgFWFzmD3b59DIDxWigmKIH6sCjlkMkCvVC
+djS6B+D5HE7dtWm12u38hZ6I1dgz6W+dtlpqZvt7j/opHNYeyAlaY1yEL2HiEoF9
+hI4FdxxXC332FdOP/FS/q+nuTj4wqvO6QsVG6V2nEhIKe7tLEiKmlBf9rAVqTEZp
+rWURoDDfUZPwGe38AloFpMr3k+NR1k0CmG9j9L6aw5bugS1Yqb/6oX3e/d5AQkJK
+XmhfsGUTShNEF5WthotgPGoBKF2astUAF0p50GB9lfuzlBZVvt6hVIecQDUO6/G7
+MT68JbRk2kHw2U0K9+3T2y8PpvHurE8jcH1kkSy0bKW+h0CTK17869keLSLH4+2D
+3gk6xrEEUFb+qLGTTfIbWCLxbCUJP5FGZHsQiTmecGECP4qYNlaedAiI76wxJGG3
+UrMi8kkae5PeujFDVo1CsRXAQoeBAzVkuVU93acCPm62hm8Z3wBJafEIWwEQmXRQ
+Zuk443OkjT4eB3U1RJSoglDaFBvj3eq9CthXZBDZPWFD21gXa4r3MW84aRBX3FPc
+FVrLqAbEcoULomvQz/lKJ4Q2i6jHloHioz/X4OgyrkkYXqst5UuXB8hE7jI48i4e
+mlOxQ0ORyXEwhXS6CnT0zGYlyrevipqI0ch0QSW4391dDVG+ud6PTaft9kc3zDpK
+CDONQYlN2GNQ91KxUDYKcPtH5wDjsSUPYYfsPBL10+yhhJLQ3S9lKsnNOnRvtTa5
+EORCFcDkDi18pR4rXz2qQhdrv5slWiWrB76d/1bhUo3hFnbSHDbl1jOO/e/OJ+wP
+cb/bfIH6iua4X3EVrVK0hm22SaoarhXi4XLdPiIUTVrEiSqDKF3XOE5uq+kGzfWc
+YaToLAOTFuwBYjIfgnhu/CrrrPganMFQrKOxjnR5q12xYmkneRc8xc5XYab9jVG2
+vdYh3yNl9/bwbguPmYZkwh3POrSiUfMnhTr/s6umNMjvnacab1c35hJUGssYZ7kV
+20a1jjTvYzH+RFhzPZpRUwiCcYKTQneta54h4eVCOOE1wdhWxeBv8MwtXijvf8Mq
+0+wpbCuW46/jO0F+oHEunTppXGgFKiwiKlElcMqrCpgVaFGgmyHDrE0Kgi+up9hv
+a5UG//0uRAvBgZAsffX9KbbkJLrZsv/YXqvlN5xhFolUNjtUndxLgRrEe6Z4r5EL
+FAjkH0ex1/Yvb3WromGbfAQRRzLqDKGqdAO6OgYeIW5q13QO1UwrPPPFHdXTDx64
+/8t5YC2ctJ/PAS6QMPFpHl3CrybkO7mvugQYaEG0vxV1whXb1uFe1OGILDUsGR/E
+XCz0D9xTNojphOK1zRof0Qg4FPIZGI90SZLGJTNZnwN52b/ig839B4MIlT6nwUCr
+42yBCbI/k1QYm4Gb3zxDxBZwlOkQjU9LSv5lsmW/ObRsPmnK2pAjmT8n5O7wyXnR
+I3LuIWB2ssxySbvqzRAx2WC6fo4PBXpAKRgM5ZTH9NwFACyR84AC0ijw5UAGztXe
+WUAxx4l2aUYRasKQsQ9IS1wDmUE+q9zhCiv/toyDMwTENW3iFMoWFnaZVGWNAnlA
+YTjrix/SPwA9ybYIxRbh+FpP/aEWyp7OGDk9hQQvDLUkzwNJnfAycV8jq5OETid3
+3l+xzpGe414S5xAMMr3KDZnwVNbIkoYDAmtjIrfemnB0NuT1lDZ0eRZZXpFQPUAv
+U9y3p/5VRU7Ihe7TWjOrs9WGF2yBt5pcC8WbNDu8WMs3wtA8e+DBZHIJnHa/UsSu
+HTIKAXrrB4fmchumVwQT3Fdd8ZgJVvlgAcGNmko6fPVbM+CgwJ1iVwzsNKinF9xT
+J53twma7cpAYpwqSLMENZle9Wc2RPzv/mb4brud38csgrwQ28xfkcntjcT+Jykgw
+2ae5zlaP/R1a2sYbbT/ta0PncdfBuYuRbGZSNBQKKbe2+0BDqSvFSJGNB0beQ/xE
+daxg7Q6nZdeWksmIUZB5BHC1WDmfmk9N6M+pl0+7YbH1pUMqg61JE2QTCMzfQAoC
+v4jQ4o703KdvMRcnjQQCqab/Ihoeq5HUmXRCy3za6Vpxxp6mJpIK/6OWGn2UU/6w
+saujG7F2ewBBBGReg8pgUZODayAX+TBu8+5JCKeAD+u707KABaeBEyGa8bp3AZZu
+onzQ2tMmylusmmC/GiJO5UnousOovogl8HtsANdP16A/U6222kuQ5aahAnGTHLpF
+2EwMWDo6SWN5bBDlUQq0IA9WIMGvGFaID1rDwNKw4ZOLdVhGlXhZxq8FoVZVozrX
+khyiiK1UAp9/BpeXTzqJm+aUQNJu3J28LcRMNgmrdWTjzA9X7s/7mFQfYauLehz+
+Jf/RwSca4EXTFkvmhauhzwnPhdBqCSncCJmNi2I0OeJRFsOerajicxvHW6AUDkiD
+7SCDSTvOBEl20cZwdk/WJ7n+ID5QwWDxV+KzB6dXoMPFZXggat5qA+e0JMEbWxJD
+be0HwuiHyK3lLpBMc7Vv7KzZOtH1JscVT9n1Yd184CphTyi0gexcdwa5T0WmVpyi
+ze1zT8pbTOOHWCvJkdqmxKfHp9GutAtviEloNoK4YbRUJUM4uCF81p7vOYNK6vot
+bGvqXtQ7QvTkyKA/Ue4uSQCG1dLaedZQPSIVGFrqMrAFoUxDWFN8NcPiMkETrGFE
+l9psgia1ktvdFdUOgMjpy7xNBodRedSHMcsyVHjfhGxdxPGW9sG8N1DWxTeArGpX
+nkXs+RaZmMWijknT1dZdNt3XZ7+cGm04NG6JfjxY/kvWcuDKAnhCWmNUnQzLEbZF
+-----END RSA PRIVATE KEY-----
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_4096_testpass.pub b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_4096_testpass.pub
new file mode 100644
index 0000000..3c3c16f
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_rsa_4096_testpass.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDDNtJeBHx0z8RTfpPaUApShGWiMqyk+EpV8O3s3emsYBzYvoA/TLcbUcsuAo8rb1LQLJ1yrKXr3hJP6672pjMMtCUVAdvEgbvo86YdeP3ZsXjuUlRE3GLvyGgQOdla0MbSuxQIEPUjjP61eNZXzWm/cGpWDmKVHq6o2C/dHNePaMvpD7fPBfF4zDuMndO66SF/nSGXFuUfhsEg4u5nJvi3xHgTJeasA+6kdpRlKLBvFoOtX6yB27/sbx2fIHR1UvcBQ1QSntauHY97CeOBRw1BCbIm4H3zAQ/f4atOyS2LGMgSYJ17+B8OyXknEHPNtQW2VDwlU6ef0kJDZTftxGCCxbfKiMXEYJT5slpfPTNKA+Z9H+tPuheAMpHvolnZVJMa4AWs9x1HISs+Dg8LoW1bSY3GfZLpErbyzrGlMPLAQO7wROGUohcac31cNfbqBjDnpmWYBolDopA/8XQCR/ydEBHOQ8m0hrbWIGtQdFcsXtgqcHAvIJSBMDdZIOA10/0Zrv7okLmAY9bkoYgqgrtJlj0vE2hXrqKCWnScYQzbtlTWtrCWXBwkDORtJpWIP+++siK7PTizj7kr6sCF7wJThRMFZQmBS+1NJXt39IF8UuxAkj9bXWf35IIhsELH2c437GQyQxBXKsw9agP6hhHH/BcYg7aqSQ956AbJvygpiw== testuser
diff --git a/org.eclipse.jgit.test/src/org/eclipse/jgit/transport/ssh/SshTestBase.java b/org.eclipse.jgit.test/src/org/eclipse/jgit/transport/ssh/SshTestBase.java
new file mode 100644
index 0000000..dde55b6
--- /dev/null
+++ b/org.eclipse.jgit.test/src/org/eclipse/jgit/transport/ssh/SshTestBase.java
@@ -0,0 +1,844 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.transport.ssh;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.List;
+import java.util.Locale;
+
+import org.eclipse.jgit.api.errors.TransportException;
+import org.eclipse.jgit.transport.CredentialItem;
+import org.eclipse.jgit.transport.JschConfigSessionFactory;
+import org.junit.Test;
+import org.junit.experimental.theories.DataPoints;
+import org.junit.experimental.theories.Theory;
+
+/**
+ * The ssh tests. Concrete subclasses can re-use these tests by implementing the
+ * abstract operations from {@link SshTestHarness}. This gives a way to test
+ * different ssh clients against a unified test suite.
+ */
+public abstract class SshTestBase extends SshTestHarness {
+
+	@DataPoints
+	public static String[] KEY_RESOURCES = { //
+			"id_dsa", //
+			"id_rsa_1024", //
+			"id_rsa_2048", //
+			"id_rsa_3072", //
+			"id_rsa_4096", //
+			"id_ecdsa_256", //
+			"id_ecdsa_384", //
+			"id_ecdsa_521", //
+			"id_ed25519", //
+			// And now encrypted. Passphrase is "testpass".
+			"id_dsa_testpass", //
+			"id_rsa_1024_testpass", //
+			"id_rsa_2048_testpass", //
+			"id_rsa_3072_testpass", //
+			"id_rsa_4096_testpass", //
+			"id_ecdsa_256_testpass", //
+			"id_ecdsa_384_testpass", //
+			"id_ecdsa_521_testpass" };
+
+	protected File defaultCloneDir;
+
+	@Override
+	public void setUp() throws Exception {
+		super.setUp();
+		defaultCloneDir = new File(getTemporaryDirectory(), "cloned");
+	}
+
+	@Test(expected = TransportException.class)
+	public void testSshWithoutConfig() throws Exception {
+		cloneWith("ssh://" + TEST_USER + "@localhost:" + testPort
+				+ "/doesntmatter", defaultCloneDir, null);
+	}
+
+	@Test
+	public void testSshWithGlobalIdentity() throws Exception {
+		cloneWith(
+				"ssh://" + TEST_USER + "@localhost:" + testPort
+						+ "/doesntmatter",
+				defaultCloneDir, null,
+				"IdentityFile " + privateKey1.getAbsolutePath());
+	}
+
+	@Test
+	public void testSshWithDefaultIdentity() throws Exception {
+		File idRsa = new File(privateKey1.getParentFile(), "id_rsa");
+		Files.copy(privateKey1.toPath(), idRsa.toPath());
+		// We expect the session factory to pick up these keys...
+		cloneWith("ssh://" + TEST_USER + "@localhost:" + testPort
+				+ "/doesntmatter", defaultCloneDir, null);
+	}
+
+	@Test
+	public void testSshWithConfig() throws Exception {
+		cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, //
+				"Host localhost", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"IdentityFile " + privateKey1.getAbsolutePath());
+	}
+
+	@Test
+	public void testSshWithConfigEncryptedUnusedKey() throws Exception {
+		// Copy the encrypted test key from the bundle.
+		File encryptedKey = new File(sshDir, "id_dsa");
+		copyTestResource("id_dsa_testpass", encryptedKey);
+		TestCredentialsProvider provider = new TestCredentialsProvider(
+				"testpass");
+		cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, //
+				"Host localhost", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"IdentityFile " + privateKey1.getAbsolutePath());
+		assertEquals("CredentialsProvider should not have been called", 0,
+				provider.getLog().size());
+	}
+
+	@Test
+	public void testSshWithConfigEncryptedUnusedKeyInConfigLast()
+			throws Exception {
+		// Copy the encrypted test key from the bundle.
+		File encryptedKey = new File(sshDir, "id_dsa_test_key");
+		copyTestResource("id_dsa_testpass", encryptedKey);
+		TestCredentialsProvider provider = new TestCredentialsProvider(
+				"testpass");
+		cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, //
+				"Host localhost", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"IdentityFile " + privateKey1.getAbsolutePath(),
+				"IdentityFile " + encryptedKey.getAbsolutePath());
+		// This test passes with JSch per chance because JSch completely ignores
+		// the second IdentityFile
+		assertEquals("CredentialsProvider should not have been called", 0,
+				provider.getLog().size());
+	}
+
+	@Test
+	public void testSshWithConfigEncryptedUnusedKeyInConfigFirst()
+			throws Exception {
+		// Test cannot pass with JSch; it handles only one IdentityFile.
+		// assumeTrue(!(getSessionFactory() instanceof
+		// JschConfigSessionFactory)); gives in bazel a failure with "Never
+		// found parameters that satisfied method assumptions."
+		// In maven it's fine!?
+		if (getSessionFactory() instanceof JschConfigSessionFactory) {
+			return;
+		}
+		// Copy the encrypted test key from the bundle.
+		File encryptedKey = new File(sshDir, "id_dsa_test_key");
+		copyTestResource("id_dsa_testpass", encryptedKey);
+		TestCredentialsProvider provider = new TestCredentialsProvider(
+				"testpass");
+		cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, //
+				"Host localhost", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"IdentityFile " + encryptedKey.getAbsolutePath(),
+				"IdentityFile " + privateKey1.getAbsolutePath());
+		assertEquals("CredentialsProvider should have been called once", 1,
+				provider.getLog().size());
+	}
+
+	@Test
+	public void testSshEncryptedUsedKeyCached() throws Exception {
+		// Make sure we are asked for the password only once if we do several
+		// operations with an encrypted key.
+		File encryptedKey = new File(sshDir, "id_dsa_test_key");
+		copyTestResource("id_dsa_testpass", encryptedKey);
+		File encryptedPublicKey = new File(sshDir, "id_dsa_test_key.pub");
+		copyTestResource("id_dsa_testpass.pub", encryptedPublicKey);
+		server.setTestUserPublicKey(encryptedPublicKey.toPath());
+		TestCredentialsProvider provider = new TestCredentialsProvider(
+				"testpass");
+		pushTo(provider,
+				cloneWith("ssh://localhost/doesntmatter", //
+						defaultCloneDir, provider, //
+						"Host localhost", //
+						"HostName localhost", //
+						"Port " + testPort, //
+						"User " + TEST_USER, //
+						"IdentityFile " + encryptedKey.getAbsolutePath()));
+		assertEquals("CredentialsProvider should have been called once", 1,
+				provider.getLog().size());
+	}
+
+	@Test(expected = TransportException.class)
+	public void testSshEncryptedUsedKeyWrongPassword() throws Exception {
+		File encryptedKey = new File(sshDir, "id_dsa_test_key");
+		copyTestResource("id_dsa_testpass", encryptedKey);
+		File encryptedPublicKey = new File(sshDir, "id_dsa_test_key.pub");
+		copyTestResource("id_dsa_testpass.pub", encryptedPublicKey);
+		server.setTestUserPublicKey(encryptedPublicKey.toPath());
+		TestCredentialsProvider provider = new TestCredentialsProvider(
+				"wrongpass");
+		cloneWith("ssh://localhost/doesntmatter", //
+				defaultCloneDir, provider, //
+				"Host localhost", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"NumberOfPasswordPrompts 1", //
+				"IdentityFile " + encryptedKey.getAbsolutePath());
+	}
+
+	@Test
+	public void testSshEncryptedUsedKeySeveralPassword() throws Exception {
+		File encryptedKey = new File(sshDir, "id_dsa_test_key");
+		copyTestResource("id_dsa_testpass", encryptedKey);
+		File encryptedPublicKey = new File(sshDir, "id_dsa_test_key.pub");
+		copyTestResource("id_dsa_testpass.pub", encryptedPublicKey);
+		server.setTestUserPublicKey(encryptedPublicKey.toPath());
+		TestCredentialsProvider provider = new TestCredentialsProvider(
+				"wrongpass", "wrongpass2", "testpass");
+		cloneWith("ssh://localhost/doesntmatter", //
+				defaultCloneDir, provider, //
+				"Host localhost", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"IdentityFile " + encryptedKey.getAbsolutePath());
+		assertEquals("CredentialsProvider should have been called 3 times", 3,
+				provider.getLog().size());
+	}
+
+	@Test(expected = TransportException.class)
+	public void testSshWithoutKnownHosts() throws Exception {
+		assertTrue("Could not delete known_hosts", knownHosts.delete());
+		cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, //
+				"Host localhost", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"IdentityFile " + privateKey1.getAbsolutePath());
+	}
+
+	@Test
+	public void testSshWithoutKnownHostsWithProviderAsk()
+			throws Exception {
+		File copiedHosts = new File(knownHosts.getParentFile(),
+				"copiedKnownHosts");
+		assertTrue("Failed to rename known_hosts",
+				knownHosts.renameTo(copiedHosts));
+		// The provider will answer "yes" to all questions, so we should be able
+		// to connect and end up with a new known_hosts file with the host key.
+		TestCredentialsProvider provider = new TestCredentialsProvider();
+		cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, //
+				"Host localhost", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"IdentityFile " + privateKey1.getAbsolutePath());
+		List<LogEntry> messages = provider.getLog();
+		assertFalse("Expected user interaction", messages.isEmpty());
+		if (getSessionFactory() instanceof JschConfigSessionFactory) {
+			// JSch doesn't create a non-existing file.
+			assertEquals("Expected to be asked about the key", 1,
+					messages.size());
+			return;
+		}
+		assertEquals(
+				"Expected to be asked about the key, and the file creation",
+				2, messages.size());
+		assertTrue("~/.ssh/known_hosts should exist now", knownHosts.exists());
+		// Instead of checking the file contents, let's just clone again
+		// without provider. If it works, the server host key was written
+		// correctly.
+		File clonedAgain = new File(getTemporaryDirectory(), "cloned2");
+		cloneWith("ssh://localhost/doesntmatter", clonedAgain, provider, //
+				"Host localhost", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"IdentityFile " + privateKey1.getAbsolutePath());
+	}
+
+	@Test
+	public void testSshWithoutKnownHostsWithProviderAcceptNew()
+			throws Exception {
+		File copiedHosts = new File(knownHosts.getParentFile(),
+				"copiedKnownHosts");
+		assertTrue("Failed to rename known_hosts",
+				knownHosts.renameTo(copiedHosts));
+		TestCredentialsProvider provider = new TestCredentialsProvider();
+		cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, //
+				"Host localhost", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"StrictHostKeyChecking accept-new", //
+				"IdentityFile " + privateKey1.getAbsolutePath());
+		if (getSessionFactory() instanceof JschConfigSessionFactory) {
+			// JSch doesn't create new files.
+			assertTrue("CredentialsProvider not called",
+					provider.getLog().isEmpty());
+			return;
+		}
+		assertEquals("Expected to be asked about the file creation", 1,
+				provider.getLog().size());
+		assertTrue("~/.ssh/known_hosts should exist now", knownHosts.exists());
+		// Instead of checking the file contents, let's just clone again
+		// without provider. If it works, the server host key was written
+		// correctly.
+		File clonedAgain = new File(getTemporaryDirectory(), "cloned2");
+		cloneWith("ssh://localhost/doesntmatter", clonedAgain, null, //
+				"Host localhost", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"IdentityFile " + privateKey1.getAbsolutePath());
+	}
+
+	@Test(expected = TransportException.class)
+	public void testSshWithoutKnownHostsDeny() throws Exception {
+		File copiedHosts = new File(knownHosts.getParentFile(),
+				"copiedKnownHosts");
+		assertTrue("Failed to rename known_hosts",
+				knownHosts.renameTo(copiedHosts));
+		cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, //
+				"Host localhost", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"StrictHostKeyChecking yes", //
+				"IdentityFile " + privateKey1.getAbsolutePath());
+	}
+
+	@Test(expected = TransportException.class)
+	public void testSshModifiedHostKeyDeny()
+			throws Exception {
+		File copiedHosts = new File(knownHosts.getParentFile(),
+				"copiedKnownHosts");
+		assertTrue("Failed to rename known_hosts",
+				knownHosts.renameTo(copiedHosts));
+		// Now produce a new known_hosts file containing some other key.
+		createKnownHostsFile(knownHosts, "localhost", testPort, publicKey1);
+		cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, //
+				"Host localhost", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"StrictHostKeyChecking yes", //
+				"IdentityFile " + privateKey1.getAbsolutePath());
+	}
+
+	@Test(expected = TransportException.class)
+	public void testSshModifiedHostKeyWithProviderDeny() throws Exception {
+		File copiedHosts = new File(knownHosts.getParentFile(),
+				"copiedKnownHosts");
+		assertTrue("Failed to rename known_hosts",
+				knownHosts.renameTo(copiedHosts));
+		// Now produce a new known_hosts file containing some other key.
+		createKnownHostsFile(knownHosts, "localhost", testPort, publicKey1);
+		TestCredentialsProvider provider = new TestCredentialsProvider();
+		try {
+			cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, //
+					"Host localhost", //
+					"HostName localhost", //
+					"Port " + testPort, //
+					"User " + TEST_USER, //
+					"StrictHostKeyChecking yes", //
+					"IdentityFile " + privateKey1.getAbsolutePath());
+		} catch (Exception e) {
+			assertEquals("Expected to be told about the modified key", 1,
+					provider.getLog().size());
+			assertTrue("Only messages expected", provider.getLog().stream()
+					.flatMap(l -> l.getItems().stream()).allMatch(
+							c -> c instanceof CredentialItem.InformationalMessage));
+			throw e;
+		}
+	}
+
+	private void checkKnownHostsModifiedHostKey(File backup, File newFile,
+			String wrongKey) throws IOException {
+		List<String> oldLines = Files.readAllLines(backup.toPath(),
+				StandardCharsets.UTF_8);
+		// Find the original entry. We should have that again in known_hosts.
+		String oldKeyPart = null;
+		for (String oldLine : oldLines) {
+			if (oldLine.contains("[localhost]:")) {
+				String[] parts = oldLine.split("\\s+");
+				if (parts.length > 2) {
+					oldKeyPart = parts[parts.length - 2] + ' '
+							+ parts[parts.length - 1];
+					break;
+				}
+			}
+		}
+		assertNotNull("Old key not found", oldKeyPart);
+		List<String> newLines = Files.readAllLines(newFile.toPath(),
+				StandardCharsets.UTF_8);
+		assertFalse("Old host key still found in known_hosts file" + newFile,
+				hasHostKey("localhost", testPort, wrongKey, newLines));
+		assertTrue("New host key not found in known_hosts file" + newFile,
+				hasHostKey("localhost", testPort, oldKeyPart, newLines));
+
+	}
+
+	@Test
+	public void testSshModifiedHostKeyAllow() throws Exception {
+		assertTrue("Failed to delete known_hosts", knownHosts.delete());
+		createKnownHostsFile(knownHosts, "localhost", testPort, publicKey1);
+		File backup = new File(getTemporaryDirectory(), "backupKnownHosts");
+		Files.copy(knownHosts.toPath(), backup.toPath());
+		cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, //
+				"Host localhost", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"StrictHostKeyChecking no", //
+				"IdentityFile " + privateKey1.getAbsolutePath());
+		// File should not have been updated!
+		String[] oldLines = Files
+				.readAllLines(backup.toPath(), StandardCharsets.UTF_8)
+				.toArray(new String[0]);
+		String[] newLines = Files
+				.readAllLines(knownHosts.toPath(), StandardCharsets.UTF_8)
+				.toArray(new String[0]);
+		assertArrayEquals("Known hosts file should not be modified", oldLines,
+				newLines);
+	}
+
+	@Test
+	public void testSshModifiedHostKeyAsk() throws Exception {
+		File copiedHosts = new File(knownHosts.getParentFile(),
+				"copiedKnownHosts");
+		assertTrue("Failed to rename known_hosts",
+				knownHosts.renameTo(copiedHosts));
+		String wrongKeyPart = createKnownHostsFile(knownHosts, "localhost",
+				testPort, publicKey1);
+		TestCredentialsProvider provider = new TestCredentialsProvider();
+		cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, //
+				"Host localhost", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"IdentityFile " + privateKey1.getAbsolutePath());
+		checkKnownHostsModifiedHostKey(copiedHosts, knownHosts, wrongKeyPart);
+		assertEquals("Expected to be asked about the modified key", 1,
+				provider.getLog().size());
+	}
+
+	@Test
+	public void testSshCloneWithConfigAndPush() throws Exception {
+		pushTo(cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, //
+				"Host localhost", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"IdentityFile " + privateKey1.getAbsolutePath()));
+	}
+
+	@Test
+	public void testSftpWithConfig() throws Exception {
+		cloneWith("sftp://localhost/.git", defaultCloneDir, null, //
+				"Host localhost", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"IdentityFile " + privateKey1.getAbsolutePath());
+	}
+
+	@Test
+	public void testSftpCloneWithConfigAndPush() throws Exception {
+		pushTo(cloneWith("sftp://localhost/.git", defaultCloneDir, null, //
+				"Host localhost", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"IdentityFile " + privateKey1.getAbsolutePath()));
+	}
+
+	@Test(expected = TransportException.class)
+	public void testSshWithConfigWrongKey() throws Exception {
+		cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, //
+				"Host localhost", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"IdentityFile " + privateKey2.getAbsolutePath());
+	}
+
+	@Test
+	public void testSshWithWrongUserNameInConfig() throws Exception {
+		// Bug 526778
+		cloneWith(
+				"ssh://" + TEST_USER + "@localhost:" + testPort
+						+ "/doesntmatter",
+				defaultCloneDir, null, //
+				"Host localhost", //
+				"HostName localhost", //
+				"User sombody_else", //
+				"IdentityFile " + privateKey1.getAbsolutePath());
+	}
+
+	@Test
+	public void testSshWithWrongPortInConfig() throws Exception {
+		// Bug 526778
+		cloneWith(
+				"ssh://" + TEST_USER + "@localhost:" + testPort
+						+ "/doesntmatter",
+				defaultCloneDir, null, //
+				"Host localhost", //
+				"HostName localhost", //
+				"Port 22", //
+				"User " + TEST_USER, //
+				"IdentityFile " + privateKey1.getAbsolutePath());
+	}
+
+	@Test
+	public void testSshWithAliasInConfig() throws Exception {
+		// Bug 531118
+		cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
+				"Host git", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"IdentityFile " + privateKey1.getAbsolutePath(), "", //
+				"Host localhost", //
+				"HostName localhost", //
+				"Port 22", //
+				"User someone_else", //
+				"IdentityFile " + privateKey2.getAbsolutePath());
+	}
+
+	@Test
+	public void testSshWithUnknownCiphersInConfig() throws Exception {
+		// Bug 535672
+		cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
+				"Host git", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"IdentityFile " + privateKey1.getAbsolutePath(), //
+				"Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr");
+	}
+
+	@Test
+	public void testSshWithUnknownHostKeyAlgorithmsInConfig()
+			throws Exception {
+		// Bug 535672
+		cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
+				"Host git", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"IdentityFile " + privateKey1.getAbsolutePath(), //
+				"HostKeyAlgorithms foobar,ssh-rsa,ssh-dss");
+	}
+
+	@Test
+	public void testSshWithUnknownKexAlgorithmsInConfig()
+			throws Exception {
+		// Bug 535672
+		cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
+				"Host git", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"IdentityFile " + privateKey1.getAbsolutePath(), //
+				"KexAlgorithms foobar,diffie-hellman-group14-sha1,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521");
+	}
+
+	@Test
+	public void testSshWithMinimalHostKeyAlgorithmsInConfig()
+			throws Exception {
+		// Bug 537790
+		cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
+				"Host git", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"IdentityFile " + privateKey1.getAbsolutePath(), //
+				"HostKeyAlgorithms ssh-rsa,ssh-dss");
+	}
+
+	@Test
+	public void testSshWithUnknownAuthInConfig() throws Exception {
+		cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
+				"Host git", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"IdentityFile " + privateKey1.getAbsolutePath(), //
+				"PreferredAuthentications gssapi-with-mic,hostbased,publickey,keyboard-interactive,password");
+	}
+
+	@Test(expected = TransportException.class)
+	public void testSshWithNoMatchingAuthInConfig() throws Exception {
+		// Server doesn't do password, and anyway we set no password.
+		cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
+				"Host git", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"IdentityFile " + privateKey1.getAbsolutePath(), //
+				"PreferredAuthentications password");
+	}
+
+	@Test
+	public void testRsaHostKeySecond() throws Exception {
+		// See https://git.eclipse.org/r/#/c/130402/ : server has EcDSA
+		// (preferred), RSA, we have RSA in known_hosts: client and server
+		// should agree on RSA.
+		File newHostKey = new File(getTemporaryDirectory(), "newhostkey");
+		copyTestResource("id_ecdsa_256", newHostKey);
+		server.addHostKey(newHostKey.toPath(), true);
+		cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
+				"Host git", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"IdentityFile " + privateKey1.getAbsolutePath());
+	}
+
+	@Test
+	public void testEcDsaHostKey() throws Exception {
+		// See https://git.eclipse.org/r/#/c/130402/ : server has RSA
+		// (preferred), EcDSA, we have EcDSA in known_hosts: client and server
+		// should agree on EcDSA.
+		File newHostKey = new File(getTemporaryDirectory(), "newhostkey");
+		copyTestResource("id_ecdsa_256", newHostKey);
+		server.addHostKey(newHostKey.toPath(), false);
+		File newHostKeyPub = new File(getTemporaryDirectory(),
+				"newhostkey.pub");
+		copyTestResource("id_ecdsa_256.pub", newHostKeyPub);
+		createKnownHostsFile(knownHosts, "localhost", testPort, newHostKeyPub);
+		cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
+				"Host git", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"IdentityFile " + privateKey1.getAbsolutePath());
+	}
+
+	@Test
+	public void testPasswordAuth() throws Exception {
+		server.enablePasswordAuthentication();
+		TestCredentialsProvider provider = new TestCredentialsProvider(
+				TEST_USER.toUpperCase(Locale.ROOT));
+		cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
+				"Host git", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"PreferredAuthentications password");
+	}
+
+	@Test
+	public void testPasswordAuthSeveralTimes() throws Exception {
+		server.enablePasswordAuthentication();
+		TestCredentialsProvider provider = new TestCredentialsProvider(
+				"wrongpass", "wrongpass", TEST_USER.toUpperCase(Locale.ROOT));
+		cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
+				"Host git", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"PreferredAuthentications password");
+	}
+
+	@Test(expected = TransportException.class)
+	public void testPasswordAuthWrongPassword() throws Exception {
+		server.enablePasswordAuthentication();
+		TestCredentialsProvider provider = new TestCredentialsProvider(
+				"wrongpass");
+		cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
+				"Host git", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"PreferredAuthentications password");
+	}
+
+	@Test(expected = TransportException.class)
+	public void testPasswordAuthNoPassword() throws Exception {
+		server.enablePasswordAuthentication();
+		TestCredentialsProvider provider = new TestCredentialsProvider();
+		cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
+				"Host git", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"PreferredAuthentications password");
+	}
+
+	@Test(expected = TransportException.class)
+	public void testPasswordAuthCorrectPasswordTooLate() throws Exception {
+		server.enablePasswordAuthentication();
+		TestCredentialsProvider provider = new TestCredentialsProvider(
+				"wrongpass", "wrongpass", "wrongpass",
+				TEST_USER.toUpperCase(Locale.ROOT));
+		cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
+				"Host git", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"PreferredAuthentications password");
+	}
+
+	@Test
+	public void testKeyboardInteractiveAuth() throws Exception {
+		server.enableKeyboardInteractiveAuthentication();
+		TestCredentialsProvider provider = new TestCredentialsProvider(
+				TEST_USER.toUpperCase(Locale.ROOT));
+		cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
+				"Host git", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"PreferredAuthentications keyboard-interactive");
+	}
+
+	@Test
+	public void testKeyboardInteractiveAuthSeveralTimes() throws Exception {
+		server.enableKeyboardInteractiveAuthentication();
+		TestCredentialsProvider provider = new TestCredentialsProvider(
+				"wrongpass", "wrongpass", TEST_USER.toUpperCase(Locale.ROOT));
+		cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
+				"Host git", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"PreferredAuthentications keyboard-interactive");
+	}
+
+	@Test(expected = TransportException.class)
+	public void testKeyboardInteractiveAuthWrongPassword() throws Exception {
+		server.enableKeyboardInteractiveAuthentication();
+		TestCredentialsProvider provider = new TestCredentialsProvider(
+				"wrongpass");
+		cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
+				"Host git", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"PreferredAuthentications keyboard-interactive");
+	}
+
+	@Test(expected = TransportException.class)
+	public void testKeyboardInteractiveAuthNoPassword() throws Exception {
+		server.enableKeyboardInteractiveAuthentication();
+		TestCredentialsProvider provider = new TestCredentialsProvider();
+		cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
+				"Host git", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"PreferredAuthentications keyboard-interactive");
+	}
+
+	@Test(expected = TransportException.class)
+	public void testKeyboardInteractiveAuthCorrectPasswordTooLate()
+			throws Exception {
+		server.enableKeyboardInteractiveAuthentication();
+		TestCredentialsProvider provider = new TestCredentialsProvider(
+				"wrongpass", "wrongpass", "wrongpass",
+				TEST_USER.toUpperCase(Locale.ROOT));
+		cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
+				"Host git", //
+				"HostName localhost", //
+				"Port " + testPort, //
+				"User " + TEST_USER, //
+				"PreferredAuthentications keyboard-interactive");
+	}
+
+	@Theory
+	public void testSshKeys(String keyName) throws Exception {
+		// JSch fails on ECDSA 384/521 keys. Compare
+		// https://sourceforge.net/p/jsch/patches/10/
+		assumeTrue(!(getSessionFactory() instanceof JschConfigSessionFactory
+				&& (keyName.contains("ed25519")
+						|| keyName.startsWith("id_ecdsa_384")
+						|| keyName.startsWith("id_ecdsa_521"))));
+		File cloned = new File(getTemporaryDirectory(), "cloned");
+		String keyFileName = keyName + "_key";
+		File privateKey = new File(sshDir, keyFileName);
+		copyTestResource(keyName, privateKey);
+		File publicKey = new File(sshDir, keyFileName + ".pub");
+		copyTestResource(keyName + ".pub", publicKey);
+		server.setTestUserPublicKey(publicKey.toPath());
+		TestCredentialsProvider provider = new TestCredentialsProvider(
+				"testpass");
+		pushTo(provider,
+				cloneWith("ssh://localhost/doesntmatter", //
+						cloned, provider, //
+						"Host localhost", //
+						"HostName localhost", //
+						"Port " + testPort, //
+						"User " + TEST_USER, //
+						"IdentityFile " + privateKey.getAbsolutePath()));
+		int expectedCalls = keyName.endsWith("testpass") ? 1 : 0;
+		assertEquals("Unexpected calls to CredentialsProvider", expectedCalls,
+				provider.getLog().size());
+		// Should now also work without credentials provider, even if the key
+		// was encrypted.
+		cloned = new File(getTemporaryDirectory(), "cloned2");
+		pushTo(null,
+				cloneWith("ssh://localhost/doesntmatter", //
+						cloned, null, //
+						"Host localhost", //
+						"HostName localhost", //
+						"Port " + testPort, //
+						"User " + TEST_USER, //
+						"IdentityFile " + privateKey.getAbsolutePath()));
+	}
+}
diff --git a/org.eclipse.jgit.test/src/org/eclipse/jgit/transport/ssh/SshTestHarness.java b/org.eclipse.jgit.test/src/org/eclipse/jgit/transport/ssh/SshTestHarness.java
new file mode 100644
index 0000000..59925a5
--- /dev/null
+++ b/org.eclipse.jgit.test/src/org/eclipse/jgit/transport/ssh/SshTestHarness.java
@@ -0,0 +1,452 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.transport.ssh;
+
+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.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.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.jgit.api.CloneCommand;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.PushCommand;
+import org.eclipse.jgit.api.ResetCommand.ResetType;
+import org.eclipse.jgit.errors.UnsupportedCredentialItem;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.junit.ssh.SshTestGitServer;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.transport.CredentialItem;
+import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.PushResult;
+import org.eclipse.jgit.transport.RemoteRefUpdate;
+import org.eclipse.jgit.transport.SshSessionFactory;
+import org.eclipse.jgit.transport.URIish;
+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
+ * {@link #copyTestResource(String, File)}. These test key files names have four
+ * components, separated by a single underscore: "id", the algorithm, the bits
+ * (if variable), and the password if the private key is encrypted. For instance
+ * "{@code id_ecdsa_384_testpass}" is an encrypted ECDSA-384 key. The passphrase
+ * to decrypt is "testpass". The key "{@code id_ecdsa_384}" is the same but
+ * unencrypted. All keys were generated and encrypted via ssh-keygen. Note that
+ * DSA and ec25519 have no "bits" component. Available keys are listed in
+ * {@link SshTestBase#KEY_RESOURCES}.
+ */
+public abstract class SshTestHarness extends RepositoryTestCase {
+
+	protected static final String TEST_USER = "testuser";
+
+	protected File sshDir;
+
+	protected File privateKey1;
+
+	protected File privateKey2;
+
+	protected File publicKey1;
+
+	protected SshTestGitServer server;
+
+	private SshSessionFactory factory;
+
+	protected int testPort;
+
+	protected File knownHosts;
+
+	private File homeDir;
+
+	@Override
+	public void setUp() throws Exception {
+		super.setUp();
+		writeTrashFile("file.txt", "something");
+		try (Git git = new Git(db)) {
+			git.add().addFilepattern("file.txt").call();
+			git.commit().setMessage("Initial commit").call();
+		}
+		mockSystemReader.setProperty("user.home",
+				getTemporaryDirectory().getAbsolutePath());
+		mockSystemReader.setProperty("HOME",
+				getTemporaryDirectory().getAbsolutePath());
+		homeDir = FS.DETECTED.userHome();
+		FS.DETECTED.setUserHome(getTemporaryDirectory().getAbsoluteFile());
+		sshDir = new File(getTemporaryDirectory(), ".ssh");
+		assertTrue(sshDir.mkdir());
+		File serverDir = new File(getTemporaryDirectory(), "srv");
+		assertTrue(serverDir.mkdir());
+		// Create two key pairs. Let's not call them "id_rsa".
+		privateKey1 = new File(sshDir, "first_key");
+		privateKey2 = new File(sshDir, "second_key");
+		publicKey1 = createKeyPair(privateKey1);
+		createKeyPair(privateKey2);
+		ByteArrayOutputStream publicHostKey = new ByteArrayOutputStream();
+		// Start a server with our test user and the first key.
+		server = new SshTestGitServer(TEST_USER, publicKey1.toPath(), db,
+				createHostKey(publicHostKey));
+		testPort = server.start();
+		assertTrue(testPort > 0);
+		knownHosts = new File(sshDir, "known_hosts");
+		Files.write(knownHosts.toPath(), Collections.singleton("[localhost]:"
+				+ testPort + ' '
+				+ publicHostKey.toString(StandardCharsets.US_ASCII.name())));
+		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);
+		}
+		File publicKeyFile = new File(privateKeyFile.getParentFile(),
+				privateKeyFile.getName() + ".pub");
+		try (OutputStream out = new FileOutputStream(publicKeyFile)) {
+			pair.writePublicKey(out, TEST_USER);
+		}
+		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();
+		}
+	}
+
+	/**
+	 * Creates a new known_hosts file with one entry for the given host and port
+	 * taken from the given public key file.
+	 *
+	 * @param file
+	 *            to write the known_hosts file to
+	 * @param host
+	 *            for the entry
+	 * @param port
+	 *            for the entry
+	 * @param publicKey
+	 *            to use
+	 * @return the public-key part of the line
+	 * @throws IOException
+	 */
+	protected static String createKnownHostsFile(File file, String host,
+			int port, File publicKey) throws IOException {
+		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.
+		String[] parts = pubKey.split("\\s+");
+		assertTrue("Unexpected key content",
+				parts.length == 2 || parts.length == 3);
+		String keyPart = parts[0] + ' ' + parts[1];
+		String line = '[' + host + "]:" + port + ' ' + keyPart;
+		Files.write(file.toPath(), Collections.singletonList(line));
+		return keyPart;
+	}
+
+	/**
+	 * Checks whether there is a line for the given host and port that also
+	 * matches the given key part in the list of lines.
+	 *
+	 * @param host
+	 *            to look for
+	 * @param port
+	 *            to look for
+	 * @param keyPart
+	 *            to look for
+	 * @param lines
+	 *            to look in
+	 * @return {@code true} if found, {@code false} otherwise
+	 */
+	protected boolean hasHostKey(String host, int port, String keyPart,
+			List<String> lines) {
+		String h = '[' + host + "]:" + port;
+		return lines.stream()
+				.anyMatch(l -> l.contains(h) && l.contains(keyPart));
+	}
+
+	@After
+	public void shutdownServer() throws Exception {
+		if (server != null) {
+			server.stop();
+			server = null;
+		}
+		FS.DETECTED.setUserHome(homeDir);
+		SshSessionFactory.setInstance(null);
+		factory = null;
+	}
+
+	protected abstract SshSessionFactory createSessionFactory();
+
+	protected SshSessionFactory getSessionFactory() {
+		return factory;
+	}
+
+	protected abstract void installConfig(String... config);
+
+	/**
+	 * Copies a test data file contained in the test bundle to the given file.
+	 * Equivalent to {@link #copyTestResource(Class, String, File)} with
+	 * {@code SshTestHarness.class} as first parameter.
+	 *
+	 * @param resourceName
+	 *            of the test resource to copy
+	 * @param to
+	 *            file to copy the resource to
+	 * @throws IOException
+	 *             if the resource cannot be copied
+	 */
+	protected void copyTestResource(String resourceName, File to)
+			throws IOException {
+		copyTestResource(SshTestHarness.class, resourceName, to);
+	}
+
+	/**
+	 * Copies a test data file contained in the test bundle to the given file,
+	 * using {@link Class#getResourceAsStream(String)} to get the test resource.
+	 *
+	 * @param loader
+	 *            {@link Class} to use to load the resource
+	 * @param resourceName
+	 *            of the test resource to copy
+	 * @param to
+	 *            file to copy the resource to
+	 * @throws IOException
+	 *             if the resource cannot be copied
+	 */
+	protected void copyTestResource(Class<?> loader, String resourceName,
+			File to) throws IOException {
+		try (InputStream in = loader.getResourceAsStream(resourceName)) {
+			Files.copy(in, to.toPath());
+		}
+	}
+
+	protected File cloneWith(String uri, File to, CredentialsProvider provider,
+			String... config) throws Exception {
+		installConfig(config);
+		CloneCommand clone = Git.cloneRepository().setCloneAllBranches(true)
+				.setDirectory(to).setURI(uri);
+		if (provider != null) {
+			clone.setCredentialsProvider(provider);
+		}
+		try (Git git = clone.call()) {
+			Repository repo = git.getRepository();
+			assertNotNull(repo.resolve("master"));
+			assertNotEquals(db.getWorkTree(),
+					git.getRepository().getWorkTree());
+			assertTrue(new File(git.getRepository().getWorkTree(), "file.txt")
+					.exists());
+			return repo.getWorkTree();
+		}
+	}
+
+	protected void pushTo(File localClone) throws Exception {
+		pushTo(null, localClone);
+	}
+
+	protected void pushTo(CredentialsProvider provider, File localClone)
+			throws Exception {
+		RevCommit commit;
+		File newFile = null;
+		try (Git git = Git.open(localClone)) {
+			// Write a new file and modify a file.
+			Repository local = git.getRepository();
+			newFile = File.createTempFile("new", "sshtest",
+					local.getWorkTree());
+			write(newFile, "something new");
+			File existingFile = new File(local.getWorkTree(), "file.txt");
+			write(existingFile, "something else");
+			git.add().addFilepattern("file.txt")
+					.addFilepattern(newFile.getName())
+					.call();
+			commit = git.commit().setMessage("Local commit").call();
+			// Push
+			PushCommand push = git.push().setPushAll();
+			if (provider != null) {
+				push.setCredentialsProvider(provider);
+			}
+			Iterable<PushResult> results = push.call();
+			for (PushResult result : results) {
+				for (RemoteRefUpdate u : result.getRemoteUpdates()) {
+					assertEquals(
+							"Could not update " + u.getRemoteName() + ' '
+									+ u.getMessage(),
+							RemoteRefUpdate.Status.OK, u.getStatus());
+				}
+			}
+		}
+		// Now check "master" in the remote repo directly:
+		assertEquals("Unexpected remote commit", commit, db.resolve("master"));
+		assertEquals("Unexpected remote commit", commit,
+				db.resolve(Constants.HEAD));
+		File remoteFile = new File(db.getWorkTree(), newFile.getName());
+		assertFalse("File should not exist on remote", remoteFile.exists());
+		try (Git git = new Git(db)) {
+			git.reset().setMode(ResetType.HARD).setRef(Constants.HEAD).call();
+		}
+		assertTrue("File does not exist on remote", remoteFile.exists());
+		checkFile(remoteFile, "something new");
+	}
+
+	protected static class TestCredentialsProvider extends CredentialsProvider {
+
+		private final List<String> stringStore;
+
+		private final Iterator<String> strings;
+
+		public TestCredentialsProvider(String... strings) {
+			if (strings == null || strings.length == 0) {
+				stringStore = Collections.emptyList();
+			} else {
+				stringStore = Arrays.asList(strings);
+			}
+			this.strings = stringStore.iterator();
+		}
+
+		@Override
+		public boolean isInteractive() {
+			return true;
+		}
+
+		@Override
+		public boolean supports(CredentialItem... items) {
+			return true;
+		}
+
+		@Override
+		public boolean get(URIish uri, CredentialItem... items)
+				throws UnsupportedCredentialItem {
+			System.out.println("URI: " + uri);
+			for (CredentialItem item : items) {
+				System.out.println(item.getClass().getSimpleName() + ' '
+						+ item.getPromptText());
+			}
+			logItems(uri, items);
+			for (CredentialItem item : items) {
+				if (item instanceof CredentialItem.InformationalMessage) {
+					continue;
+				}
+				if (item instanceof CredentialItem.YesNoType) {
+					((CredentialItem.YesNoType) item).setValue(true);
+				} else if (item instanceof CredentialItem.CharArrayType) {
+					if (strings.hasNext()) {
+						((CredentialItem.CharArrayType) item)
+								.setValue(strings.next().toCharArray());
+					} else {
+						return false;
+					}
+				} else if (item instanceof CredentialItem.StringType) {
+					if (strings.hasNext()) {
+						((CredentialItem.StringType) item)
+								.setValue(strings.next());
+					} else {
+						return false;
+					}
+				} else {
+					return false;
+				}
+			}
+			return true;
+		}
+
+		private List<LogEntry> log = new ArrayList<>();
+
+		private void logItems(URIish uri, CredentialItem... items) {
+			log.add(new LogEntry(uri, Arrays.asList(items)));
+		}
+
+		public List<LogEntry> getLog() {
+			return log;
+		}
+	}
+
+	protected static class LogEntry {
+
+		private URIish uri;
+
+		private List<CredentialItem> items;
+
+		public LogEntry(URIish uri, List<CredentialItem> items) {
+			this.uri = uri;
+			this.items = items;
+		}
+
+		public URIish getURIish() {
+			return uri;
+		}
+
+		public List<CredentialItem> getItems() {
+			return items;
+		}
+	}
+}
diff --git a/org.eclipse.jgit.test/tests.bzl b/org.eclipse.jgit.test/tests.bzl
index bc06e3e..dc21029 100644
--- a/org.eclipse.jgit.test/tests.bzl
+++ b/org.eclipse.jgit.test/tests.bzl
@@ -41,7 +41,18 @@
             additional_deps = [
                 "//lib:jsch",
             ]
-
+        if src.endswith("JSchSshTest.java"):
+            additional_deps = [
+                "//lib:jsch",
+                "//lib:jzlib",
+                "//lib:sshd-core",
+                "//lib:sshd-sftp",
+                ":sshd-helpers",
+            ]
+        if src.endswith("JDKHttpConnectionTest.java"):
+            additional_deps = [
+                "//lib:mockito",
+            ]
         heap_size = "-Xmx256m"
         if src.endswith("HugeCommitMessageTest.java"):
             heap_size = "-Xmx512m"
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
index 911f659..1a5793c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
@@ -43,6 +43,7 @@
  */
 package org.eclipse.jgit.api;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.util.FileUtils.RECURSIVE;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -118,7 +119,7 @@
 	public void testAddExistingSingleFile() throws IOException, GitAPIException {
 		File file = new File(db.getWorkTree(), "a.txt");
 		FileUtils.createNewFile(file);
-		try (PrintWriter writer = new PrintWriter(file)) {
+		try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
 			writer.print("content");
 		}
 
@@ -489,7 +490,7 @@
 			GitAPIException {
 		File file = new File(db.getWorkTree(), "a.txt");
 		FileUtils.createNewFile(file);
-		try (PrintWriter writer = new PrintWriter(file)) {
+		try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
 			writer.print("row1\r\nrow2");
 		}
 
@@ -519,7 +520,7 @@
 			data.append("row1\r\nrow2");
 		}
 		String crData = data.toString();
-		try (PrintWriter writer = new PrintWriter(file)) {
+		try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
 			writer.print(crData);
 		}
 		String lfData = data.toString().replaceAll("\r", "");
@@ -544,7 +545,7 @@
 			GitAPIException {
 		File file = new File(db.getWorkTree(), "a.txt");
 		FileUtils.createNewFile(file);
-		try (PrintWriter writer = new PrintWriter(file)) {
+		try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
 			writer.print("row1\r\nrow2\u0000");
 		}
 
@@ -570,7 +571,7 @@
 		FileUtils.mkdir(new File(db.getWorkTree(), "sub"));
 		File file = new File(db.getWorkTree(), "sub/a.txt");
 		FileUtils.createNewFile(file);
-		try (PrintWriter writer = new PrintWriter(file)) {
+		try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
 			writer.print("content");
 		}
 
@@ -588,7 +589,7 @@
 			GitAPIException {
 		File file = new File(db.getWorkTree(), "a.txt");
 		FileUtils.createNewFile(file);
-		try (PrintWriter writer = new PrintWriter(file)) {
+		try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
 			writer.print("content");
 		}
 
@@ -597,7 +598,7 @@
 
 			dc.getEntry(0).getObjectId();
 
-			try (PrintWriter writer = new PrintWriter(file)) {
+			try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
 				writer.print("other content");
 			}
 
@@ -613,7 +614,7 @@
 	public void testAddExistingSingleFileTwiceWithCommit() throws Exception {
 		File file = new File(db.getWorkTree(), "a.txt");
 		FileUtils.createNewFile(file);
-		try (PrintWriter writer = new PrintWriter(file)) {
+		try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
 			writer.print("content");
 		}
 
@@ -624,7 +625,7 @@
 
 			git.commit().setMessage("commit a.txt").call();
 
-			try (PrintWriter writer = new PrintWriter(file)) {
+			try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
 				writer.print("other content");
 			}
 
@@ -640,7 +641,7 @@
 	public void testAddRemovedFile() throws Exception {
 		File file = new File(db.getWorkTree(), "a.txt");
 		FileUtils.createNewFile(file);
-		try (PrintWriter writer = new PrintWriter(file)) {
+		try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
 			writer.print("content");
 		}
 
@@ -663,7 +664,7 @@
 	public void testAddRemovedCommittedFile() throws Exception {
 		File file = new File(db.getWorkTree(), "a.txt");
 		FileUtils.createNewFile(file);
-		try (PrintWriter writer = new PrintWriter(file)) {
+		try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
 			writer.print("content");
 		}
 
@@ -690,13 +691,13 @@
 
 		File file = new File(db.getWorkTree(), "a.txt");
 		FileUtils.createNewFile(file);
-		try (PrintWriter writer = new PrintWriter(file)) {
+		try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
 			writer.print("content");
 		}
 
 		File file2 = new File(db.getWorkTree(), "b.txt");
 		FileUtils.createNewFile(file2);
-		try (PrintWriter writer = new PrintWriter(file2)) {
+		try (PrintWriter writer = new PrintWriter(file2, UTF_8.name())) {
 			writer.print("content b");
 		}
 
@@ -707,12 +708,12 @@
 		addEntryToBuilder("b.txt", file2, newObjectInserter, builder, 0);
 		addEntryToBuilder("a.txt", file, newObjectInserter, builder, 1);
 
-		try (PrintWriter writer = new PrintWriter(file)) {
+		try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
 			writer.print("other content");
 		}
 		addEntryToBuilder("a.txt", file, newObjectInserter, builder, 3);
 
-		try (PrintWriter writer = new PrintWriter(file)) {
+		try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
 			writer.print("our content");
 		}
 		addEntryToBuilder("a.txt", file, newObjectInserter, builder, 2)
@@ -743,13 +744,13 @@
 	public void testAddTwoFiles() throws Exception  {
 		File file = new File(db.getWorkTree(), "a.txt");
 		FileUtils.createNewFile(file);
-		try (PrintWriter writer = new PrintWriter(file)) {
+		try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
 			writer.print("content");
 		}
 
 		File file2 = new File(db.getWorkTree(), "b.txt");
 		FileUtils.createNewFile(file2);
-		try (PrintWriter writer = new PrintWriter(file2)) {
+		try (PrintWriter writer = new PrintWriter(file2, UTF_8.name())) {
 			writer.print("content b");
 		}
 
@@ -767,13 +768,13 @@
 		FileUtils.mkdir(new File(db.getWorkTree(), "sub"));
 		File file = new File(db.getWorkTree(), "sub/a.txt");
 		FileUtils.createNewFile(file);
-		try (PrintWriter writer = new PrintWriter(file)) {
+		try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
 			writer.print("content");
 		}
 
 		File file2 = new File(db.getWorkTree(), "sub/b.txt");
 		FileUtils.createNewFile(file2);
-		try (PrintWriter writer = new PrintWriter(file2)) {
+		try (PrintWriter writer = new PrintWriter(file2, UTF_8.name())) {
 			writer.print("content b");
 		}
 
@@ -791,19 +792,19 @@
 		FileUtils.mkdir(new File(db.getWorkTree(), "sub"));
 		File file = new File(db.getWorkTree(), "sub/a.txt");
 		FileUtils.createNewFile(file);
-		try (PrintWriter writer = new PrintWriter(file)) {
+		try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
 			writer.print("content");
 		}
 
 		File ignoreFile = new File(db.getWorkTree(), ".gitignore");
 		FileUtils.createNewFile(ignoreFile);
-		try (PrintWriter writer = new PrintWriter(ignoreFile)) {
+		try (PrintWriter writer = new PrintWriter(ignoreFile, UTF_8.name())) {
 			writer.print("sub/b.txt");
 		}
 
 		File file2 = new File(db.getWorkTree(), "sub/b.txt");
 		FileUtils.createNewFile(file2);
-		try (PrintWriter writer = new PrintWriter(file2)) {
+		try (PrintWriter writer = new PrintWriter(file2, UTF_8.name())) {
 			writer.print("content b");
 		}
 
@@ -821,13 +822,13 @@
 		FileUtils.mkdir(new File(db.getWorkTree(), "sub"));
 		File file = new File(db.getWorkTree(), "sub/a.txt");
 		FileUtils.createNewFile(file);
-		try (PrintWriter writer = new PrintWriter(file)) {
+		try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
 			writer.print("content");
 		}
 
 		File file2 = new File(db.getWorkTree(), "sub/b.txt");
 		FileUtils.createNewFile(file2);
-		try (PrintWriter writer = new PrintWriter(file2)) {
+		try (PrintWriter writer = new PrintWriter(file2, UTF_8.name())) {
 			writer.print("content b");
 		}
 
@@ -849,13 +850,13 @@
 		FileUtils.mkdir(new File(db.getWorkTree(), "sub"));
 		File file = new File(db.getWorkTree(), "sub/a.txt");
 		FileUtils.createNewFile(file);
-		try (PrintWriter writer = new PrintWriter(file)) {
+		try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
 			writer.print("content");
 		}
 
 		File file2 = new File(db.getWorkTree(), "sub/b.txt");
 		FileUtils.createNewFile(file2);
-		try (PrintWriter writer = new PrintWriter(file2)) {
+		try (PrintWriter writer = new PrintWriter(file2, UTF_8.name())) {
 			writer.print("content b");
 		}
 
@@ -872,12 +873,12 @@
 			// new unstaged file sub/c.txt
 			File file3 = new File(db.getWorkTree(), "sub/c.txt");
 			FileUtils.createNewFile(file3);
-			try (PrintWriter writer = new PrintWriter(file3)) {
+			try (PrintWriter writer = new PrintWriter(file3, UTF_8.name())) {
 				writer.print("content c");
 			}
 
 			// file sub/a.txt is modified
-			try (PrintWriter writer = new PrintWriter(file)) {
+			try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
 				writer.print("modified content");
 			}
 
@@ -904,13 +905,13 @@
 		FileUtils.mkdir(new File(db.getWorkTree(), "sub"));
 		File file = new File(db.getWorkTree(), "sub/a.txt");
 		FileUtils.createNewFile(file);
-		try (PrintWriter writer = new PrintWriter(file)) {
+		try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
 			writer.print("content");
 		}
 
 		File file2 = new File(db.getWorkTree(), "sub/b.txt");
 		FileUtils.createNewFile(file2);
-		try (PrintWriter writer = new PrintWriter(file2)) {
+		try (PrintWriter writer = new PrintWriter(file2, UTF_8.name())) {
 			writer.print("content b");
 		}
 
@@ -927,12 +928,12 @@
 			// new unstaged file sub/c.txt
 			File file3 = new File(db.getWorkTree(), "sub/c.txt");
 			FileUtils.createNewFile(file3);
-			try (PrintWriter writer = new PrintWriter(file3)) {
+			try (PrintWriter writer = new PrintWriter(file3, UTF_8.name())) {
 				writer.print("content c");
 			}
 
 			// file sub/a.txt is modified
-			try (PrintWriter writer = new PrintWriter(file)) {
+			try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
 				writer.print("modified content");
 			}
 
@@ -1244,7 +1245,8 @@
 				ConfigConstants.CONFIG_KEY_DIRNOGITLINKS, true);
 		config.save();
 
-		assert (db.getConfig().get(WorkingTreeOptions.KEY).isDirNoGitLinks());
+		assertTrue(
+				db.getConfig().get(WorkingTreeOptions.KEY).isDirNoGitLinks());
 
 		try (Git git = new Git(db)) {
 			git.add().addFilepattern("nested-repo").call();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java
index 1300f98..1c41018 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java
@@ -42,6 +42,7 @@
  */
 package org.eclipse.jgit.api;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 
@@ -190,7 +191,8 @@
 		}
 	}
 
-	private class MockFormat implements ArchiveCommand.Format<MockOutputStream> {
+	private static class MockFormat
+			implements ArchiveCommand.Format<MockOutputStream> {
 
 		private Map<String, String> entries = new HashMap<>();
 
@@ -230,7 +232,9 @@
 
 		@Override
 		public void putEntry(MockOutputStream out, ObjectId tree, String path, FileMode mode, ObjectLoader loader) {
-			String content = mode != FileMode.TREE ? new String(loader.getBytes()) : null;
+			String content = mode != FileMode.TREE
+					? new String(loader.getBytes(), UTF_8)
+					: null;
 			entries.put(path, content);
 		}
 
@@ -240,7 +244,7 @@
 		}
 	}
 
-	public class MockOutputStream extends OutputStream {
+	public static class MockOutputStream extends OutputStream {
 
 		private int foo;
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BlameCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BlameCommandTest.java
index 7a1d222..7e73084 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BlameCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BlameCommandTest.java
@@ -44,6 +44,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 import java.io.File;
 
@@ -489,4 +490,73 @@
 			assertEquals(side, lines.getSourceCommit(2));
 		}
 	}
+
+	@Test
+	public void testBlameWithNulByteInHistory() throws Exception {
+		try (Git git = new Git(db)) {
+			String[] content1 = { "First line", "Another line" };
+			writeTrashFile("file.txt", join(content1));
+			git.add().addFilepattern("file.txt").call();
+			RevCommit c1 = git.commit().setMessage("create file").call();
+
+			String[] content2 = { "First line", "Second line with NUL >\000<",
+					"Another line" };
+			assertTrue("Content should contain a NUL byte",
+					content2[1].indexOf(0) > 0);
+			writeTrashFile("file.txt", join(content2));
+			git.add().addFilepattern("file.txt").call();
+			git.commit().setMessage("add line with NUL").call();
+
+			String[] content3 = { "First line", "Second line with NUL >\000<",
+					"Third line" };
+			writeTrashFile("file.txt", join(content3));
+			git.add().addFilepattern("file.txt").call();
+			RevCommit c3 = git.commit().setMessage("change third line").call();
+
+			String[] content4 = { "First line", "Second line with NUL >\\000<",
+					"Third line" };
+			assertTrue("Content should not contain a NUL byte",
+					content4[1].indexOf(0) < 0);
+			writeTrashFile("file.txt", join(content4));
+			git.add().addFilepattern("file.txt").call();
+			RevCommit c4 = git.commit().setMessage("fix NUL line").call();
+
+			BlameResult lines = git.blame().setFilePath("file.txt").call();
+			assertEquals(3, lines.getResultContents().size());
+			assertEquals(c1, lines.getSourceCommit(0));
+			assertEquals(c4, lines.getSourceCommit(1));
+			assertEquals(c3, lines.getSourceCommit(2));
+		}
+	}
+
+	@Test
+	public void testBlameWithNulByteInTopRevision() throws Exception {
+		try (Git git = new Git(db)) {
+			String[] content1 = { "First line", "Another line" };
+			writeTrashFile("file.txt", join(content1));
+			git.add().addFilepattern("file.txt").call();
+			RevCommit c1 = git.commit().setMessage("create file").call();
+
+			String[] content2 = { "First line", "Second line with NUL >\000<",
+					"Another line" };
+			assertTrue("Content should contain a NUL byte",
+					content2[1].indexOf(0) > 0);
+			writeTrashFile("file.txt", join(content2));
+			git.add().addFilepattern("file.txt").call();
+			RevCommit c2 = git.commit().setMessage("add line with NUL").call();
+
+			String[] content3 = { "First line", "Second line with NUL >\000<",
+					"Third line" };
+			writeTrashFile("file.txt", join(content3));
+			git.add().addFilepattern("file.txt").call();
+			RevCommit c3 = git.commit().setMessage("change third line").call();
+
+			BlameResult lines = git.blame().setFilePath("file.txt").call();
+			assertEquals(3, lines.getResultContents().size());
+			assertEquals(c1, lines.getSourceCommit(0));
+			assertEquals(c2, lines.getSourceCommit(1));
+			assertEquals(c3, lines.getSourceCommit(2));
+		}
+	}
+
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java
index 71df59e..498005d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java
@@ -154,7 +154,7 @@
 	}
 
 	@Test
-	public void testCheckoutWithConflict() {
+	public void testCheckoutWithConflict() throws Exception {
 		CheckoutCommand co = git.checkout();
 		try {
 			writeTrashFile("Test.txt", "Another change");
@@ -165,6 +165,8 @@
 			assertEquals(Status.CONFLICTS, co.getResult().getStatus());
 			assertTrue(co.getResult().getConflictList().contains("Test.txt"));
 		}
+		git.checkout().setName("master").setForce(true).call();
+		assertThat(read("Test.txt"), is("Hello world"));
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CleanCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CleanCommandTest.java
index 065b5b4..139f199 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CleanCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CleanCommandTest.java
@@ -234,6 +234,27 @@
 	}
 
 	@Test
+	public void testCleanDirsWithPrefixFolder() throws Exception {
+		String path = "sub/foo.txt";
+		writeTrashFile(path, "sub is a prefix of sub-noclean");
+		git.add().addFilepattern(path).call();
+		Status beforeCleanStatus = git.status().call();
+		assertTrue(beforeCleanStatus.getAdded().contains(path));
+
+		Set<String> cleanedFiles = git.clean().setCleanDirectories(true).call();
+
+		// The "sub" directory should not be cleaned.
+		assertTrue(!cleanedFiles.contains(path + "/"));
+
+		assertTrue(cleanedFiles.contains("File2.txt"));
+		assertTrue(cleanedFiles.contains("File3.txt"));
+		assertTrue(!cleanedFiles.contains("sub-noclean/File1.txt"));
+		assertTrue(cleanedFiles.contains("sub-noclean/File2.txt"));
+		assertTrue(cleanedFiles.contains("sub-clean/"));
+		assertTrue(cleanedFiles.size() == 4);
+	}
+
+	@Test
 	public void testCleanDirsWithSubmodule() throws Exception {
 		SubmoduleAddCommand command = new SubmoduleAddCommand(db);
 		String path = "sub";
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 0d7009d..6b5fe50 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
@@ -370,8 +370,7 @@
 	}
 
 	@Test
-	public void testCloneRepositoryOnlyOneBranch() throws IOException,
-			JGitInternalException, GitAPIException {
+	public void testCloneRepositoryOnlyOneBranch() throws Exception {
 		File directory = createTempDirectory("testCloneRepositoryWithBranch");
 		CloneCommand command = Git.cloneRepository();
 		command.setBranch("refs/heads/master");
@@ -382,25 +381,47 @@
 		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/remotes/origin/master", allRefNames(git2
 				.branchList().setListMode(ListMode.REMOTE).call()));
+		RemoteConfig cfg = new RemoteConfig(git2.getRepository().getConfig(),
+				Constants.DEFAULT_REMOTE_NAME);
+		List<RefSpec> specs = cfg.getFetchRefSpecs();
+		assertEquals(1, specs.size());
+		assertEquals(
+				new RefSpec("+refs/heads/master:refs/remotes/origin/master"),
+				specs.get(0));
+	}
 
+	@Test
+	public void testBareCloneRepositoryOnlyOneBranch() throws Exception {
 		// Same thing, but now test with bare repo
-		directory = createTempDirectory("testCloneRepositoryWithBranch_bare");
-		command = Git.cloneRepository();
+		File directory = createTempDirectory(
+				"testCloneRepositoryWithBranch_bare");
+		CloneCommand command = Git.cloneRepository();
 		command.setBranch("refs/heads/master");
 		command.setBranchesToClone(Collections
 				.singletonList("refs/heads/master"));
 		command.setDirectory(directory);
 		command.setURI(fileUri());
 		command.setBare(true);
-		git2 = command.call();
+		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", allRefNames(git2.branchList()
 				.setListMode(ListMode.ALL).call()));
+		RemoteConfig cfg = new RemoteConfig(git2.getRepository().getConfig(),
+				Constants.DEFAULT_REMOTE_NAME);
+		List<RefSpec> specs = cfg.getFetchRefSpecs();
+		assertEquals(1, specs.size());
+		assertEquals(
+				new RefSpec("+refs/heads/master:refs/heads/master"),
+				specs.get(0));
 	}
 
 	public static String allRefNames(List<Ref> refs) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTest.java
index ca0630e..c028ca3 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTest.java
@@ -42,6 +42,7 @@
  */
 package org.eclipse.jgit.api;
 
+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.assertTrue;
@@ -120,7 +121,7 @@
 			// create first file
 			File file = new File(db.getWorkTree(), "a.txt");
 			FileUtils.createNewFile(file);
-			try (PrintWriter writer = new PrintWriter(file)) {
+			try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
 				writer.print("content1");
 			}
 
@@ -131,7 +132,7 @@
 			// create second file
 			file = new File(db.getWorkTree(), "b.txt");
 			FileUtils.createNewFile(file);
-			try (PrintWriter writer = new PrintWriter(file)) {
+			try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
 				writer.print("content2");
 			}
 
@@ -231,7 +232,7 @@
 			JGitInternalException, GitAPIException {
 		File file = new File(db.getWorkTree(), "a.txt");
 		FileUtils.createNewFile(file);
-		try (PrintWriter writer = new PrintWriter(file)) {
+		try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
 			writer.print("content");
 		}
 
@@ -242,7 +243,7 @@
 			assertEquals("6b584e8ece562ebffc15d38808cd6b98fc3d97ea",
 					tw.getObjectId(0).getName());
 
-			try (PrintWriter writer = new PrintWriter(file)) {
+			try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
 				writer.print("content2");
 			}
 			commit = git.commit().setMessage("second commit").call();
@@ -265,7 +266,7 @@
 			// create file
 			File file = new File(db.getWorkTree(), "a.txt");
 			FileUtils.createNewFile(file);
-			try (PrintWriter writer = new PrintWriter(file)) {
+			try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
 				writer.print("content1");
 			}
 
@@ -358,7 +359,7 @@
 					messageHeader + messageFooter)
 					.setInsertChangeId(true).call();
 			// we should find a real change id (at the end of the file)
-			byte[] chars = commit.getFullMessage().getBytes();
+			byte[] chars = commit.getFullMessage().getBytes(UTF_8);
 			int lastLineBegin = RawParseUtils.prevLF(chars, chars.length - 2);
 			String lastLine = RawParseUtils.decode(chars, lastLineBegin + 1,
 					chars.length);
@@ -371,7 +372,7 @@
 					.setInsertChangeId(true).call();
 			// we should find a real change id (in the line as dictated by the
 			// template)
-			chars = commit.getFullMessage().getBytes();
+			chars = commit.getFullMessage().getBytes(UTF_8);
 			int lineStart = 0;
 			int lineEnd = 0;
 			for (int i = 0; i < 4; i++) {
@@ -389,7 +390,7 @@
 					messageHeader + changeIdTemplate + messageFooter)
 					.setInsertChangeId(false).call();
 			// we should find the untouched template
-			chars = commit.getFullMessage().getBytes();
+			chars = commit.getFullMessage().getBytes(UTF_8);
 			lineStart = 0;
 			lineEnd = 0;
 			for (int i = 0; i < 4; i++) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitOnlyTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitOnlyTest.java
index bbd6ec0..83181ee 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitOnlyTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitOnlyTest.java
@@ -43,6 +43,7 @@
  */
 package org.eclipse.jgit.api;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -1298,7 +1299,7 @@
 				final TreeWalk tw = TreeWalk.forPath(repo, path,
 						rw.parseTree(headId));
 				return new String(tw.getObjectReader().open(tw.getObjectId(0))
-						.getBytes());
+						.getBytes(), UTF_8);
 			}
 		} catch (Exception e) {
 			return "";
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CrLfNativeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CrLfNativeTest.java
new file mode 100644
index 0000000..c726128
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CrLfNativeTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.api;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jgit.api.ResetCommand.ResetType;
+import org.eclipse.jgit.junit.MockSystemReader;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.treewalk.FileTreeIterator;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
+import org.eclipse.jgit.util.SystemReader;
+import org.junit.Test;
+
+public class CrLfNativeTest extends RepositoryTestCase {
+
+	@Test
+	public void checkoutWithCrLfNativeUnix() throws Exception {
+		verifyNativeCheckout(new MockSystemReader() {
+			{
+				setUnix();
+			}
+		});
+	}
+
+	@Test
+	public void checkoutWithCrLfNativeWindows() throws Exception {
+		verifyNativeCheckout(new MockSystemReader() {
+			{
+				setWindows();
+			}
+		});
+	}
+
+	private void verifyNativeCheckout(SystemReader systemReader)
+			throws Exception {
+		SystemReader.setInstance(systemReader);
+		Git git = Git.wrap(db);
+		FileBasedConfig config = db.getConfig();
+		config.setString("core", null, "autocrlf", "false");
+		config.setString("core", null, "eol", "native");
+		config.save();
+		// core.eol is active only if text is set, or if text=auto
+		writeTrashFile(".gitattributes", "*.txt text\n");
+		File file = writeTrashFile("file.txt", "line 1\nline 2\n");
+		git.add().addFilepattern("file.txt").addFilepattern(".gitattributes")
+				.call();
+		git.commit().setMessage("Initial").call();
+		// Check-in with core.eol=native normalization
+		assertEquals(
+				"[.gitattributes, mode:100644, content:*.txt text\n]"
+						+ "[file.txt, mode:100644, content:line 1\nline 2\n]",
+				indexState(CONTENT));
+		writeTrashFile("file.txt", "something else");
+		git.add().addFilepattern("file.txt").call();
+		git.commit().setMessage("New commit").call();
+		git.reset().setMode(ResetType.HARD).setRef("HEAD~").call();
+		// Check-out should convert to the native line separator
+		checkFile(file, systemReader.isWindows() ? "line 1\r\nline 2\r\n"
+				: "line 1\nline 2\n");
+		Status status = git.status().call();
+		assertTrue("git status should be clean", status.isClean());
+	}
+
+	/**
+	 * Verifies the handling of the crlf attribute: crlf == text, -crlf ==
+	 * -text, crlf=input == eol=lf
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void testCrLfAttribute() throws Exception {
+		FileBasedConfig config = db.getConfig();
+		config.setString("core", null, "autocrlf", "false");
+		config.setString("core", null, "eol", "crlf");
+		config.save();
+		writeTrashFile(".gitattributes",
+				"*.txt text\n*.crlf crlf\n*.bin -text\n*.nocrlf -crlf\n*.input crlf=input\n*.eol eol=lf");
+		writeTrashFile("foo.txt", "");
+		writeTrashFile("foo.crlf", "");
+		writeTrashFile("foo.bin", "");
+		writeTrashFile("foo.nocrlf", "");
+		writeTrashFile("foo.input", "");
+		writeTrashFile("foo.eol", "");
+		Map<String, EolStreamType> inTypes = new HashMap<>();
+		Map<String, EolStreamType> outTypes = new HashMap<>();
+		try (TreeWalk walk = new TreeWalk(db)) {
+			walk.addTree(new FileTreeIterator(db));
+			while (walk.next()) {
+				String path = walk.getPathString();
+				if (".gitattributes".equals(path)) {
+					continue;
+				}
+				EolStreamType in = walk
+						.getEolStreamType(OperationType.CHECKIN_OP);
+				EolStreamType out = walk
+						.getEolStreamType(OperationType.CHECKOUT_OP);
+				inTypes.put(path, in);
+				outTypes.put(path, out);
+			}
+		}
+		assertEquals("", checkTypes("check-in", inTypes));
+		assertEquals("", checkTypes("check-out", outTypes));
+	}
+
+	private String checkTypes(String prefix, Map<String, EolStreamType> types) {
+		StringBuilder result = new StringBuilder();
+		EolStreamType a = types.get("foo.crlf");
+		EolStreamType b = types.get("foo.txt");
+		report(result, prefix, "crlf != text", a, b);
+		a = types.get("foo.nocrlf");
+		b = types.get("foo.bin");
+		report(result, prefix, "-crlf != -text", a, b);
+		a = types.get("foo.input");
+		b = types.get("foo.eol");
+		report(result, prefix, "crlf=input != eol=lf", a, b);
+		return result.toString();
+	}
+
+	private void report(StringBuilder result, String prefix, String label,
+			EolStreamType a,
+			EolStreamType b) {
+		if (a == null || b == null || !a.equals(b)) {
+			result.append(prefix).append(' ').append(label).append(": ")
+					.append(toString(a)).append(" != ").append(toString(b))
+					.append('\n');
+		}
+	}
+
+	private String toString(EolStreamType type) {
+		return type == null ? "null" : type.name();
+	}
+}
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 f2093e3..807079e 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
@@ -42,14 +42,16 @@
  */
 package org.eclipse.jgit.api;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import java.io.BufferedWriter;
 import java.io.File;
-import java.io.FileWriter;
 import java.io.IOException;
+import java.nio.file.Files;
 import java.util.Arrays;
 import java.util.Collection;
 
@@ -427,7 +429,7 @@
 	}
 
 	private static void touch(File f, String contents) throws Exception {
-		try (FileWriter w = new FileWriter(f)) {
+		try (BufferedWriter w = Files.newBufferedWriter(f.toPath(), UTF_8)) {
 			w.write(contents);
 		}
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolRepositoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolRepositoryTest.java
index 48d3733..47806cb 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolRepositoryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolRepositoryTest.java
@@ -38,6 +38,7 @@
  */
 package org.eclipse.jgit.api;
 
+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.assertTrue;
@@ -722,10 +723,10 @@
 			}
 			e.attrs = e.attrs.trim();
 			e.file = new String(
-					IO.readFully(new File(db.getWorkTree(), pathName)));
+					IO.readFully(new File(db.getWorkTree(), pathName)), UTF_8);
 			DirCacheEntry dce = dirCache.getEntry(pathName);
 			ObjectLoader open = walk.getObjectReader().open(dce.getObjectId());
-			e.index = new String(open.getBytes());
+			e.index = new String(open.getBytes(), UTF_8);
 			e.indexContentLength = dce.getLength();
 		}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java
index ca86d81..98dfcc0 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java
@@ -148,7 +148,7 @@
 			git1.push().setRemote("test").setRefSpecs(spec).call();
 			assertEquals("1:test, 2:" + uri + ", 3:\n" + "refs/heads/master "
 					+ commit.getName() + " refs/heads/x "
-					+ ObjectId.zeroId().name(), read(hookOutput));
+					+ ObjectId.zeroId().name() + "\n", read(hookOutput));
 		}
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java
index 2b97b30..9b12011 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java
@@ -82,6 +82,8 @@
 
 	private File indexFile;
 
+	private File indexNestedFile;
+
 	private File untrackedFile;
 
 	private DirCacheEntry prestage;
@@ -97,7 +99,7 @@
 		indexFile = writeTrashFile("a.txt", "content");
 
 		// create nested file
-		writeTrashFile("dir/b.txt", "content");
+		indexNestedFile = writeTrashFile("dir/b.txt", "content");
 
 		// add files and commit them
 		git.add().addFilepattern("a.txt").addFilepattern("dir/b.txt").call();
@@ -119,13 +121,16 @@
 			AmbiguousObjectException, IOException, GitAPIException {
 		setupRepository();
 		ObjectId prevHead = db.resolve(Constants.HEAD);
-		assertSameAsHead(git.reset().setMode(ResetType.HARD)
+		ResetCommand reset = git.reset();
+		assertSameAsHead(reset.setMode(ResetType.HARD)
 				.setRef(initialCommit.getName()).call());
+		assertFalse("reflog should be enabled", reset.isReflogDisabled());
 		// check if HEAD points to initial commit now
 		ObjectId head = db.resolve(Constants.HEAD);
 		assertEquals(initialCommit, head);
 		// check if files were removed
 		assertFalse(indexFile.exists());
+		assertFalse(indexNestedFile.exists());
 		assertTrue(untrackedFile.exists());
 		// fileInIndex must no longer be in HEAD and in the index
 		String fileInIndexPath = indexFile.getAbsolutePath();
@@ -148,6 +153,7 @@
 		assertEquals(initialCommit, head);
 		// check if files were removed
 		assertFalse(indexFile.exists());
+		assertFalse(indexNestedFile.exists());
 		assertTrue(untrackedFile.exists());
 		// fileInIndex must no longer be in HEAD and in the index
 		String fileInIndexPath = indexFile.getAbsolutePath();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java
index 196c4f7..08553e1 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java
@@ -415,6 +415,14 @@
 		}
 	}
 
+	@Test
+	public void testFileNameWithLineTerminator() {
+		assertMatched("a?", "a\r");
+		assertMatched("a?", "dir/a\r");
+		assertMatched("*a", "\ra");
+		assertMatched("dir/*a*", "dir/\ra\r");
+	}
+
 	/**
 	 * Check for a match. If target ends with "/", match will assume that the
 	 * target is meant to be a directory.
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java
index f0d3c36..f4ccf05 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java
@@ -42,6 +42,7 @@
  */
 package org.eclipse.jgit.attributes;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.attributes.Attribute.State.SET;
 import static org.eclipse.jgit.attributes.Attribute.State.UNSET;
 import static org.junit.Assert.assertEquals;
@@ -88,7 +89,7 @@
 		String attributeFileContent = "*.type1 A -B C=value\n"
 				+ "*.type2 -A B C=value2";
 
-		is = new ByteArrayInputStream(attributeFileContent.getBytes());
+		is = new ByteArrayInputStream(attributeFileContent.getBytes(UTF_8));
 		AttributesNode node = new AttributesNode();
 		node.parse(is);
 		assertAttribute("file.type1", node,
@@ -102,7 +103,7 @@
 		String attributeFileContent = "!*.type1 A -B C=value\n"
 				+ "!*.type2 -A B C=value2";
 
-		is = new ByteArrayInputStream(attributeFileContent.getBytes());
+		is = new ByteArrayInputStream(attributeFileContent.getBytes(UTF_8));
 		AttributesNode node = new AttributesNode();
 		node.parse(is);
 		assertAttribute("file.type1", node, new Attributes());
@@ -113,7 +114,7 @@
 	public void testEmptyNegativeAttributeKey() throws IOException {
 		String attributeFileContent = "*.type1 - \n" //
 				+ "*.type2 -   -A";
-		is = new ByteArrayInputStream(attributeFileContent.getBytes());
+		is = new ByteArrayInputStream(attributeFileContent.getBytes(UTF_8));
 		AttributesNode node = new AttributesNode();
 		node.parse(is);
 		assertAttribute("file.type1", node, new Attributes());
@@ -125,7 +126,7 @@
 		String attributeFileContent = "*.type1 = \n" //
 				+ "*.type2 =value\n"//
 				+ "*.type3 attr=\n";
-		is = new ByteArrayInputStream(attributeFileContent.getBytes());
+		is = new ByteArrayInputStream(attributeFileContent.getBytes(UTF_8));
 		AttributesNode node = new AttributesNode();
 		node.parse(is);
 		assertAttribute("file.type1", node, new Attributes());
@@ -140,7 +141,7 @@
 				+ "    \n" //
 				+ "*.type2 -A B C=value2";
 
-		is = new ByteArrayInputStream(attributeFileContent.getBytes());
+		is = new ByteArrayInputStream(attributeFileContent.getBytes(UTF_8));
 		AttributesNode node = new AttributesNode();
 		node.parse(is);
 		assertAttribute("file.type1", node,
@@ -156,7 +157,7 @@
 				+ "*.type3  \t\t   B\n" //
 				+ "*.type3\t-A";//
 
-		is = new ByteArrayInputStream(attributeFileContent.getBytes());
+		is = new ByteArrayInputStream(attributeFileContent.getBytes(UTF_8));
 		AttributesNode node = new AttributesNode();
 		node.parse(is);
 		assertAttribute("file.type1", node,
@@ -170,7 +171,7 @@
 	public void testDoubleAsteriskAtEnd() throws IOException {
 		String attributeFileContent = "dir/** \tA -B\tC=value";
 
-		is = new ByteArrayInputStream(attributeFileContent.getBytes());
+		is = new ByteArrayInputStream(attributeFileContent.getBytes(UTF_8));
 		AttributesNode node = new AttributesNode();
 		node.parse(is);
 		assertAttribute("dir", node,
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffEntryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffEntryTest.java
index 73c230a..de76811 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffEntryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffEntryTest.java
@@ -59,9 +59,12 @@
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.DirCacheEditor;
 import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
+import org.eclipse.jgit.internal.storage.file.FileRepository;
 import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.junit.JGitTestUtil;
 import org.eclipse.jgit.junit.RepositoryTestCase;
 import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.treewalk.EmptyTreeIterator;
 import org.eclipse.jgit.treewalk.FileTreeIterator;
@@ -417,4 +420,64 @@
 			assertEquals(FileMode.REGULAR_FILE, diff.getOldMode());
 		}
 	}
+
+	@Test
+	public void shouldReportSubmoduleReplacedByFileMove() throws Exception {
+		// Create a submodule
+		FileRepository submoduleStandalone = createWorkRepository();
+		JGitTestUtil.writeTrashFile(submoduleStandalone, "fileInSubmodule",
+				"submodule");
+		Git submoduleStandaloneGit = Git.wrap(submoduleStandalone);
+		submoduleStandaloneGit.add().addFilepattern("fileInSubmodule").call();
+		submoduleStandaloneGit.commit().setMessage("add file to submodule")
+				.call();
+
+		Repository submodule_db = Git.wrap(db).submoduleAdd()
+				.setPath("modules/submodule")
+				.setURI(submoduleStandalone.getDirectory().toURI().toString())
+				.call();
+		File submodule_trash = submodule_db.getWorkTree();
+		addRepoToClose(submodule_db);
+		writeTrashFile("fileInRoot", "root");
+		Git rootGit = Git.wrap(db);
+		rootGit.add().addFilepattern("fileInRoot").call();
+		rootGit.commit().setMessage("add submodule and root file").call();
+		// Dummy change on fileInRoot
+		writeTrashFile("fileInRoot", "changed");
+		rootGit.add().addFilepattern("fileInRoot").call();
+		RevCommit firstCommit = rootGit.commit().setMessage("change root file")
+				.call();
+		// Remove the submodule again and move fileInRoot into that subfolder
+		rootGit.rm().setCached(true).addFilepattern("modules/submodule").call();
+		recursiveDelete(submodule_trash);
+		JGitTestUtil.deleteTrashFile(db, "fileInRoot");
+		// Move the fileInRoot file
+		writeTrashFile("modules/submodule/fileInRoot", "changed");
+		rootGit.rm().addFilepattern("fileInRoot").addFilepattern("modules/")
+				.call();
+		rootGit.add().addFilepattern("modules/").call();
+		RevCommit secondCommit = rootGit.commit()
+				.setMessage("remove submodule and move root file")
+				.call();
+		// Diff should report submodule having been deleted and file moved
+		// (deleted and added)
+		try (TreeWalk walk = new TreeWalk(db)) {
+			walk.addTree(firstCommit.getTree());
+			walk.addTree(secondCommit.getTree());
+			walk.setRecursive(true);
+			List<DiffEntry> diffs = DiffEntry.scan(walk);
+			assertEquals(3, diffs.size());
+			DiffEntry e = diffs.get(0);
+			assertEquals(DiffEntry.ChangeType.DELETE, e.getChangeType());
+			assertEquals("fileInRoot", e.getOldPath());
+			e = diffs.get(1);
+			assertEquals(DiffEntry.ChangeType.DELETE, e.getChangeType());
+			assertEquals("modules/submodule", e.getOldPath());
+			assertEquals(FileMode.GITLINK, e.getOldMode());
+			e = diffs.get(2);
+			assertEquals(DiffEntry.ChangeType.ADD, e.getChangeType());
+			assertEquals("modules/submodule/fileInRoot", e.getNewPath());
+		}
+
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RawTextTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RawTextTest.java
index 5885d9b..178d620 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RawTextTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RawTextTest.java
@@ -66,13 +66,16 @@
 	}
 
 	@Test
-	public void testBinary() {
+	public void testNul() {
 		String input = "foo-a\nf\0o-b\n";
 		byte[] data = Constants.encodeASCII(input);
 		final RawText a = new RawText(data);
 		assertArrayEquals(a.content, data);
-		assertEquals(a.size(), 1);
-		assertEquals(a.getString(0, 1, false), input);
+		assertEquals(2, a.size());
+		assertEquals("foo-a\n", a.getString(0, 1, false));
+		assertEquals("f\0o-b\n", a.getString(1, 2, false));
+		assertEquals("foo-a", a.getString(0, 1, true));
+		assertEquals("f\0o-b", a.getString(1, 2, true));
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java
index d12f302..b6291bf 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java
@@ -89,6 +89,7 @@
 		assertEquals(0, e.getRawMode());
 		try {
 			b.add(e);
+			fail("did not reject unset file mode");
 		} catch (IllegalArgumentException err) {
 			assertEquals("FileMode not set for path a", err.getMessage());
 		}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java
index 92f0cf6..1f6861b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java
@@ -52,16 +52,20 @@
 import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
 import java.io.File;
-import java.io.FileReader;
 import java.io.IOException;
 import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.text.MessageFormat;
 import java.util.HashMap;
 import java.util.Map;
 
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.InvalidRefNameException;
 import org.eclipse.jgit.api.errors.InvalidRemoteException;
-import org.eclipse.jgit.api.errors.RefNotFoundException;
+import org.eclipse.jgit.gitrepo.RepoCommand.RemoteFile;
+import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.junit.JGitTestUtil;
 import org.eclipse.jgit.junit.RepositoryTestCase;
 import org.eclipse.jgit.lib.BlobBasedConfig;
@@ -73,6 +77,7 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.treewalk.TreeWalk;
 import org.eclipse.jgit.util.FS;
 import org.junit.Test;
 
@@ -139,8 +144,9 @@
 		resolveRelativeUris();
 	}
 
-	class IndexedRepos implements RepoCommand.RemoteReader {
+	static class IndexedRepos implements RepoCommand.RemoteReader {
 		Map<String, Repository> uriRepoMap;
+
 		IndexedRepos() {
 			uriRepoMap = new HashMap<>();
 		}
@@ -169,19 +175,21 @@
 		}
 
 		@Override
-		public byte[] readFile(String uri, String refName, String path)
-			throws GitAPIException, IOException {
+		public RemoteFile readFileWithMode(String uri, String ref, String path)
+				throws GitAPIException, IOException {
 			Repository repo = uriRepoMap.get(uri);
+			ObjectId refCommitId = sha1(uri, ref);
+			if (refCommitId == null) {
+				throw new InvalidRefNameException(MessageFormat
+						.format(JGitText.get().refNotResolved, ref));
+			}
+			RevCommit commit = repo.parseCommit(refCommitId);
+			TreeWalk tw = TreeWalk.forPath(repo, path, commit.getTree());
 
-			String idStr = refName + ":" + path;
-			ObjectId id = repo.resolve(idStr);
-			if (id == null) {
-				throw new RefNotFoundException(
-					String.format("repo %s does not have %s", repo.toString(), idStr));
-			}
-			try (ObjectReader reader = repo.newObjectReader()) {
-				return reader.open(id).getCachedBytes(Integer.MAX_VALUE);
-			}
+			// TODO(ifrade): Cope better with big files (e.g. using InputStream
+			// instead of byte[])
+			return new RemoteFile(tw.getObjectReader().open(tw.getObjectId(0))
+					.getCachedBytes(Integer.MAX_VALUE), tw.getFileMode(0));
 		}
 	}
 
@@ -199,6 +207,15 @@
 		return r;
 	}
 
+	private static void assertContents(Path path, String expected)
+			throws IOException {
+		try (BufferedReader reader = Files.newBufferedReader(path, UTF_8)) {
+			String content = reader.readLine();
+			assertEquals("Unexpected content in " + path.getFileName(),
+					expected, content);
+		}
+	}
+
 	@Test
 	public void runTwiceIsNOP() throws Exception {
 		try (Repository child = cloneRepository(groupADb, true);
@@ -474,12 +491,7 @@
 			.call();
 		File hello = new File(db.getWorkTree(), "foo/hello.txt");
 		assertTrue("submodule should be checked out", hello.exists());
-		try (BufferedReader reader = new BufferedReader(
-				new FileReader(hello))) {
-			String content = reader.readLine();
-			assertEquals("submodule content should be as expected",
-					"master world", content);
-		}
+		assertContents(hello.toPath(), "master world");
 	}
 
 	@Test
@@ -565,20 +577,66 @@
 		// The original file should exist
 		File hello = new File(localDb.getWorkTree(), "foo/hello.txt");
 		assertTrue("The original file should exist", hello.exists());
-		try (BufferedReader reader = new BufferedReader(
-				new FileReader(hello))) {
-			String content = reader.readLine();
-			assertEquals("The original file should have expected content",
-					"master world", content);
-		}
+		assertFalse("The original file should not be executable",
+				hello.canExecute());
+		assertContents(hello.toPath(), "master world");
 		// The dest file should also exist
 		hello = new File(localDb.getWorkTree(), "Hello");
 		assertTrue("The destination file should exist", hello.exists());
-		try (BufferedReader reader = new BufferedReader(
-				new FileReader(hello))) {
+		assertFalse("The destination file should not be executable",
+				hello.canExecute());
+		assertContents(hello.toPath(), "master world");
+	}
+
+	@Test
+	public void testRepoManifestCopyFile_executable() throws Exception {
+		try (Git git = new Git(defaultDb)) {
+			git.checkout().setName("master").call();
+			File f = JGitTestUtil.writeTrashFile(defaultDb, "hello.sh",
+					"content of the executable file");
+			f.setExecutable(true);
+			git.add().addFilepattern("hello.sh").call();
+			git.commit().setMessage("Add binary file").call();
+		}
+
+		Repository localDb = createWorkRepository();
+		StringBuilder xmlContent = new StringBuilder();
+		xmlContent.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+				.append("<manifest>")
+				.append("<remote name=\"remote1\" fetch=\".\" />")
+				.append("<default revision=\"master\" remote=\"remote1\" />")
+				.append("<project path=\"foo\" name=\"").append(defaultUri)
+				.append("\">")
+				.append("<copyfile src=\"hello.sh\" dest=\"copy-hello.sh\" />")
+				.append("</project>").append("</manifest>");
+		JGitTestUtil.writeTrashFile(localDb, "manifest.xml",
+				xmlContent.toString());
+		RepoCommand command = new RepoCommand(localDb);
+		command.setPath(
+				localDb.getWorkTree().getAbsolutePath() + "/manifest.xml")
+				.setURI(rootUri).call();
+
+		// The original file should exist and be an executable
+		File hello = new File(localDb.getWorkTree(), "foo/hello.sh");
+		assertTrue("The original file should exist", hello.exists());
+		assertTrue("The original file must be executable", hello.canExecute());
+		try (BufferedReader reader = Files.newBufferedReader(hello.toPath(),
+				UTF_8)) {
+			String content = reader.readLine();
+			assertEquals("The original file should have expected content",
+					"content of the executable file", content);
+		}
+
+		// The destination file should also exist and be an executable
+		hello = new File(localDb.getWorkTree(), "copy-hello.sh");
+		assertTrue("The destination file should exist", hello.exists());
+		assertTrue("The destination file must be executable",
+				hello.canExecute());
+		try (BufferedReader reader = Files.newBufferedReader(hello.toPath(),
+				UTF_8)) {
 			String content = reader.readLine();
 			assertEquals("The destination file should have expected content",
-					"master world", content);
+					"content of the executable file", content);
 		}
 	}
 
@@ -610,8 +668,8 @@
 			assertTrue("The .gitmodules file should exist",
 					gitmodules.exists());
 			// The first line of .gitmodules file should be expected
-			try (BufferedReader reader = new BufferedReader(
-					new FileReader(gitmodules))) {
+			try (BufferedReader reader = Files
+					.newBufferedReader(gitmodules.toPath(), UTF_8)) {
 				String content = reader.readLine();
 				assertEquals(
 						"The first line of .gitmodules file should be as expected",
@@ -644,8 +702,8 @@
 			.setURI(rootUri)
 			.call();
 		File hello = new File(db.getWorkTree(), "foo/hello.txt");
-		try (BufferedReader reader = new BufferedReader(
-				new FileReader(hello))) {
+		try (BufferedReader reader = Files.newBufferedReader(hello.toPath(),
+				UTF_8)) {
 			String content = reader.readLine();
 			assertEquals("submodule content should be as expected",
 					"branch world", content);
@@ -671,12 +729,7 @@
 			.setURI(rootUri)
 			.call();
 		File hello = new File(db.getWorkTree(), "foo/hello.txt");
-		try (BufferedReader reader = new BufferedReader(
-				new FileReader(hello))) {
-			String content = reader.readLine();
-			assertEquals("submodule content should be as expected",
-					"branch world", content);
-		}
+		assertContents(hello.toPath(), "branch world");
 	}
 
 	@Test
@@ -698,12 +751,7 @@
 			.setURI(rootUri)
 			.call();
 		File hello = new File(db.getWorkTree(), "foo/hello.txt");
-		try (BufferedReader reader = new BufferedReader(
-				new FileReader(hello))) {
-			String content = reader.readLine();
-			assertEquals("submodule content should be as expected",
-					"branch world", content);
-		}
+		assertContents(hello.toPath(), "branch world");
 	}
 
 	@Test
@@ -771,12 +819,69 @@
 			assertFalse("The foo/Hello file should be skipped",
 					foohello.exists());
 			// The content of Hello file should be expected
-			try (BufferedReader reader = new BufferedReader(
-					new FileReader(hello))) {
+			assertContents(hello.toPath(), "branch world");
+		}
+	}
+
+	@Test
+	public void testCopyFileBare_executable() throws Exception {
+		try (Git git = new Git(defaultDb)) {
+			git.checkout().setName(BRANCH).call();
+			File f = JGitTestUtil.writeTrashFile(defaultDb, "hello.sh",
+					"content of the executable file");
+			f.setExecutable(true);
+			git.add().addFilepattern("hello.sh").call();
+			git.commit().setMessage("Add binary file").call();
+		}
+
+		Repository remoteDb = createBareRepository();
+		Repository tempDb = createWorkRepository();
+
+		StringBuilder xmlContent = new StringBuilder();
+		xmlContent.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+				.append("<manifest>")
+				.append("<remote name=\"remote1\" fetch=\".\" />")
+				.append("<default revision=\"master\" remote=\"remote1\" />")
+				.append("<project path=\"foo\" name=\"").append(defaultUri)
+				.append("\" revision=\"").append(BRANCH)
+				.append("\" >")
+				.append("<copyfile src=\"hello.txt\" dest=\"Hello\" />")
+				.append("<copyfile src=\"hello.txt\" dest=\"foo/Hello\" />")
+				.append("<copyfile src=\"hello.sh\" dest=\"copy-hello.sh\" />")
+				.append("</project>").append("</manifest>");
+		JGitTestUtil.writeTrashFile(tempDb, "manifest.xml",
+				xmlContent.toString());
+		RepoCommand command = new RepoCommand(remoteDb);
+		command.setPath(
+				tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml")
+				.setURI(rootUri).call();
+		// Clone it
+		File directory = createTempDirectory("testCopyFileBare");
+		try (Repository localDb = Git.cloneRepository().setDirectory(directory)
+				.setURI(remoteDb.getDirectory().toURI().toString()).call()
+				.getRepository()) {
+			// The Hello file should exist
+			File hello = new File(localDb.getWorkTree(), "Hello");
+			assertTrue("The Hello file should exist", hello.exists());
+			// The foo/Hello file should be skipped.
+			File foohello = new File(localDb.getWorkTree(), "foo/Hello");
+			assertFalse("The foo/Hello file should be skipped",
+					foohello.exists());
+			// The content of Hello file should be expected
+			try (BufferedReader reader = Files.newBufferedReader(hello.toPath(),
+					UTF_8)) {
 				String content = reader.readLine();
 				assertEquals("The Hello file should have expected content",
 						"branch world", content);
 			}
+
+			// The executable file must be there and preserve the executable bit
+			File helloSh = new File(localDb.getWorkTree(), "copy-hello.sh");
+			assertTrue("Destination file should exist", helloSh.exists());
+			assertContents(helloSh.toPath(), "content of the executable file");
+			assertTrue("Destination file should be executable",
+					helloSh.canExecute());
+
 		}
 	}
 
@@ -829,8 +934,8 @@
 		// The .gitmodules file should have 'submodule "bar"' and shouldn't
 		// have
 		// 'submodule "foo"' lines.
-		try (BufferedReader reader = new BufferedReader(
-				new FileReader(dotmodules))) {
+		try (BufferedReader reader = Files
+				.newBufferedReader(dotmodules.toPath(), UTF_8)) {
 			boolean foo = false;
 			boolean bar = false;
 			while (true) {
@@ -879,8 +984,8 @@
 		}
 
 		// Check .gitmodules file
-		try (BufferedReader reader = new BufferedReader(
-				new FileReader(dotmodules))) {
+		try (BufferedReader reader = Files
+				.newBufferedReader(dotmodules.toPath(), UTF_8)) {
 			boolean foo = false;
 			boolean foobar = false;
 			boolean a = false;
@@ -935,8 +1040,8 @@
 			.call();
 		File hello = new File(localDb.getWorkTree(), "foo/hello.txt");
 		assertTrue("submodule should be checked out", hello.exists());
-		try (BufferedReader reader = new BufferedReader(
-				new FileReader(hello))) {
+		try (BufferedReader reader = Files.newBufferedReader(hello.toPath(),
+				UTF_8)) {
 			String content = reader.readLine();
 			assertEquals("submodule content should be as expected",
 					"master world", content);
@@ -1074,8 +1179,9 @@
 					".gitattributes");
 			assertTrue("The .gitattributes file should exist",
 					gitattributes.exists());
-			try (BufferedReader reader = new BufferedReader(
-					new FileReader(gitattributes));) {
+			try (BufferedReader reader = Files
+					.newBufferedReader(gitattributes.toPath(),
+					UTF_8)) {
 				String content = reader.readLine();
 				assertEquals(".gitattributes content should be as expected",
 						"/test a1 a2", content);
@@ -1142,12 +1248,7 @@
 			.setURI(rootUri)
 			.call();
 		File hello = new File(db.getWorkTree(), "foo/hello.txt");
-		try (BufferedReader reader = new BufferedReader(
-				new FileReader(hello))) {
-			String content = reader.readLine();
-			assertEquals("submodule content should be as expected",
-					"branch world", content);
-		}
+		assertContents(hello.toPath(), "branch world");
 	}
 
 	@Test
@@ -1169,8 +1270,8 @@
 			.setURI(rootUri)
 			.call();
 		File hello = new File(db.getWorkTree(), "foo/hello.txt");
-		try (BufferedReader reader = new BufferedReader(
-				new FileReader(hello))) {
+		try (BufferedReader reader = Files.newBufferedReader(hello.toPath(),
+				UTF_8)) {
 			String content = reader.readLine();
 			assertEquals("submodule content should be as expected",
 					"branch world", content);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java
index 2a1721e..4bd1dab 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java
@@ -512,6 +512,15 @@
 		assertMatched("x/**/", "x/y/a/");
 	}
 
+	@Test
+	public void testFileNameWithLineTerminator() {
+		assertMatched("a?", "a\r");
+		assertMatched("a?", "dir/a\r");
+		assertMatched("a?", "a\r/file");
+		assertMatched("*a", "\ra");
+		assertMatched("dir/*a*", "dir/\ra\r");
+	}
+
 	private void assertMatched(String pattern, String path) {
 		boolean match = match(pattern, path);
 		String result = path + " is " + (match ? "ignored" : "not ignored")
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsFsckTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsFsckTest.java
index 804d744..c181125 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsFsckTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsFsckTest.java
@@ -266,4 +266,59 @@
 				"refs/heads/master");
 	}
 
+	private ObjectId insertGitModules(String contents) throws IOException {
+		ObjectId blobId = ins.insert(Constants.OBJ_BLOB,
+				Constants.encode(contents));
+
+		byte[] blobIdBytes = new byte[OBJECT_ID_LENGTH];
+		blobId.copyRawTo(blobIdBytes, 0);
+		byte[] data = concat(encodeASCII("100644 .gitmodules\0"), blobIdBytes);
+		ins.insert(Constants.OBJ_TREE, data);
+		ins.flush();
+
+		return blobId;
+	}
+
+	@Test
+	public void testInvalidGitModules() throws Exception {
+		String fakeGitmodules = new StringBuilder()
+				.append("[submodule \"test\"]\n")
+				.append("    path = xlib\n")
+				.append("    url = https://example.com/repo/xlib.git\n\n")
+				.append("[submodule \"test2\"]\n")
+				.append("    path = zlib\n")
+				.append("    url = -upayload.sh\n")
+				.toString();
+
+		ObjectId blobId = insertGitModules(fakeGitmodules);
+
+		DfsFsck fsck = new DfsFsck(repo);
+		FsckError errors = fsck.check(null);
+		assertEquals(errors.getCorruptObjects().size(), 1);
+
+		CorruptObject error = errors.getCorruptObjects().iterator().next();
+		assertEquals(error.getId(), blobId);
+		assertEquals(error.getType(), Constants.OBJ_BLOB);
+		assertEquals(error.getErrorType(), ErrorType.GITMODULES_URL);
+	}
+
+
+	@Test
+	public void testValidGitModules() throws Exception {
+		String fakeGitmodules = new StringBuilder()
+				.append("[submodule \"test\"]\n")
+				.append("    path = xlib\n")
+				.append("    url = https://example.com/repo/xlib.git\n\n")
+				.append("[submodule \"test2\"]\n")
+				.append("    path = zlib\n")
+				.append("    url = ok/path\n")
+				.toString();
+
+		insertGitModules(fakeGitmodules);
+
+		DfsFsck fsck = new DfsFsck(repo);
+		FsckError errors = fsck.check(null);
+		assertEquals(errors.getCorruptObjects().size(), 0);
+	}
+
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileRepositoryBuilderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileRepositoryBuilderTest.java
index deffa04..f6cb558 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileRepositoryBuilderTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileRepositoryBuilderTest.java
@@ -43,14 +43,16 @@
 
 package org.eclipse.jgit.internal.storage.file;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import java.io.BufferedWriter;
 import java.io.File;
-import java.io.FileWriter;
 import java.io.IOException;
+import java.nio.file.Files;
 
 import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
 import org.eclipse.jgit.lib.ConfigConstants;
@@ -120,18 +122,19 @@
 		Repository repo1 = createWorkRepository();
 		File dir = createTempDirectory("dir");
 		File dotGit = new File(dir, Constants.DOT_GIT);
-		try (FileWriter writer = new FileWriter(dotGit)) {
-			writer.append("gitdir: " + repo1.getDirectory().getAbsolutePath()).close();
-			FileRepositoryBuilder builder = new FileRepositoryBuilder();
-
-			builder.setWorkTree(dir);
-			builder.setMustExist(true);
-			Repository repo2 = builder.build();
-
-			assertEquals(repo1.getDirectory().getAbsolutePath(), repo2
-					.getDirectory().getAbsolutePath());
-			assertEquals(dir, repo2.getWorkTree());
+		try (BufferedWriter writer = Files.newBufferedWriter(dotGit.toPath(),
+				UTF_8)) {
+			writer.append("gitdir: " + repo1.getDirectory().getAbsolutePath());
 		}
+		FileRepositoryBuilder builder = new FileRepositoryBuilder();
+
+		builder.setWorkTree(dir);
+		builder.setMustExist(true);
+		Repository repo2 = builder.build();
+
+		assertEquals(repo1.getDirectory().getAbsolutePath(),
+				repo2.getDirectory().getAbsolutePath());
+		assertEquals(dir, repo2.getWorkTree());
 	}
 
 	@Test
@@ -140,20 +143,20 @@
 		File dir = new File(repo1.getWorkTree(), "dir");
 		assertTrue(dir.mkdir());
 		File dotGit = new File(dir, Constants.DOT_GIT);
-		try (FileWriter writer = new FileWriter(dotGit)) {
-			writer.append("gitdir: ../" + Constants.DOT_GIT).close();
-
-			FileRepositoryBuilder builder = new FileRepositoryBuilder();
-			builder.setWorkTree(dir);
-			builder.setMustExist(true);
-			Repository repo2 = builder.build();
-
-			// The tmp directory may be a symlink so the actual path
-			// may not
-			assertEquals(repo1.getDirectory().getCanonicalPath(), repo2
-					.getDirectory().getCanonicalPath());
-			assertEquals(dir, repo2.getWorkTree());
+		try (BufferedWriter writer = Files.newBufferedWriter(dotGit.toPath(),
+				UTF_8)) {
+			writer.append("gitdir: ../" + Constants.DOT_GIT);
 		}
+		FileRepositoryBuilder builder = new FileRepositoryBuilder();
+		builder.setWorkTree(dir);
+		builder.setMustExist(true);
+		Repository repo2 = builder.build();
+
+		// The tmp directory may be a symlink so the actual path
+		// may not
+		assertEquals(repo1.getDirectory().getCanonicalPath(),
+				repo2.getDirectory().getCanonicalPath());
+		assertEquals(dir, repo2.getWorkTree());
 	}
 
 	@Test
@@ -161,22 +164,23 @@
 		Repository repo1 = createWorkRepository();
 		File dir = createTempDirectory("dir");
 		File dotGit = new File(dir, Constants.DOT_GIT);
-		try (FileWriter writer = new FileWriter(dotGit)) {
+		try (BufferedWriter writer = Files.newBufferedWriter(dotGit.toPath(),
+				UTF_8)) {
 			writer.append(
-					"gitdir: " + repo1.getDirectory().getAbsolutePath()).close();
-			FileRepositoryBuilder builder = new FileRepositoryBuilder();
-
-			builder.setWorkTree(dir);
-			builder.findGitDir(dir);
-			assertEquals(repo1.getDirectory().getAbsolutePath(), builder
-					.getGitDir().getAbsolutePath());
-			builder.setMustExist(true);
-			Repository repo2 = builder.build();
-
-			// The tmp directory may be a symlink
-			assertEquals(repo1.getDirectory().getCanonicalPath(), repo2
-					.getDirectory().getCanonicalPath());
-			assertEquals(dir, repo2.getWorkTree());
+					"gitdir: " + repo1.getDirectory().getAbsolutePath());
 		}
+		FileRepositoryBuilder builder = new FileRepositoryBuilder();
+
+		builder.setWorkTree(dir);
+		builder.findGitDir(dir);
+		assertEquals(repo1.getDirectory().getAbsolutePath(),
+				builder.getGitDir().getAbsolutePath());
+		builder.setMustExist(true);
+		Repository repo2 = builder.build();
+
+		// The tmp directory may be a symlink
+		assertEquals(repo1.getDirectory().getCanonicalPath(),
+				repo2.getDirectory().getCanonicalPath());
+		assertEquals(dir, repo2.getWorkTree());
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java
index cbb73bb..3ca689a 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java
@@ -42,6 +42,7 @@
 
 package org.eclipse.jgit.internal.storage.file;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
@@ -189,7 +190,8 @@
 
 		String commit = "d3148f9410b071edd4a4c85d2a43d1fa2574b0d2";
 		try (PrintWriter writer = new PrintWriter(
-				new File(repository.getDirectory(), Constants.SHALLOW))) {
+				new File(repository.getDirectory(), Constants.SHALLOW),
+				UTF_8.name())) {
 			writer.println(commit);
 		}
 		Set<ObjectId> shallowCommits = dir.getShallowCommits();
@@ -205,7 +207,8 @@
 
 		String commit = "X3148f9410b071edd4a4c85d2a43d1fa2574b0d2";
 		try (PrintWriter writer = new PrintWriter(
-				new File(repository.getDirectory(), Constants.SHALLOW))) {
+				new File(repository.getDirectory(), Constants.SHALLOW),
+				UTF_8.name())) {
 			writer.println(commit);
 		}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogReaderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogReaderTest.java
index dc05eea..acdaf3a 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogReaderTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogReaderTest.java
@@ -44,6 +44,7 @@
 
 package org.eclipse.jgit.internal.storage.file;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
@@ -67,31 +68,31 @@
 public class ReflogReaderTest extends SampleDataRepositoryTestCase {
 
 	static byte[] oneLine = "da85355dfc525c9f6f3927b876f379f46ccf826e 3e7549db262d1e836d9bf0af7e22355468f1717c A O Thor Too <authortoo@wri.tr> 1243028200 +0200\tcommit: Add a toString for debugging to RemoteRefUpdate\n"
-			.getBytes();
+			.getBytes(UTF_8);
 
 	static byte[] twoLine = ("0000000000000000000000000000000000000000 c6734895958052a9dbc396cff4459dc1a25029ab A U Thor <thor@committer.au> 1243028201 -0100\tbranch: Created from rr/renamebranchv4\n"
 			+ "c6734895958052a9dbc396cff4459dc1a25029ab 54794942a18a237c57a80719afed44bb78172b10 Same A U Thor <same.author@example.com> 1243028202 +0100\trebase finished: refs/heads/rr/renamebranch5 onto c6e3b9fe2da0293f11eae202ec35fb343191a82d\n")
-			.getBytes();
+					.getBytes(UTF_8);
 
 	static byte[] twoLineWithAppendInProgress = ("0000000000000000000000000000000000000000 c6734895958052a9dbc396cff4459dc1a25029ab A U Thor <thor@committer.au> 1243028201 -0100\tbranch: Created from rr/renamebranchv4\n"
 			+ "c6734895958052a9dbc396cff4459dc1a25029ab 54794942a18a237c57a80719afed44bb78172b10 Same A U Thor <same.author@example.com> 1243028202 +0100\trebase finished: refs/heads/rr/renamebranch5 onto c6e3b9fe2da0293f11eae202ec35fb343191a82d\n"
 			+ "54794942a18a237c57a80719afed44bb78172b10 ")
-			.getBytes();
+					.getBytes(UTF_8);
 
 	static byte[] aLine = "1111111111111111111111111111111111111111 3e7549db262d1e836d9bf0af7e22355468f1717c A U Thor <thor@committer.au> 1243028201 -0100\tbranch: change to a\n"
-			.getBytes();
+			.getBytes(UTF_8);
 
 	static byte[] masterLine = "2222222222222222222222222222222222222222 3e7549db262d1e836d9bf0af7e22355468f1717c A U Thor <thor@committer.au> 1243028201 -0100\tbranch: change to master\n"
-			.getBytes();
+			.getBytes(UTF_8);
 
 	static byte[] headLine = "3333333333333333333333333333333333333333 3e7549db262d1e836d9bf0af7e22355468f1717c A U Thor <thor@committer.au> 1243028201 -0100\tbranch: change to HEAD\n"
-			.getBytes();
+			.getBytes(UTF_8);
 
 	static byte[] oneLineWithoutComment = "da85355dfc525c9f6f3927b876f379f46ccf826e 3e7549db262d1e836d9bf0af7e22355468f1717c A O Thor Too <authortoo@wri.tr> 1243028200 +0200\n"
-			.getBytes();
+			.getBytes(UTF_8);
 
 	static byte[] switchBranch = "0d43a6890a19fd657faad1c4cfbe3cb1b47851c3 4809df9c0d8bce5b00955563f77c5a9f25aa0d12 A O Thor Too <authortoo@wri.tr> 1315088009 +0200\tcheckout: moving from new/work to master\n"
-			.getBytes();
+			.getBytes(UTF_8);
 
 	@Test
 	public void testReadOneLine() throws Exception {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogWriterTest.java
index 1d188c3..a84be7e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogWriterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogWriterTest.java
@@ -42,6 +42,7 @@
  *******************************************************************************/
 package org.eclipse.jgit.internal.storage.file;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 
 import java.io.File;
@@ -73,9 +74,9 @@
 		writer.log("refs/heads/master", oldId, newId, ident,
 				"stash: Add\nmessage\r\nwith line feeds");
 
-		byte[] buffer = new byte[oneLine.getBytes().length];
+		byte[] buffer = new byte[oneLine.getBytes(UTF_8).length];
 		readReflog(buffer);
-		assertEquals(oneLine, new String(buffer));
+		assertEquals(oneLine, new String(buffer, UTF_8));
 	}
 
 	private void readReflog(byte[] buffer)
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java
index 0207322..96caa01 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java
@@ -333,9 +333,9 @@
 	public void test002_CreateBadTree() throws Exception {
 		// We won't create a tree entry with an empty filename
 		//
+		final TreeFormatter formatter = new TreeFormatter();
 		expectedException.expect(IllegalArgumentException.class);
 		expectedException.expectMessage(JGitText.get().invalidTreeZeroLengthName);
-		final TreeFormatter formatter = new TreeFormatter();
 		formatter.append("", FileMode.TREE,
 				ObjectId.fromString("4b825dc642cb6eb9a060e54bf8d69288fbee4904"));
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/WindowCacheGetTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/WindowCacheGetTest.java
index 01426ee..9063b65 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/WindowCacheGetTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/WindowCacheGetTest.java
@@ -141,7 +141,7 @@
 		}
 	}
 
-	private class TestObject {
+	private static class TestObject {
 		ObjectId id;
 
 		int type;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/transport/parser/FirstWantTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/transport/parser/FirstWantTest.java
new file mode 100644
index 0000000..b877c59
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/transport/parser/FirstWantTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2018, Google LLC.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.parser;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jgit.errors.PackProtocolException;
+import org.junit.Test;
+
+public class FirstWantTest {
+
+	@Test
+	public void testFirstWantWithOptions() throws PackProtocolException {
+		String line = "want b9d4d1eb2f93058814480eae9e1b67550f46ee38 "
+				+ "no-progress include-tag ofs-delta agent=JGit/unknown";
+
+		FirstWant r = FirstWant.fromLine(line);
+		assertEquals("want b9d4d1eb2f93058814480eae9e1b67550f46ee38",
+				r.getLine());
+		Set<String> capabilities = r.getCapabilities();
+		Set<String> expectedCapabilities = new HashSet<>(
+				Arrays.asList("no-progress", "include-tag", "ofs-delta"));
+		assertEquals(expectedCapabilities, capabilities);
+		assertEquals("JGit/unknown", r.getAgent());
+	}
+
+	@Test
+	public void testFirstWantWithoutOptions() throws PackProtocolException {
+		String line = "want b9d4d1eb2f93058814480eae9e1b67550f46ee38";
+
+		FirstWant r = FirstWant.fromLine(line);
+		assertEquals("want b9d4d1eb2f93058814480eae9e1b67550f46ee38",
+				r.getLine());
+		assertTrue(r.getCapabilities().isEmpty());
+		assertNull(r.getAgent());
+	}
+
+	private String makeFirstWantLine(String capability) {
+		return String.format("want b9d4d1eb2f93058814480eae9e1b67550f46ee38 %s", capability);
+	}
+
+	@Test
+	public void testFirstWantNoWhitespace() {
+		try {
+			FirstWant.fromLine(
+					"want b9d4d1eb2f93058814480eae9e1b67550f400000capability");
+			fail("Accepting first want line without SP between oid and first capability");
+		} catch (PackProtocolException e) {
+			// pass
+		}
+	}
+
+	@Test
+	public void testFirstWantOnlyWhitespace() throws PackProtocolException {
+		FirstWant r = FirstWant
+				.fromLine("want b9d4d1eb2f93058814480eae9e1b67550f46ee38 ");
+		assertEquals("want b9d4d1eb2f93058814480eae9e1b67550f46ee38",
+				r.getLine());
+	}
+
+	@Test
+	public void testFirstWantValidCapabilityNames()
+			throws PackProtocolException {
+		List<String> validNames = Arrays.asList(
+				"c", "cap", "C", "CAP", "1", "1cap", "cap-64k_test",
+				"-", "-cap",
+				"_", "_cap");
+
+		for (String capability: validNames) {
+			FirstWant r = FirstWant.fromLine(makeFirstWantLine(capability));
+			assertEquals(r.getCapabilities().size(), 1);
+			assertTrue(r.getCapabilities().contains(capability));
+		}
+	}
+
+	@Test
+	public void testFirstWantValidAgentName() throws PackProtocolException {
+		FirstWant r = FirstWant.fromLine(makeFirstWantLine("agent=pack.age/Version"));
+		assertEquals(r.getCapabilities().size(), 0);
+		assertEquals("pack.age/Version", r.getAgent());
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
index c4c4da8..21d8d66 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
@@ -48,6 +48,7 @@
 
 package org.eclipse.jgit.lib;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.concurrent.TimeUnit.DAYS;
 import static java.util.concurrent.TimeUnit.HOURS;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
@@ -66,11 +67,15 @@
 import java.io.IOException;
 import java.nio.file.Files;
 import java.text.MessageFormat;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.LinkedList;
+import java.util.List;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 
 import org.eclipse.jgit.api.MergeCommand.FastForwardMode;
 import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -78,6 +83,7 @@
 import org.eclipse.jgit.junit.MockSystemReader;
 import org.eclipse.jgit.merge.MergeConfig;
 import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.transport.RefSpec;
 import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.SystemReader;
 import org.junit.After;
@@ -94,6 +100,12 @@
 	// A non-ASCII whitespace character: U+2002 EN QUAD.
 	private static final char WS = '\u2002';
 
+	private static final String REFS_ORIGIN = "+refs/heads/*:refs/remotes/origin/*";
+
+	private static final String REFS_UPSTREAM = "+refs/heads/*:refs/remotes/upstream/*";
+
+	private static final String REFS_BACKUP = "+refs/heads/*:refs/remotes/backup/*";
+
 	@Rule
 	public ExpectedException expectedEx = ExpectedException.none();
 
@@ -690,11 +702,7 @@
 
 		assertEquals("", c.getString("a", null, "y"));
 		assertArrayEquals(new String[]{""}, c.getStringList("a", null, "y"));
-		try {
-			c.getInt("a", null, "y", 1);
-		} catch (IllegalArgumentException e) {
-			assertEquals("Invalid integer value: a.y=", e.getMessage());
-		}
+		assertEquals(1, c.getInt("a", null, "y", 1));
 
 		assertNull(c.getString("a", null, "z"));
 		assertArrayEquals(new String[]{}, c.getStringList("a", null, "z"));
@@ -711,11 +719,7 @@
 
 		assertNull(c.getString("a", null, "y"));
 		assertArrayEquals(new String[]{null}, c.getStringList("a", null, "y"));
-		try {
-			c.getInt("a", null, "y", 1);
-		} catch (IllegalArgumentException e) {
-			assertEquals("Invalid integer value: a.y=", e.getMessage());
-		}
+		assertEquals(1, c.getInt("a", null, "y", 1));
 
 		assertNull(c.getString("a", null, "z"));
 		assertArrayEquals(new String[]{}, c.getStringList("a", null, "z"));
@@ -801,11 +805,9 @@
 	public void testIncludeTooManyRecursions() throws IOException {
 		File config = tmp.newFile("config");
 		String include = "[include]\npath=" + pathToString(config) + "\n";
-		Files.write(config.toPath(), include.getBytes());
-		FileBasedConfig fbConfig = new FileBasedConfig(null, config,
-				FS.DETECTED);
+		Files.write(config.toPath(), include.getBytes(UTF_8));
 		try {
-			fbConfig.load();
+			loadConfig(config);
 			fail();
 		} catch (ConfigInvalidException cie) {
 			for (Throwable t = cie; t != null; t = t.getCause()) {
@@ -824,7 +826,7 @@
 		File config = tmp.newFile("config");
 
 		String fooBar = "[foo]\nbar=true\n";
-		Files.write(config.toPath(), fooBar.getBytes());
+		Files.write(config.toPath(), fooBar.getBytes(UTF_8));
 
 		Config parsed = parse("[include]\npath=" + pathToString(config) + "\n");
 		assertFalse(parsed.getBoolean("foo", "bar", false));
@@ -835,15 +837,13 @@
 			throws IOException, ConfigInvalidException {
 		File included = tmp.newFile("included");
 		String content = "[foo]\nbar=true\n";
-		Files.write(included.toPath(), content.getBytes());
+		Files.write(included.toPath(), content.getBytes(UTF_8));
 
 		File config = tmp.newFile("config");
 		content = "[Include]\npath=" + pathToString(included) + "\n";
-		Files.write(config.toPath(), content.getBytes());
+		Files.write(config.toPath(), content.getBytes(UTF_8));
 
-		FileBasedConfig fbConfig = new FileBasedConfig(null, config,
-				FS.DETECTED);
-		fbConfig.load();
+		FileBasedConfig fbConfig = loadConfig(config);
 		assertTrue(fbConfig.getBoolean("foo", "bar", false));
 	}
 
@@ -852,15 +852,13 @@
 			throws IOException, ConfigInvalidException {
 		File included = tmp.newFile("included");
 		String content = "[foo]\nbar=true\n";
-		Files.write(included.toPath(), content.getBytes());
+		Files.write(included.toPath(), content.getBytes(UTF_8));
 
 		File config = tmp.newFile("config");
 		content = "[include]\nPath=" + pathToString(included) + "\n";
-		Files.write(config.toPath(), content.getBytes());
+		Files.write(config.toPath(), content.getBytes(UTF_8));
 
-		FileBasedConfig fbConfig = new FileBasedConfig(null, config,
-				FS.DETECTED);
-		fbConfig.load();
+		FileBasedConfig fbConfig = loadConfig(config);
 		assertTrue(fbConfig.getBoolean("foo", "bar", false));
 	}
 
@@ -881,15 +879,13 @@
 		File included = tmp.newFile("included");
 		String includedPath = pathToString(included);
 		String content = "[include]\npath=\n";
-		Files.write(included.toPath(), content.getBytes());
+		Files.write(included.toPath(), content.getBytes(UTF_8));
 
 		File config = tmp.newFile("config");
 		String include = "[include]\npath=" + includedPath + "\n";
-		Files.write(config.toPath(), include.getBytes());
-		FileBasedConfig fbConfig = new FileBasedConfig(null, config,
-				FS.DETECTED);
+		Files.write(config.toPath(), include.getBytes(UTF_8));
 		try {
-			fbConfig.load();
+			loadConfig(config);
 			fail("Expected ConfigInvalidException");
 		} catch (ConfigInvalidException e) {
 			// Check that there is some exception in the chain that contains
@@ -904,6 +900,306 @@
 		}
 	}
 
+	@Test
+	public void testIncludeSetValueMustNotTouchIncludedLines1()
+			throws IOException, ConfigInvalidException {
+		File includedFile = createAllTypesIncludedContent();
+
+		File configFile = tmp.newFile("config");
+		String content = createAllTypesSampleContent("Alice Parker", false, 11,
+				21, 31, CoreConfig.AutoCRLF.FALSE,
+				"+refs/heads/*:refs/remotes/origin/*") + "\n[include]\npath="
+				+ pathToString(includedFile);
+		Files.write(configFile.toPath(), content.getBytes(UTF_8));
+
+		FileBasedConfig fbConfig = loadConfig(configFile);
+		assertValuesAsIncluded(fbConfig, REFS_ORIGIN, REFS_UPSTREAM);
+		assertSections(fbConfig, "user", "core", "remote", "include");
+
+		setAllValuesNew(fbConfig);
+		assertValuesAsIsSaveLoad(fbConfig, config -> {
+			assertValuesAsIncluded(config, REFS_BACKUP, REFS_UPSTREAM);
+			assertSections(fbConfig, "user", "core", "remote", "include");
+		});
+	}
+
+	@Test
+	public void testIncludeSetValueMustNotTouchIncludedLines2()
+			throws IOException, ConfigInvalidException {
+		File includedFile = createAllTypesIncludedContent();
+
+		File configFile = tmp.newFile("config");
+		String content = "[include]\npath=" + pathToString(includedFile) + "\n"
+				+ createAllTypesSampleContent("Alice Parker", false, 11, 21, 31,
+						CoreConfig.AutoCRLF.FALSE,
+						"+refs/heads/*:refs/remotes/origin/*");
+		Files.write(configFile.toPath(), content.getBytes(UTF_8));
+
+		FileBasedConfig fbConfig = loadConfig(configFile);
+		assertValuesAsConfig(fbConfig, REFS_UPSTREAM, REFS_ORIGIN);
+		assertSections(fbConfig, "include", "user", "core", "remote");
+
+		setAllValuesNew(fbConfig);
+		assertValuesAsIsSaveLoad(fbConfig, config -> {
+			assertValuesAsNew(config, REFS_UPSTREAM, REFS_BACKUP);
+			assertSections(fbConfig, "include", "user", "core", "remote");
+		});
+	}
+
+	@Test
+	public void testIncludeSetValueOnFileWithJustContainsInclude()
+			throws IOException, ConfigInvalidException {
+		File includedFile = createAllTypesIncludedContent();
+
+		File configFile = tmp.newFile("config");
+		String content = "[include]\npath=" + pathToString(includedFile);
+		Files.write(configFile.toPath(), content.getBytes(UTF_8));
+
+		FileBasedConfig fbConfig = loadConfig(configFile);
+		assertValuesAsIncluded(fbConfig, REFS_UPSTREAM);
+		assertSections(fbConfig, "include", "user", "core", "remote");
+
+		setAllValuesNew(fbConfig);
+		assertValuesAsIsSaveLoad(fbConfig, config -> {
+			assertValuesAsNew(config, REFS_UPSTREAM, REFS_BACKUP);
+			assertSections(fbConfig, "include", "user", "core", "remote");
+		});
+	}
+
+	@Test
+	public void testIncludeSetValueOnFileWithJustEmptySection1()
+			throws IOException, ConfigInvalidException {
+		File includedFile = createAllTypesIncludedContent();
+
+		File configFile = tmp.newFile("config");
+		String content = "[user]\n[include]\npath="
+				+ pathToString(includedFile);
+		Files.write(configFile.toPath(), content.getBytes(UTF_8));
+
+		FileBasedConfig fbConfig = loadConfig(configFile);
+		assertValuesAsIncluded(fbConfig, REFS_UPSTREAM);
+		assertSections(fbConfig, "user", "include", "core", "remote");
+
+		setAllValuesNew(fbConfig);
+		assertValuesAsIsSaveLoad(fbConfig, config -> {
+			assertValuesAsNewWithName(config, "Alice Muller", REFS_UPSTREAM,
+					REFS_BACKUP);
+			assertSections(fbConfig, "user", "include", "core", "remote");
+		});
+	}
+
+	@Test
+	public void testIncludeSetValueOnFileWithJustEmptySection2()
+			throws IOException, ConfigInvalidException {
+		File includedFile = createAllTypesIncludedContent();
+
+		File configFile = tmp.newFile("config");
+		String content = "[include]\npath=" + pathToString(includedFile)
+				+ "\n[user]";
+		Files.write(configFile.toPath(), content.getBytes(UTF_8));
+
+		FileBasedConfig fbConfig = loadConfig(configFile);
+		assertValuesAsIncluded(fbConfig, REFS_UPSTREAM);
+		assertSections(fbConfig, "include", "user", "core", "remote");
+
+		setAllValuesNew(fbConfig);
+		assertValuesAsIsSaveLoad(fbConfig, config -> {
+			assertValuesAsNew(config, REFS_UPSTREAM, REFS_BACKUP);
+			assertSections(fbConfig, "include", "user", "core", "remote");
+		});
+	}
+
+	@Test
+	public void testIncludeSetValueOnFileWithJustExistingSection1()
+			throws IOException, ConfigInvalidException {
+		File includedFile = createAllTypesIncludedContent();
+
+		File configFile = tmp.newFile("config");
+		String content = "[user]\nemail=alice@home\n[include]\npath="
+				+ pathToString(includedFile);
+		Files.write(configFile.toPath(), content.getBytes(UTF_8));
+
+		FileBasedConfig fbConfig = loadConfig(configFile);
+		assertValuesAsIncluded(fbConfig, REFS_UPSTREAM);
+		assertSections(fbConfig, "user", "include", "core", "remote");
+
+		setAllValuesNew(fbConfig);
+		assertValuesAsIsSaveLoad(fbConfig, config -> {
+			assertValuesAsNewWithName(config, "Alice Muller", REFS_UPSTREAM,
+					REFS_BACKUP);
+			assertSections(fbConfig, "user", "include", "core", "remote");
+		});
+	}
+
+	@Test
+	public void testIncludeSetValueOnFileWithJustExistingSection2()
+			throws IOException, ConfigInvalidException {
+		File includedFile = createAllTypesIncludedContent();
+
+		File configFile = tmp.newFile("config");
+		String content = "[include]\npath=" + pathToString(includedFile)
+				+ "\n[user]\nemail=alice@home\n";
+		Files.write(configFile.toPath(), content.getBytes(UTF_8));
+
+		FileBasedConfig fbConfig = loadConfig(configFile);
+		assertValuesAsIncluded(fbConfig, REFS_UPSTREAM);
+		assertSections(fbConfig, "include", "user", "core", "remote");
+
+		setAllValuesNew(fbConfig);
+		assertValuesAsIsSaveLoad(fbConfig, config -> {
+			assertValuesAsNew(config, REFS_UPSTREAM, REFS_BACKUP);
+			assertSections(fbConfig, "include", "user", "core", "remote");
+		});
+	}
+
+	@Test
+	public void testIncludeUnsetSectionMustNotTouchIncludedLines()
+			throws IOException, ConfigInvalidException {
+		File includedFile = tmp.newFile("included");
+		RefSpec includedRefSpec = new RefSpec(REFS_UPSTREAM);
+		String includedContent = "[remote \"origin\"]\n" + "fetch="
+				+ includedRefSpec;
+		Files.write(includedFile.toPath(), includedContent.getBytes(UTF_8));
+
+		File configFile = tmp.newFile("config");
+		RefSpec refSpec = new RefSpec(REFS_ORIGIN);
+		String content = "[include]\npath=" + pathToString(includedFile) + "\n"
+				+ "[remote \"origin\"]\n" + "fetch=" + refSpec;
+		Files.write(configFile.toPath(), content.getBytes(UTF_8));
+
+		FileBasedConfig fbConfig = loadConfig(configFile);
+
+		Consumer<FileBasedConfig> assertion = config -> {
+			assertEquals(Arrays.asList(includedRefSpec, refSpec),
+					config.getRefSpecs("remote", "origin", "fetch"));
+		};
+		assertion.accept(fbConfig);
+
+		fbConfig.unsetSection("remote", "origin");
+		assertValuesAsIsSaveLoad(fbConfig, config -> {
+			assertEquals(Collections.singletonList(includedRefSpec),
+					config.getRefSpecs("remote", "origin", "fetch"));
+		});
+	}
+
+	private File createAllTypesIncludedContent() throws IOException {
+		File includedFile = tmp.newFile("included");
+		String includedContent = createAllTypesSampleContent("Alice Muller",
+				true, 10, 20, 30, CoreConfig.AutoCRLF.TRUE,
+				"+refs/heads/*:refs/remotes/upstream/*");
+		Files.write(includedFile.toPath(), includedContent.getBytes(UTF_8));
+		return includedFile;
+	}
+
+	private static void assertValuesAsIsSaveLoad(FileBasedConfig fbConfig,
+			Consumer<FileBasedConfig> assertion)
+			throws IOException, ConfigInvalidException {
+		assertion.accept(fbConfig);
+
+		fbConfig.save();
+		assertion.accept(fbConfig);
+
+		fbConfig = loadConfig(fbConfig.getFile());
+		assertion.accept(fbConfig);
+	}
+
+	private static void setAllValuesNew(Config config) {
+		config.setString("user", null, "name", "Alice Bauer");
+		config.setBoolean("core", null, "fileMode", false);
+		config.setInt("core", null, "deltaBaseCacheLimit", 12);
+		config.setLong("core", null, "packedGitLimit", 22);
+		config.setLong("core", null, "repositoryCacheExpireAfter", 32);
+		config.setEnum("core", null, "autocrlf", CoreConfig.AutoCRLF.FALSE);
+		config.setString("remote", "origin", "fetch",
+				"+refs/heads/*:refs/remotes/backup/*");
+	}
+
+	private static void assertValuesAsIncluded(Config config, String... refs) {
+		assertAllTypesSampleContent("Alice Muller", true, 10, 20, 30,
+				CoreConfig.AutoCRLF.TRUE, config, refs);
+	}
+
+	private static void assertValuesAsConfig(Config config, String... refs) {
+		assertAllTypesSampleContent("Alice Parker", false, 11, 21, 31,
+				CoreConfig.AutoCRLF.FALSE, config, refs);
+	}
+
+	private static void assertValuesAsNew(Config config, String... refs) {
+		assertValuesAsNewWithName(config, "Alice Bauer", refs);
+	}
+
+	private static void assertValuesAsNewWithName(Config config, String name,
+			String... refs) {
+		assertAllTypesSampleContent(name, false, 12, 22, 32,
+				CoreConfig.AutoCRLF.FALSE, config, refs);
+	}
+
+	private static void assertSections(Config config, String... sections) {
+		assertEquals(Arrays.asList(sections),
+				new ArrayList<>(config.getSections()));
+	}
+
+	private static String createAllTypesSampleContent(String name,
+			boolean fileMode, int deltaBaseCacheLimit, long packedGitLimit,
+			long repositoryCacheExpireAfter, CoreConfig.AutoCRLF autoCRLF,
+			String fetchRefSpec) {
+		final StringBuilder builder = new StringBuilder();
+		builder.append("[user]\n");
+		builder.append("name=");
+		builder.append(name);
+		builder.append("\n");
+
+		builder.append("[core]\n");
+		builder.append("fileMode=");
+		builder.append(fileMode);
+		builder.append("\n");
+
+		builder.append("deltaBaseCacheLimit=");
+		builder.append(deltaBaseCacheLimit);
+		builder.append("\n");
+
+		builder.append("packedGitLimit=");
+		builder.append(packedGitLimit);
+		builder.append("\n");
+
+		builder.append("repositoryCacheExpireAfter=");
+		builder.append(repositoryCacheExpireAfter);
+		builder.append("\n");
+
+		builder.append("autocrlf=");
+		builder.append(autoCRLF.name());
+		builder.append("\n");
+
+		builder.append("[remote \"origin\"]\n");
+		builder.append("fetch=");
+		builder.append(fetchRefSpec);
+		builder.append("\n");
+		return builder.toString();
+	}
+
+	private static void assertAllTypesSampleContent(String name,
+			boolean fileMode, int deltaBaseCacheLimit, long packedGitLimit,
+			long repositoryCacheExpireAfter, CoreConfig.AutoCRLF autoCRLF,
+			Config config, String... fetchRefSpecs) {
+		assertEquals(name, config.getString("user", null, "name"));
+		assertEquals(fileMode,
+				config.getBoolean("core", "fileMode", !fileMode));
+		assertEquals(deltaBaseCacheLimit,
+				config.getInt("core", "deltaBaseCacheLimit", -1));
+		assertEquals(packedGitLimit,
+				config.getLong("core", "packedGitLimit", -1));
+		assertEquals(repositoryCacheExpireAfter, config.getTimeUnit("core",
+				null, "repositoryCacheExpireAfter", -1, MILLISECONDS));
+		assertEquals(autoCRLF, config.getEnum("core", null, "autocrlf",
+				CoreConfig.AutoCRLF.INPUT));
+		final List<RefSpec> refspecs = new ArrayList<>();
+		for (String fetchRefSpec : fetchRefSpecs) {
+			refspecs.add(new RefSpec(fetchRefSpec));
+		}
+
+		assertEquals(refspecs, config.getRefSpecs("remote", "origin", "fetch"));
+	}
+
 	private static void assertReadLong(long exp) throws ConfigInvalidException {
 		assertReadLong(exp, String.valueOf(exp));
 	}
@@ -1217,4 +1513,12 @@
 			assertEquals(expectedMessage, e.getMessage());
 		}
 	}
+
+	private static FileBasedConfig loadConfig(File file)
+			throws IOException, ConfigInvalidException {
+		final FileBasedConfig config = new FileBasedConfig(null, file,
+				FS.DETECTED);
+		config.load();
+		return config;
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutMaliciousPathTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutMaliciousPathTest.java
index 32a1ec9..057e0c8 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutMaliciousPathTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutMaliciousPathTest.java
@@ -37,6 +37,7 @@
  */
 package org.eclipse.jgit.lib;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -343,7 +344,7 @@
 			ObjectInserter newObjectInserter;
 			newObjectInserter = git.getRepository().newObjectInserter();
 			ObjectId blobId = newObjectInserter.insert(Constants.OBJ_BLOB,
-					"data".getBytes());
+					"data".getBytes(UTF_8));
 			newObjectInserter = git.getRepository().newObjectInserter();
 			FileMode mode = FileMode.REGULAR_FILE;
 			ObjectId insertId = blobId;
@@ -366,8 +367,8 @@
 			insertId = blobId;
 			for (int i = path.length - 1; i >= 0; --i) {
 				TreeFormatter treeFormatter = new TreeFormatter();
-				treeFormatter.append(path[i].getBytes(), 0,
-							path[i].getBytes().length,
+				treeFormatter.append(path[i].getBytes(UTF_8), 0,
+						path[i].getBytes(UTF_8).length,
 							mode, insertId, true);
 				insertId = newObjectInserter.insert(treeFormatter);
 				mode = FileMode.TREE;
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 eb87827..534b323 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
@@ -40,6 +40,7 @@
  */
 package org.eclipse.jgit.lib;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -310,7 +311,7 @@
 			assertTrue("unexpected content for path " + path
 					+ " in index. Expected: <" + expectedValue + ">",
 					Arrays.equals(db.open(read.getEntry(j).getObjectId())
-							.getCachedBytes(), i.get(path).getBytes()));
+							.getCachedBytes(), i.get(path).getBytes(UTF_8)));
 		}
 	}
 
@@ -405,7 +406,7 @@
 
 	ObjectId genSha1(String data) {
 		try (ObjectInserter w = db.newObjectInserter()) {
-			ObjectId id = w.insert(Constants.OBJ_BLOB, data.getBytes());
+			ObjectId id = w.insert(Constants.OBJ_BLOB, data.getBytes(UTF_8));
 			w.flush();
 			return id;
 		} catch (IOException e) {
@@ -928,6 +929,7 @@
 						"e/g3"));
 		try {
 			checkout();
+			fail("did not throw CheckoutConflictException");
 		} catch (CheckoutConflictException e) {
 			assertWorkDir(mkmap("a", "a", "b/c", "b/c", "d", "d", "e/f",
 					"e/f", "e/g", "e/g3"));
@@ -2048,7 +2050,7 @@
 						assertArrayEquals(
 								"unexpected content for path " + path
 										+ " in workDir. ",
-								buffer, i.get(path).getBytes());
+								buffer, i.get(path).getBytes(UTF_8));
 					}
 					nrFiles++;
 				} else if (file.isDirectory()) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/GpgConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/GpgConfigTest.java
new file mode 100644
index 0000000..2098b17
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/GpgConfigTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2018, Salesforce.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.lib;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.junit.Test;
+
+public class GpgConfigTest {
+
+	private static Config parse(String content) throws ConfigInvalidException {
+		final Config c = new Config(null);
+		c.fromText(content);
+		return c;
+	}
+
+	@Test
+	public void isSignCommits_defaultIsFalse() throws Exception {
+		Config c = parse("");
+
+		assertFalse(new GpgConfig(c).isSignCommits());
+	}
+
+	@Test
+	public void isSignCommits_false() throws Exception {
+		Config c = parse("" //
+				+ "[gpg]\n" //
+				+ "  format = x509\n" //
+				+ "[commit]\n" //
+				+ "  gpgSign = false\n" //
+		);
+
+		assertFalse(new GpgConfig(c).isSignCommits());
+	}
+
+	@Test
+	public void isSignCommits_true() throws Exception {
+		Config c = parse("" //
+				+ "[commit]\n" //
+				+ "  gpgSign = true\n" //
+		);
+
+		assertTrue(new GpgConfig(c).isSignCommits());
+	}
+
+	@Test
+	public void testGetKeyFormat_defaultsToOpenpgp() throws Exception {
+		Config c = parse("");
+
+		assertEquals(GpgConfig.GpgFormat.OPENPGP,
+				new GpgConfig(c).getKeyFormat());
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void testGetKeyFormat_failsForInvalidValue() throws Exception {
+		Config c = parse("" //
+				+ "[gpg]\n" //
+				+ "  format = invalid\n" //
+		);
+
+		new GpgConfig(c).getKeyFormat();
+		fail("Call should not have succeeded!");
+	}
+
+	@Test
+	public void testGetKeyFormat_openpgp() throws Exception {
+		Config c = parse("" //
+				+ "[gpg]\n" //
+				+ "  format = openpgp\n" //
+		);
+
+		assertEquals(GpgConfig.GpgFormat.OPENPGP,
+				new GpgConfig(c).getKeyFormat());
+	}
+
+	@Test
+	public void testGetKeyFormat_x509() throws Exception {
+		Config c = parse("" //
+				+ "[gpg]\n" //
+				+ "  format = x509\n" //
+		);
+
+		assertEquals(GpgConfig.GpgFormat.X509, new GpgConfig(c).getKeyFormat());
+	}
+
+	@Test
+	public void testGetSigningKey() throws Exception {
+		Config c = parse("" //
+				+ "[user]\n" //
+				+ "  signingKey = 0x2345\n" //
+		);
+
+		assertEquals("0x2345", new GpgConfig(c).getSigningKey());
+	}
+
+	@Test
+	public void testGetSigningKey_defaultToNull() throws Exception {
+		Config c = parse("");
+
+		assertNull(new GpgConfig(c).getSigningKey());
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java
index d89aabe..fa7f5ab 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java
@@ -43,14 +43,17 @@
 
 package org.eclipse.jgit.lib;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.Set;
 
+import org.eclipse.jgit.api.CloneCommand;
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.errors.NoWorkTreeException;
@@ -109,6 +112,59 @@
 		assertFalse(indexDiff.diff());
 	}
 
+	private Repository cloneWithoutCloningSubmodule() throws Exception {
+		File directory = createTempDirectory(
+				"testCloneWithoutCloningSubmodules");
+		CloneCommand clone = Git.cloneRepository();
+		clone.setDirectory(directory);
+		clone.setCloneSubmodules(false);
+		clone.setURI(db.getDirectory().toURI().toString());
+		Git git2 = clone.call();
+		addRepoToClose(git2.getRepository());
+		return git2.getRepository();
+	}
+
+	@Theory
+	public void testCleanAfterClone(IgnoreSubmoduleMode mode) throws Exception {
+		Repository db2 = cloneWithoutCloningSubmodule();
+		IndexDiff indexDiff = new IndexDiff(db2, Constants.HEAD,
+				new FileTreeIterator(db2));
+		indexDiff.setIgnoreSubmoduleMode(mode);
+		boolean changed = indexDiff.diff();
+		assertFalse(changed);
+	}
+
+	@Theory
+	public void testMissingIfDirectoryGone(IgnoreSubmoduleMode mode)
+			throws Exception {
+		recursiveDelete(submodule_trash);
+		IndexDiff indexDiff = new IndexDiff(db, Constants.HEAD,
+				new FileTreeIterator(db));
+		indexDiff.setIgnoreSubmoduleMode(mode);
+		boolean hasChanges = indexDiff.diff();
+		if (mode != IgnoreSubmoduleMode.ALL) {
+			assertTrue(hasChanges);
+			assertEquals("[modules/submodule]",
+					indexDiff.getMissing().toString());
+		} else {
+			assertFalse(hasChanges);
+		}
+	}
+
+	@Theory
+	public void testSubmoduleReplacedByFile(IgnoreSubmoduleMode mode)
+			throws Exception {
+		recursiveDelete(submodule_trash);
+		writeTrashFile("modules/submodule", "nonsense");
+		IndexDiff indexDiff = new IndexDiff(db, Constants.HEAD,
+				new FileTreeIterator(db));
+		indexDiff.setIgnoreSubmoduleMode(mode);
+		assertTrue(indexDiff.diff());
+		assertEquals("[]", indexDiff.getMissing().toString());
+		assertEquals("[]", indexDiff.getUntracked().toString());
+		assertEquals("[modules/submodule]", indexDiff.getModified().toString());
+	}
+
 	@Theory
 	public void testDirtyRootWorktree(IgnoreSubmoduleMode mode)
 			throws IOException {
@@ -210,4 +266,33 @@
 		assertDiff(indexDiff, mode, IgnoreSubmoduleMode.ALL,
 				IgnoreSubmoduleMode.DIRTY, IgnoreSubmoduleMode.UNTRACKED);
 	}
+
+	@Theory
+	public void testSubmoduleReplacedByMovedFile(IgnoreSubmoduleMode mode)
+			throws Exception {
+		Git git = Git.wrap(db);
+		git.rm().setCached(true).addFilepattern("modules/submodule").call();
+		recursiveDelete(submodule_trash);
+		JGitTestUtil.deleteTrashFile(db, "fileInRoot");
+		// Move the fileInRoot file
+		writeTrashFile("modules/submodule/fileInRoot", "root");
+		git.rm().addFilepattern("fileInRoot").addFilepattern("modules/").call();
+		git.add().addFilepattern("modules/").call();
+		IndexDiff indexDiff = new IndexDiff(db, Constants.HEAD,
+				new FileTreeIterator(db));
+		indexDiff.setIgnoreSubmoduleMode(mode);
+		assertTrue(indexDiff.diff());
+		String[] removed = indexDiff.getRemoved().toArray(new String[0]);
+		Arrays.sort(removed);
+		if (IgnoreSubmoduleMode.ALL.equals(mode)) {
+			assertArrayEquals(new String[] { "fileInRoot" }, removed);
+		} else {
+			assertArrayEquals(
+					new String[] { "fileInRoot", "modules/submodule" },
+					removed);
+		}
+		assertEquals("[modules/submodule/fileInRoot]",
+				indexDiff.getAdded().toString());
+	}
+
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java
index 580b08b..ba5aaf1 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java
@@ -119,6 +119,28 @@
 	}
 
 	@Test
+	public void testMissing() throws Exception {
+		File file2 = writeTrashFile("file2", "file2");
+		File file3 = writeTrashFile("dir/file3", "dir/file3");
+		Git git = Git.wrap(db);
+		git.add().addFilepattern("file2").addFilepattern("dir/file3").call();
+		git.commit().setMessage("commit").call();
+		assertTrue(file2.delete());
+		assertTrue(file3.delete());
+		IndexDiff diff = new IndexDiff(db, Constants.HEAD,
+				new FileTreeIterator(db));
+		diff.diff();
+		assertEquals(2, diff.getMissing().size());
+		assertTrue(diff.getMissing().contains("file2"));
+		assertTrue(diff.getMissing().contains("dir/file3"));
+		assertEquals(0, diff.getChanged().size());
+		assertEquals(0, diff.getModified().size());
+		assertEquals(0, diff.getAdded().size());
+		assertEquals(0, diff.getRemoved().size());
+		assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
+	}
+
+	@Test
 	public void testRemoved() throws IOException {
 		writeTrashFile("file2", "file2");
 		writeTrashFile("dir/file3", "dir/file3");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/MergeHeadMsgTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/MergeHeadMsgTest.java
index 347883f..abf7d56 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/MergeHeadMsgTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/MergeHeadMsgTest.java
@@ -42,6 +42,7 @@
  */
 package org.eclipse.jgit.lib;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 
@@ -72,7 +73,9 @@
 		// same test again, this time with lower-level io
 		try (FileOutputStream fos = new FileOutputStream(
 				new File(db.getDirectory(), "MERGE_HEAD"));) {
-			fos.write("0000000000000000000000000000000000000000\n1c6db447abdbb291b25f07be38ea0b1bf94947c5\n".getBytes(Constants.CHARACTER_ENCODING));
+			fos.write(
+					"0000000000000000000000000000000000000000\n1c6db447abdbb291b25f07be38ea0b1bf94947c5\n"
+							.getBytes(UTF_8));
 		}
 		assertEquals(db.readMergeHeads().size(), 2);
 		assertEquals(db.readMergeHeads().get(0), ObjectId.zeroId());
@@ -82,7 +85,7 @@
 		assertEquals(db.readMergeHeads(), null);
 		try (FileOutputStream fos = new FileOutputStream(
 				new File(db.getDirectory(), "MERGE_HEAD"))) {
-			fos.write(sampleId.getBytes(Constants.CHARACTER_ENCODING));
+			fos.write(sampleId.getBytes(UTF_8));
 		}
 		assertEquals(db.readMergeHeads().size(), 1);
 		assertEquals(db.readMergeHeads().get(0), ObjectId.fromString(sampleId));
@@ -100,7 +103,7 @@
 		assertFalse(new File(db.getDirectory(), "MERGE_MSG").exists());
 		try (FileOutputStream fos = new FileOutputStream(
 				new File(db.getDirectory(), Constants.MERGE_MSG))) {
-			fos.write(mergeMsg.getBytes(Constants.CHARACTER_ENCODING));
+			fos.write(mergeMsg.getBytes(UTF_8));
 		}
 		assertEquals(db.readMergeCommitMsg(), mergeMsg);
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectLoaderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectLoaderTest.java
index 83e61d9..055e66e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectLoaderTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectLoaderTest.java
@@ -260,6 +260,12 @@
 						fail("never should have reached read");
 						return -1;
 					}
+
+					@Override
+					public int read(byte b[], int off, int len) {
+						fail("never should have reached read");
+						return -1;
+					}
 				};
 			}
 		};
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java
index 3542dfa..bb24994 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java
@@ -43,6 +43,7 @@
 package org.eclipse.jgit.lib;
 
 import static java.lang.Long.valueOf;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
@@ -181,7 +182,7 @@
 	private File addToWorkDir(String path, String content) throws IOException {
 		File f = new File(db.getWorkTree(), path);
 		try (FileOutputStream fos = new FileOutputStream(f)) {
-			fos.write(content.getBytes(Constants.CHARACTER_ENCODING));
+			fos.write(content.getBytes(UTF_8));
 			return f;
 		}
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java
index a42027b..7d2c4a2 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java
@@ -45,6 +45,7 @@
 
 package org.eclipse.jgit.lib;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.junit.Assert.assertEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -261,7 +262,7 @@
 		assertEquals(Storage.PACKED, ref.getStorage());
 		try (FileOutputStream os = new FileOutputStream(
 				new File(db.getDirectory(), "refs/heads/master"))) {
-			os.write(ref.getObjectId().name().getBytes());
+			os.write(ref.getObjectId().name().getBytes(UTF_8));
 			os.write('\n');
 		}
 
@@ -333,4 +334,17 @@
 		assertEquals(1, refs.size());
 		checkContainsRef(refs, db.exactRef("refs/heads/prefix/a"));
 	}
+
+	@Test
+	public void testGetRefsByPrefixes() throws IOException {
+		List<Ref> refs = db.getRefDatabase().getRefsByPrefix();
+		assertEquals(0, refs.size());
+
+		refs = db.getRefDatabase().getRefsByPrefix("refs/heads/p",
+				"refs/tags/A");
+		assertEquals(3, refs.size());
+		checkContainsRef(refs, db.exactRef("refs/heads/pa"));
+		checkContainsRef(refs, db.exactRef("refs/heads/prefix/a"));
+		checkContainsRef(refs, db.exactRef("refs/tags/A"));
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SquashCommitMsgTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SquashCommitMsgTest.java
index 203c00e..f58ab06 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SquashCommitMsgTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SquashCommitMsgTest.java
@@ -42,6 +42,7 @@
  */
 package org.eclipse.jgit.lib;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 
@@ -68,7 +69,7 @@
 		assertFalse(new File(db.getDirectory(), Constants.SQUASH_MSG).exists());
 		try (FileOutputStream fos = new FileOutputStream(
 				new File(db.getDirectory(), Constants.SQUASH_MSG))) {
-			fos.write(squashMsg.getBytes(Constants.CHARACTER_ENCODING));
+			fos.write(squashMsg.getBytes(UTF_8));
 		}
 		assertEquals(db.readSquashCommitMsg(), squashMsg);
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ValidRefNameTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ValidRefNameTest.java
index 87e901f..df5079a 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ValidRefNameTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ValidRefNameTest.java
@@ -270,8 +270,8 @@
 
 	@Test
 	public void testNormalizeBranchName() {
-		assertEquals(true, Repository.normalizeBranchName(null) == "");
-		assertEquals(true, Repository.normalizeBranchName("").equals(""));
+		assertEquals("", Repository.normalizeBranchName(null));
+		assertEquals("", Repository.normalizeBranchName(""));
 		assertNormalized("Bug 12345::::Hello World", "Bug_12345-Hello_World");
 		assertNormalized("Bug 12345 :::: Hello World", "Bug_12345_Hello_World");
 		assertNormalized("Bug 12345 :::: Hello::: World",
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeAlgorithmTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeAlgorithmTest.java
index 5af62b6..3da779b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeAlgorithmTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeAlgorithmTest.java
@@ -43,6 +43,7 @@
 
 package org.eclipse.jgit.merge;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 
 import java.io.ByteArrayOutputStream;
@@ -301,8 +302,8 @@
 		MergeResult r = new MergeAlgorithm().merge(RawTextComparator.DEFAULT,
 				T(commonBase), T(ours), T(theirs));
 		ByteArrayOutputStream bo=new ByteArrayOutputStream(50);
-		fmt.formatMerge(bo, r, "B", "O", "T", Constants.CHARACTER_ENCODING);
-		return new String(bo.toByteArray(), Constants.CHARACTER_ENCODING);
+		fmt.formatMerge(bo, r, "B", "O", "T", UTF_8);
+		return new String(bo.toByteArray(), UTF_8);
 	}
 
 	public String t(String text) {
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 8f12dd7..8ca5d45 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
@@ -839,7 +839,7 @@
 	/**
 	 * Throws an exception if reading beyond limit.
 	 */
-	class BigReadForbiddenStream extends ObjectStream.Filter {
+	static class BigReadForbiddenStream extends ObjectStream.Filter {
 		int limit;
 
 		BigReadForbiddenStream(ObjectStream orig, int limit) {
@@ -878,7 +878,7 @@
 		}
 	}
 
-	class BigReadForbiddenReader extends ObjectReader.Filter {
+	static class BigReadForbiddenReader extends ObjectReader.Filter {
 		ObjectReader delegate;
 		int limit;
 
@@ -972,7 +972,7 @@
 			merger.getMergeResults().get("file");
 			try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
 				fmt.formatMerge(out, merger.getMergeResults().get("file"),
-						"BASE", "OURS", "THEIRS", UTF_8.name());
+						"BASE", "OURS", "THEIRS", UTF_8);
 				String expected = "<<<<<<< OURS\n"
 						+ "1master\n"
 						+ "=======\n"
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revplot/PlotCommitListTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revplot/PlotCommitListTest.java
index 9a6043f..7297de3 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revplot/PlotCommitListTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revplot/PlotCommitListTest.java
@@ -56,7 +56,7 @@
 
 public class PlotCommitListTest extends RevWalkTestCase {
 
-	class CommitListAssert {
+	static class CommitListAssert {
 		private PlotCommitList<PlotLane> pcl;
 		private PlotCommit<PlotLane> current;
 		private int nextIndex = 0;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/FileBasedConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/FileBasedConfigTest.java
index 7f0d602..b401d2b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/FileBasedConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/FileBasedConfigTest.java
@@ -52,6 +52,7 @@
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.util.StringTokenizer;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.util.FS;
@@ -67,16 +68,23 @@
 
 	private static final String NAME = "name";
 
+	private static final String EMAIL = "email";
+
 	private static final String ALICE = "Alice";
 
 	private static final String BOB = "Bob";
 
+	private static final String ALICE_EMAIL = "alice@home";
+
 	private static final String CONTENT1 = "[" + USER + "]\n\t" + NAME + " = "
 			+ ALICE + "\n";
 
 	private static final String CONTENT2 = "[" + USER + "]\n\t" + NAME + " = "
 			+ BOB + "\n";
 
+	private static final String CONTENT3 = "[" + USER + "]\n\t" + NAME + " = "
+			+ ALICE + "\n" + "[" + USER + "]\n\t" + EMAIL + " = " + ALICE_EMAIL;
+
 	private File trash;
 
 	@Before
@@ -93,14 +101,14 @@
 
 	@Test
 	public void testSystemEncoding() throws IOException, ConfigInvalidException {
-		final File file = createFile(CONTENT1.getBytes());
+		final File file = createFile(CONTENT1.getBytes(UTF_8));
 		final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED);
 		config.load();
 		assertEquals(ALICE, config.getString(USER, null, NAME));
 
 		config.setString(USER, null, NAME, BOB);
 		config.save();
-		assertArrayEquals(CONTENT2.getBytes(), IO.readFully(file));
+		assertArrayEquals(CONTENT2.getBytes(UTF_8), IO.readFully(file));
 	}
 
 	@Test
@@ -112,7 +120,7 @@
 
 		config.setString(USER, null, NAME, BOB);
 		config.save();
-		assertArrayEquals(CONTENT2.getBytes(), IO.readFully(file));
+		assertArrayEquals(CONTENT2.getBytes(UTF_8), IO.readFully(file));
 	}
 
 	@Test
@@ -142,8 +150,8 @@
 	@Test
 	public void testLeadingWhitespaces() throws IOException, ConfigInvalidException {
 		final ByteArrayOutputStream bos1 = new ByteArrayOutputStream();
-		bos1.write(" \n\t".getBytes());
-		bos1.write(CONTENT1.getBytes());
+		bos1.write(" \n\t".getBytes(UTF_8));
+		bos1.write(CONTENT1.getBytes(UTF_8));
 
 		final File file = createFile(bos1.toByteArray());
 		final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED);
@@ -154,18 +162,18 @@
 		config.save();
 
 		final ByteArrayOutputStream bos2 = new ByteArrayOutputStream();
-		bos2.write(" \n\t".getBytes());
-		bos2.write(CONTENT2.getBytes());
+		bos2.write(" \n\t".getBytes(UTF_8));
+		bos2.write(CONTENT2.getBytes(UTF_8));
 		assertArrayEquals(bos2.toByteArray(), IO.readFully(file));
 	}
 
 	@Test
 	public void testIncludeAbsolute()
 			throws IOException, ConfigInvalidException {
-		final File includedFile = createFile(CONTENT1.getBytes());
+		final File includedFile = createFile(CONTENT1.getBytes(UTF_8));
 		final ByteArrayOutputStream bos = new ByteArrayOutputStream();
-		bos.write("[include]\npath=".getBytes());
-		bos.write(pathToString(includedFile).getBytes());
+		bos.write("[include]\npath=".getBytes(UTF_8));
+		bos.write(pathToString(includedFile).getBytes(UTF_8));
 
 		final File file = createFile(bos.toByteArray());
 		final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED);
@@ -176,10 +184,10 @@
 	@Test
 	public void testIncludeRelativeDot()
 			throws IOException, ConfigInvalidException {
-		final File includedFile = createFile(CONTENT1.getBytes(), "dir1");
+		final File includedFile = createFile(CONTENT1.getBytes(UTF_8), "dir1");
 		final ByteArrayOutputStream bos = new ByteArrayOutputStream();
-		bos.write("[include]\npath=".getBytes());
-		bos.write(("./" + includedFile.getName()).getBytes());
+		bos.write("[include]\npath=".getBytes(UTF_8));
+		bos.write(("./" + includedFile.getName()).getBytes(UTF_8));
 
 		final File file = createFile(bos.toByteArray(), "dir1");
 		final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED);
@@ -190,11 +198,11 @@
 	@Test
 	public void testIncludeRelativeDotDot()
 			throws IOException, ConfigInvalidException {
-		final File includedFile = createFile(CONTENT1.getBytes(), "dir1");
+		final File includedFile = createFile(CONTENT1.getBytes(UTF_8), "dir1");
 		final ByteArrayOutputStream bos = new ByteArrayOutputStream();
-		bos.write("[include]\npath=".getBytes());
+		bos.write("[include]\npath=".getBytes(UTF_8));
 		bos.write(("../" + includedFile.getParentFile().getName() + "/"
-				+ includedFile.getName()).getBytes());
+				+ includedFile.getName()).getBytes(UTF_8));
 
 		final File file = createFile(bos.toByteArray(), "dir2");
 		final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED);
@@ -205,10 +213,10 @@
 	@Test
 	public void testIncludeRelativeDotDotNotFound()
 			throws IOException, ConfigInvalidException {
-		final File includedFile = createFile(CONTENT1.getBytes());
+		final File includedFile = createFile(CONTENT1.getBytes(UTF_8));
 		final ByteArrayOutputStream bos = new ByteArrayOutputStream();
-		bos.write("[include]\npath=".getBytes());
-		bos.write(("../" + includedFile.getName()).getBytes());
+		bos.write("[include]\npath=".getBytes(UTF_8));
+		bos.write(("../" + includedFile.getName()).getBytes(UTF_8));
 
 		final File file = createFile(bos.toByteArray());
 		final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED);
@@ -219,10 +227,10 @@
 	@Test
 	public void testIncludeWithTilde()
 			throws IOException, ConfigInvalidException {
-		final File includedFile = createFile(CONTENT1.getBytes(), "home");
+		final File includedFile = createFile(CONTENT1.getBytes(UTF_8), "home");
 		final ByteArrayOutputStream bos = new ByteArrayOutputStream();
-		bos.write("[include]\npath=".getBytes());
-		bos.write(("~/" + includedFile.getName()).getBytes());
+		bos.write("[include]\npath=".getBytes(UTF_8));
+		bos.write(("~/" + includedFile.getName()).getBytes(UTF_8));
 
 		final File file = createFile(bos.toByteArray(), "repo");
 		final FS fs = FS.DETECTED.newInstance();
@@ -233,6 +241,50 @@
 		assertEquals(ALICE, config.getString(USER, null, NAME));
 	}
 
+	@Test
+	public void testIncludeDontInlineIncludedLinesOnSave()
+			throws IOException, ConfigInvalidException {
+		// use a content with multiple sections and multiple key/value pairs
+		// because code for first line works different than for subsequent lines
+		final File includedFile = createFile(CONTENT3.getBytes(UTF_8), "dir1");
+
+		final File file = createFile(new byte[0], "dir2");
+		FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED);
+		config.setString("include", null, "path",
+				("../" + includedFile.getParentFile().getName() + "/"
+						+ includedFile.getName()));
+
+		// just by setting the include.path, it won't be included
+		assertEquals(null, config.getString(USER, null, NAME));
+		assertEquals(null, config.getString(USER, null, EMAIL));
+		config.save();
+
+		// and it won't be included after saving
+		assertEquals(null, config.getString(USER, null, NAME));
+		assertEquals(null, config.getString(USER, null, EMAIL));
+
+		final String expectedText = config.toText();
+		assertEquals(2,
+				new StringTokenizer(expectedText, "\n", false).countTokens());
+
+		config = new FileBasedConfig(file, FS.DETECTED);
+		config.load();
+
+		String actualText = config.toText();
+		assertEquals(expectedText, actualText);
+		// but it will be included after (re)loading
+		assertEquals(ALICE, config.getString(USER, null, NAME));
+		assertEquals(ALICE_EMAIL, config.getString(USER, null, EMAIL));
+
+		config.save();
+
+		actualText = config.toText();
+		assertEquals(expectedText, actualText);
+		// and of course preserved after saving
+		assertEquals(ALICE, config.getString(USER, null, NAME));
+		assertEquals(ALICE_EMAIL, config.getString(USER, null, EMAIL));
+	}
+
 	private File createFile(byte[] content) throws IOException {
 		return createFile(content, null);
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java
index fed22c0..a0cd37e 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
@@ -42,6 +42,7 @@
  */
 package org.eclipse.jgit.submodule;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PATH;
 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_URL;
 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_SUBMODULE_SECTION;
@@ -52,9 +53,10 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import java.io.BufferedWriter;
 import java.io.File;
-import java.io.FileWriter;
 import java.io.IOException;
+import java.nio.file.Files;
 
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.Status;
@@ -155,10 +157,12 @@
 		if (!dotGit.getParentFile().exists())
 			dotGit.getParentFile().mkdirs();
 
-		File modulesGitDir = new File(db.getDirectory(), "modules"
-				+ File.separatorChar + path);
-		new FileWriter(dotGit).append(
-				"gitdir: " + modulesGitDir.getAbsolutePath()).close();
+		File modulesGitDir = new File(db.getDirectory(),
+				"modules" + File.separatorChar + path);
+		try (BufferedWriter fw = Files.newBufferedWriter(dotGit.toPath(),
+				UTF_8)) {
+			fw.append("gitdir: " + modulesGitDir.getAbsolutePath());
+		}
 		FileRepositoryBuilder builder = new FileRepositoryBuilder();
 		builder.setWorkTree(new File(db.getWorkTree(), path));
 		builder.build().create();
@@ -209,9 +213,11 @@
 
 		File modulesGitDir = new File(db.getDirectory(), "modules"
 				+ File.separatorChar + path);
-		new FileWriter(dotGit).append(
-				"gitdir: " + "../" + Constants.DOT_GIT + "/modules/" + path)
-				.close();
+		try (BufferedWriter fw = Files.newBufferedWriter(dotGit.toPath(),
+				UTF_8)) {
+			fw.append("gitdir: " + "../" + Constants.DOT_GIT + "/modules/"
+					+ path);
+		}
 		FileRepositoryBuilder builder = new FileRepositoryBuilder();
 		builder.setWorkTree(new File(db.getWorkTree(), path));
 		builder.build().create();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/JSchSshTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/JSchSshTest.java
new file mode 100644
index 0000000..8ff70c4
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/JSchSshTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.transport;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.util.Arrays;
+
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.transport.OpenSshConfig.Host;
+import org.eclipse.jgit.transport.ssh.SshTestBase;
+import org.eclipse.jgit.util.FS;
+import org.junit.experimental.theories.Theories;
+import org.junit.runner.RunWith;
+
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.Session;
+
+@RunWith(Theories.class)
+public class JSchSshTest extends SshTestBase {
+
+	private class TestSshSessionFactory extends JschConfigSessionFactory {
+
+		@Override
+		protected void configure(Host hc, Session session) {
+			// Nothing
+		}
+
+		@Override
+		public synchronized RemoteSession getSession(URIish uri,
+				CredentialsProvider credentialsProvider, FS fs, int tms)
+				throws TransportException {
+			return super.getSession(uri, credentialsProvider, fs, tms);
+		}
+
+		@Override
+		protected JSch createDefaultJSch(FS fs) throws JSchException {
+			JSch defaultJSch = super.createDefaultJSch(fs);
+			if (knownHosts.exists()) {
+				defaultJSch.setKnownHosts(knownHosts.getAbsolutePath());
+			}
+			return defaultJSch;
+		}
+	}
+
+	@Override
+	protected SshSessionFactory createSessionFactory() {
+		return new TestSshSessionFactory();
+	}
+
+	@Override
+	protected void installConfig(String... config) {
+		SshSessionFactory factory = getSessionFactory();
+		assertTrue(factory instanceof JschConfigSessionFactory);
+		JschConfigSessionFactory j = (JschConfigSessionFactory) factory;
+		try {
+			j.setConfig(createConfig(config));
+		} catch (IOException e) {
+			throw new UncheckedIOException(e);
+		}
+	}
+
+	private OpenSshConfig createConfig(String... content) throws IOException {
+		File configFile = new File(sshDir, Constants.CONFIG);
+		if (content != null) {
+			Files.write(configFile.toPath(), Arrays.asList(content));
+		}
+		return new OpenSshConfig(getTemporaryDirectory(), configFile);
+	}
+
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ObjectIdMatcher.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ObjectIdMatcher.java
new file mode 100644
index 0000000..4c6e0f0
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ObjectIdMatcher.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2018, Google LLC.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.transport;
+
+import java.util.Collection;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Sets;
+import org.hamcrest.Description;
+import org.hamcrest.Factory;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+/**
+ * Multiple tests check that a collection of ObjectIds contain certain SHA1
+ * (written as strings). This matcher hides the ObjectId to string conversion to
+ * make the assertion more readable:
+ *
+ * assertThat(req.getWantsIds(), hasOnlyObjectIds("123123", "234234"));
+ */
+class ObjectIdMatcher extends TypeSafeMatcher<Collection<ObjectId>> {
+
+	private final Set<ObjectId> expectedOids;
+
+	private ObjectIdMatcher(Set<String> oids) {
+		this.expectedOids = oids.stream().map(ObjectId::fromString)
+				.collect(Collectors.toSet());
+	}
+
+	@Override
+	public void describeTo(Description desc) {
+		desc.appendText("Object ids:");
+		desc.appendValueList("<", ",", ">", expectedOids);
+	}
+
+	@Override
+	protected boolean matchesSafely(Collection<ObjectId> resultOids) {
+		return resultOids.containsAll(expectedOids)
+				&& expectedOids.containsAll(resultOids);
+	}
+
+	/**
+	 * Assert that all and only the received {@link ObjectId object ids} are in
+	 * the expected set.
+	 * <p>
+	 * ObjectIds are compared by SHA1.
+	 *
+	 * @param oids
+	 *            Object ids to examine.
+	 * @return true if examined and specified sets contains exactly the same
+	 *         elements.
+	 */
+	@Factory
+	static Matcher<Collection<ObjectId>> hasOnlyObjectIds(
+			String... oids) {
+		return new ObjectIdMatcher(Sets.of(oids));
+	}
+}
\ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java
index 19fcbfd..1a22e10 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java
@@ -50,7 +50,6 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
 import java.io.File;
@@ -67,6 +66,7 @@
 import org.junit.Test;
 
 import com.jcraft.jsch.ConfigRepository;
+import com.jcraft.jsch.ConfigRepository.Config;
 
 public class OpenSshConfigTest extends RepositoryTestCase {
 	private File home;
@@ -164,6 +164,20 @@
 	}
 
 	@Test
+	public void testCaseInsensitiveKeyLookup() throws Exception {
+		config("Host orcz\n" + "Port 29418\n"
+				+ "\tHostName repo.or.cz\nStrictHostKeyChecking yes\n");
+		final Host h = osc.lookup("orcz");
+		Config c = h.getConfig();
+		String exactCase = c.getValue("StrictHostKeyChecking");
+		assertEquals("yes", exactCase);
+		assertEquals(exactCase, c.getValue("stricthostkeychecking"));
+		assertEquals(exactCase, c.getValue("STRICTHOSTKEYCHECKING"));
+		assertEquals(exactCase, c.getValue("sTrIcThostKEYcheckING"));
+		assertNull(c.getValue("sTrIcThostKEYcheckIN"));
+	}
+
+	@Test
 	public void testAlias_DoesNotMatch() throws Exception {
 		config("Host orcz\n" + "Port 29418\n" + "\tHostName repo.or.cz\n");
 		final Host h = osc.lookup("repo.or.cz");
@@ -334,21 +348,6 @@
 	}
 
 	@Test
-	public void testRepeatedLookups() throws Exception {
-		config("Host orcz\n" + "\tConnectionAttempts 5\n");
-		final Host h1 = osc.lookup("orcz");
-		final Host h2 = osc.lookup("orcz");
-		assertNotNull(h1);
-		assertSame(h1, h2);
-		assertEquals(5, h1.getConnectionAttempts());
-		assertEquals(h1.getConnectionAttempts(), h2.getConnectionAttempts());
-		final ConfigRepository.Config c = osc.getConfig("orcz");
-		assertNotNull(c);
-		assertSame(c, h1.getConfig());
-		assertSame(c, h2.getConfig());
-	}
-
-	@Test
 	public void testRepeatedLookupsWithModification() throws Exception {
 		config("Host orcz\n" + "\tConnectionAttempts -1\n");
 		final Host h1 = osc.lookup("orcz");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PacketLineOutTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PacketLineOutTest.java
index 391a701..ad8ae42 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PacketLineOutTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PacketLineOutTest.java
@@ -43,6 +43,7 @@
 
 package org.eclipse.jgit.transport;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
@@ -50,7 +51,6 @@
 import java.io.IOException;
 import java.io.OutputStream;
 
-import org.eclipse.jgit.lib.Constants;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -173,8 +173,8 @@
 		assertEquals(1, flushCnt[0]);
 	}
 
-	private void assertBuffer(String exp) throws IOException {
+	private void assertBuffer(String exp) {
 		assertEquals(exp, new String(rawOut.toByteArray(),
-				Constants.CHARACTER_ENCODING));
+				UTF_8));
 	}
 }
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
new file mode 100644
index 0000000..2c98c84
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV0ParserTest.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2018, Google LLC.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.transport;
+
+import static org.eclipse.jgit.transport.ObjectIdMatcher.hasOnlyObjectIds;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.PackProtocolException;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Test;
+
+public class ProtocolV0ParserTest {
+	/*
+	 * Convert the input lines to the PacketLine that the parser reads.
+	 */
+	private static PacketLineIn formatAsPacketLine(String... inputLines)
+			throws IOException {
+		ByteArrayOutputStream send = new ByteArrayOutputStream();
+		PacketLineOut pckOut = new PacketLineOut(send);
+		for (String line : inputLines) {
+			if (line == PacketLineIn.END) {
+				pckOut.end();
+			} else if (line == PacketLineIn.DELIM) {
+				pckOut.writeDelim();
+			} else {
+				pckOut.writeString(line);
+			}
+		}
+
+		return new PacketLineIn(new ByteArrayInputStream(send.toByteArray()));
+	}
+
+	private static TransferConfig defaultConfig() {
+		Config rc = new Config();
+		rc.setBoolean("uploadpack", null, "allowfilter", true);
+		return new TransferConfig(rc);
+	}
+
+	@Test
+	public void testRecvWantsWithCapabilities()
+			throws PackProtocolException, IOException {
+		PacketLineIn pckIn = formatAsPacketLine(
+				String.join(" ", "want",
+						"4624442d68ee402a94364191085b77137618633e", "thin-pack",
+						"no-progress", "include-tag", "ofs-delta", "\n"),
+				"want f900c8326a43303685c46b279b9f70411bff1a4b\n",
+				PacketLineIn.END);
+		ProtocolV0Parser parser = new ProtocolV0Parser(defaultConfig());
+		FetchV0Request request = parser.recvWants(pckIn);
+		assertTrue(request.getClientCapabilities()
+				.contains(GitProtocolConstants.OPTION_THIN_PACK));
+		assertTrue(request.getClientCapabilities()
+				.contains(GitProtocolConstants.OPTION_NO_PROGRESS));
+		assertTrue(request.getClientCapabilities()
+				.contains(GitProtocolConstants.OPTION_INCLUDE_TAG));
+		assertTrue(request.getClientCapabilities()
+				.contains(GitProtocolConstants.CAPABILITY_OFS_DELTA));
+		assertThat(request.getWantIds(),
+				hasOnlyObjectIds("4624442d68ee402a94364191085b77137618633e",
+						"f900c8326a43303685c46b279b9f70411bff1a4b"));
+	}
+
+	@Test
+	public void testRecvWantsWithAgent()
+			throws PackProtocolException, IOException {
+		PacketLineIn pckIn = formatAsPacketLine(
+				String.join(" ", "want",
+						"4624442d68ee402a94364191085b77137618633e", "thin-pack",
+						"agent=JGit.test/0.0.1", "\n"),
+				"want f900c8326a43303685c46b279b9f70411bff1a4b\n",
+				PacketLineIn.END);
+		ProtocolV0Parser parser = new ProtocolV0Parser(defaultConfig());
+		FetchV0Request request = parser.recvWants(pckIn);
+		assertTrue(request.getClientCapabilities()
+				.contains(GitProtocolConstants.OPTION_THIN_PACK));
+		assertEquals(1, request.getClientCapabilities().size());
+		assertEquals("JGit.test/0.0.1", request.getAgent());
+		assertThat(request.getWantIds(),
+				hasOnlyObjectIds("4624442d68ee402a94364191085b77137618633e",
+						"f900c8326a43303685c46b279b9f70411bff1a4b"));
+	}
+
+	/*
+	 * First round of protocol v0 negotiation. Client send wants, no
+	 * capabilities.
+	 */
+	@Test
+	public void testRecvWantsWithoutCapabilities()
+			throws PackProtocolException, IOException {
+		PacketLineIn pckIn = formatAsPacketLine(
+				"want 4624442d68ee402a94364191085b77137618633e\n",
+				"want f900c8326a43303685c46b279b9f70411bff1a4b\n",
+				PacketLineIn.END);
+		ProtocolV0Parser parser = new ProtocolV0Parser(defaultConfig());
+		FetchV0Request request = parser.recvWants(pckIn);
+		assertTrue(request.getClientCapabilities().isEmpty());
+		assertThat(request.getWantIds(),
+				hasOnlyObjectIds("4624442d68ee402a94364191085b77137618633e",
+						"f900c8326a43303685c46b279b9f70411bff1a4b"));
+	}
+
+	@Test
+	public void testRecvWantsDeepen()
+			throws PackProtocolException, IOException {
+		PacketLineIn pckIn = formatAsPacketLine(
+				"want 4624442d68ee402a94364191085b77137618633e\n",
+				"want f900c8326a43303685c46b279b9f70411bff1a4b\n", "deepen 3\n",
+				PacketLineIn.END);
+		ProtocolV0Parser parser = new ProtocolV0Parser(defaultConfig());
+		FetchV0Request request = parser.recvWants(pckIn);
+		assertTrue(request.getClientCapabilities().isEmpty());
+		assertEquals(3, request.getDepth());
+		assertThat(request.getWantIds(),
+				hasOnlyObjectIds("4624442d68ee402a94364191085b77137618633e",
+						"f900c8326a43303685c46b279b9f70411bff1a4b"));
+	}
+
+	@Test
+	public void testRecvWantsShallow()
+			throws PackProtocolException, IOException {
+		PacketLineIn pckIn = formatAsPacketLine(
+				"want 4624442d68ee402a94364191085b77137618633e\n",
+				"want f900c8326a43303685c46b279b9f70411bff1a4b\n",
+				"shallow 4b643d0ef739a1b494e7d6926d8d8ed80d35edf4\n",
+				PacketLineIn.END);
+		ProtocolV0Parser parser = new ProtocolV0Parser(defaultConfig());
+		FetchV0Request request = parser.recvWants(pckIn);
+		assertTrue(request.getClientCapabilities().isEmpty());
+		assertThat(request.getWantIds(),
+				hasOnlyObjectIds("4624442d68ee402a94364191085b77137618633e",
+						"f900c8326a43303685c46b279b9f70411bff1a4b"));
+		assertThat(request.getClientShallowCommits(),
+				hasOnlyObjectIds("4b643d0ef739a1b494e7d6926d8d8ed80d35edf4"));
+	}
+
+	@Test
+	public void testRecvWantsFilter()
+			throws PackProtocolException, IOException {
+		PacketLineIn pckIn = formatAsPacketLine(
+				"want 4624442d68ee402a94364191085b77137618633e\n",
+				"want f900c8326a43303685c46b279b9f70411bff1a4b\n",
+				"filter blob:limit=13000\n",
+				PacketLineIn.END);
+		ProtocolV0Parser parser = new ProtocolV0Parser(defaultConfig());
+		FetchV0Request request = parser.recvWants(pckIn);
+		assertTrue(request.getClientCapabilities().isEmpty());
+		assertThat(request.getWantIds(),
+				hasOnlyObjectIds("4624442d68ee402a94364191085b77137618633e",
+						"f900c8326a43303685c46b279b9f70411bff1a4b"));
+		assertEquals(13000, request.getFilterBlobLimit());
+	}
+
+}
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 bf67d46..dafa81e 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
@@ -44,22 +44,20 @@
 
 import static org.hamcrest.Matchers.hasItems;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
+import static org.eclipse.jgit.transport.ObjectIdMatcher.hasOnlyObjectIds;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.util.Collection;
-import java.util.List;
-import java.util.stream.Collectors;
 
 import org.eclipse.jgit.errors.PackProtocolException;
 import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
 import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.junit.Before;
 import org.junit.Rule;
@@ -137,12 +135,6 @@
 		return new PacketLineIn(new ByteArrayInputStream(send.toByteArray()));
 	}
 
-	private static List<String> objIdsAsStrings(Collection<ObjectId> objIds) {
-		// TODO(ifrade) Translate this to a matcher, so it would read as
-		// assertThat(req.wantsIds(), hasObjectIds("...", "..."))
-		return objIds.stream().map(ObjectId::name).collect(Collectors.toList());
-	}
-
 	/*
 	 * Succesful fetch with the basic core commands of the protocol.
 	 */
@@ -160,19 +152,19 @@
 		ProtocolV2Parser parser = new ProtocolV2Parser(
 				ConfigBuilder.getDefault());
 		FetchV2Request request = parser.parseFetchRequest(pckIn);
-		assertTrue(request.getOptions()
+		assertTrue(request.getClientCapabilities()
 				.contains(GitProtocolConstants.OPTION_THIN_PACK));
-		assertTrue(request.getOptions()
+		assertTrue(request.getClientCapabilities()
 				.contains(GitProtocolConstants.OPTION_NO_PROGRESS));
-		assertTrue(request.getOptions()
+		assertTrue(request.getClientCapabilities()
 				.contains(GitProtocolConstants.OPTION_INCLUDE_TAG));
-		assertTrue(request.getOptions()
+		assertTrue(request.getClientCapabilities()
 				.contains(GitProtocolConstants.CAPABILITY_OFS_DELTA));
-		assertThat(objIdsAsStrings(request.getWantsIds()),
-				hasItems("4624442d68ee402a94364191085b77137618633e",
+		assertThat(request.getWantIds(),
+				hasOnlyObjectIds("4624442d68ee402a94364191085b77137618633e",
 						"f900c8326a43303685c46b279b9f70411bff1a4b"));
-		assertThat(objIdsAsStrings(request.getPeerHas()),
-				hasItems("554f6e41067b9e3e565b6988a8294fac1cb78f4b",
+		assertThat(request.getPeerHas(),
+				hasOnlyObjectIds("554f6e41067b9e3e565b6988a8294fac1cb78f4b",
 						"abc760ab9ad72f08209943251b36cb886a578f87"));
 		assertTrue(request.getWantedRefs().isEmpty());
 		assertTrue(request.wasDoneReceived());
@@ -190,12 +182,12 @@
 		ProtocolV2Parser parser = new ProtocolV2Parser(
 				ConfigBuilder.getDefault());
 		FetchV2Request request = parser.parseFetchRequest(pckIn);
-		assertThat(objIdsAsStrings(request.getClientShallowCommits()),
-				hasItems("28274d02c489f4c7e68153056e9061a46f62d7a0",
+		assertThat(request.getClientShallowCommits(),
+				hasOnlyObjectIds("28274d02c489f4c7e68153056e9061a46f62d7a0",
 						"145e683b229dcab9d0e2ccb01b386f9ecc17d29d"));
 		assertTrue(request.getDeepenNotRefs().isEmpty());
 		assertEquals(15, request.getDepth());
-		assertTrue(request.getOptions()
+		assertTrue(request.getClientCapabilities()
 				.contains(GitProtocolConstants.OPTION_DEEPEN_RELATIVE));
 	}
 
@@ -209,8 +201,8 @@
 		ProtocolV2Parser parser = new ProtocolV2Parser(
 				ConfigBuilder.getDefault());
 		FetchV2Request request = parser.parseFetchRequest(pckIn);
-		assertThat(objIdsAsStrings(request.getClientShallowCommits()),
-				hasItems("28274d02c489f4c7e68153056e9061a46f62d7a0",
+		assertThat(request.getClientShallowCommits(),
+				hasOnlyObjectIds("28274d02c489f4c7e68153056e9061a46f62d7a0",
 						"145e683b229dcab9d0e2ccb01b386f9ecc17d29d"));
 		assertThat(request.getDeepenNotRefs(),
 				hasItems("a08595f76159b09d57553e37a5123f1091bb13e7"));
@@ -226,8 +218,8 @@
 		ProtocolV2Parser parser = new ProtocolV2Parser(
 				ConfigBuilder.getDefault());
 		FetchV2Request request = parser.parseFetchRequest(pckIn);
-		assertThat(objIdsAsStrings(request.getClientShallowCommits()),
-				hasItems("28274d02c489f4c7e68153056e9061a46f62d7a0",
+		assertThat(request.getClientShallowCommits(),
+				hasOnlyObjectIds("28274d02c489f4c7e68153056e9061a46f62d7a0",
 						"145e683b229dcab9d0e2ccb01b386f9ecc17d29d"));
 		assertEquals(123123123, request.getDeepenSince());
 	}
@@ -256,24 +248,25 @@
 
 	@Test
 	public void testFetchMustNotHaveMultipleFilters() throws IOException {
-		thrown.expect(PackProtocolException.class);
 		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM,
 				"filter blob:none",
 				"filter blob:limit=12",
 				PacketLineIn.END);
 		ProtocolV2Parser parser = new ProtocolV2Parser(
 				ConfigBuilder.start().allowFilter().done());
-		FetchV2Request request = parser.parseFetchRequest(pckIn);
-		assertEquals(0, request.getFilterBlobLimit());
+
+		thrown.expect(PackProtocolException.class);
+		parser.parseFetchRequest(pckIn);
 	}
 
 	@Test
 	public void testFetchFilterWithoutAllowFilter() throws IOException {
-		thrown.expect(PackProtocolException.class);
 		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM,
 				"filter blob:limit=12", PacketLineIn.END);
 		ProtocolV2Parser parser = new ProtocolV2Parser(
 				ConfigBuilder.getDefault());
+
+		thrown.expect(PackProtocolException.class);
 		parser.parseFetchRequest(pckIn);
 	}
 
@@ -293,10 +286,11 @@
 
 		FetchV2Request request = parser.parseFetchRequest(pckIn);
 		assertEquals(1, request.getWantedRefs().size());
-		assertThat(request.getWantedRefs(), hasItems("refs/heads/branchA"));
-		assertEquals(1, request.getWantsIds().size());
-		assertThat(objIdsAsStrings(request.getWantsIds()),
-				hasItems("e4980cdc48cfa1301493ca94eb70523f6788b819"));
+		assertThat(request.getWantedRefs(),
+				hasItems("refs/heads/branchA"));
+		assertEquals(1, request.getWantIds().size());
+		assertThat(request.getWantIds(), hasOnlyObjectIds(
+				"e4980cdc48cfa1301493ca94eb70523f6788b819"));
 	}
 
 	@Test
@@ -318,4 +312,60 @@
 		assertThat(request.getWantedRefs(), hasItems("refs/heads/branchC"));
 	}
 
+	@Test
+	public void testLsRefsMinimalReq() throws IOException {
+		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM,
+				PacketLineIn.END);
+
+		ProtocolV2Parser parser = new ProtocolV2Parser(
+				ConfigBuilder.getDefault());
+		LsRefsV2Request req = parser.parseLsRefsRequest(pckIn);
+		assertFalse(req.getPeel());
+		assertFalse(req.getSymrefs());
+		assertEquals(0, req.getRefPrefixes().size());
+	}
+
+	@Test
+	public void testLsRefsSymrefs() throws IOException {
+		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM, "symrefs",
+				PacketLineIn.END);
+
+		ProtocolV2Parser parser = new ProtocolV2Parser(
+				ConfigBuilder.getDefault());
+		LsRefsV2Request req = parser.parseLsRefsRequest(pckIn);
+		assertFalse(req.getPeel());
+		assertTrue(req.getSymrefs());
+		assertEquals(0, req.getRefPrefixes().size());
+
+	}
+
+	@Test
+	public void testLsRefsPeel() throws IOException {
+		PacketLineIn pckIn = formatAsPacketLine(
+				PacketLineIn.DELIM,
+				"peel",
+				PacketLineIn.END);
+
+		ProtocolV2Parser parser = new ProtocolV2Parser(
+				ConfigBuilder.getDefault());
+		LsRefsV2Request req = parser.parseLsRefsRequest(pckIn);
+		assertTrue(req.getPeel());
+		assertFalse(req.getSymrefs());
+		assertEquals(0, req.getRefPrefixes().size());
+	}
+
+	@Test
+	public void testLsRefsRefPrefixes() throws IOException {
+		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM,
+				"ref-prefix refs/for", "ref-prefix refs/heads",
+				PacketLineIn.END);
+
+		ProtocolV2Parser parser = new ProtocolV2Parser(
+				ConfigBuilder.getDefault());
+		LsRefsV2Request req = parser.parseLsRefsRequest(pckIn);
+		assertFalse(req.getPeel());
+		assertFalse(req.getSymrefs());
+		assertEquals(2, req.getRefPrefixes().size());
+		assertThat(req.getRefPrefixes(), hasItems("refs/for", "refs/heads"));
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateParserTest.java
index 0647167..4bf26b6 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateParserTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateParserTest.java
@@ -42,6 +42,7 @@
 
 package org.eclipse.jgit.transport;
 
+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;
@@ -334,7 +335,7 @@
 		assertFalse(input.contains(PushCertificateParser.END_CERT));
 		input += input;
 		Reader reader = new InputStreamReader(
-				new ByteArrayInputStream(Constants.encode(input)));
+				new ByteArrayInputStream(Constants.encode(input)), UTF_8);
 
 		assertNotNull(PushCertificateParser.fromReader(reader));
 		assertNotNull(PushCertificateParser.fromReader(reader));
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateStoreTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateStoreTest.java
index 68e0129..fa4fd65 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateStoreTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateStoreTest.java
@@ -43,6 +43,7 @@
 
 package org.eclipse.jgit.transport;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.lib.ObjectId.zeroId;
 import static org.eclipse.jgit.lib.RefUpdate.Result.FAST_FORWARD;
 import static org.eclipse.jgit.lib.RefUpdate.Result.LOCK_FAILURE;
@@ -96,7 +97,9 @@
 				+ "-----END PGP SIGNATURE-----\n");
 		try {
 			return PushCertificateParser.fromReader(new InputStreamReader(
-					new ByteArrayInputStream(Constants.encode(cert.toString()))));
+					new ByteArrayInputStream(
+							Constants.encode(cert.toString())),
+					UTF_8));
 		} catch (IOException e) {
 			throw new IllegalArgumentException(e);
 		}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java
index c959f6c..dfa50b6 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java
@@ -492,9 +492,8 @@
 		assertSame(PacketLineIn.END, r.readString());
 
 		String errorLine = r.readString();
-		System.out.println(errorLine);
-		assertTrue(errorLine.startsWith(
-				"unpack error Invalid submodule URL '-"));
+		assertTrue(errorLine.startsWith("unpack error"));
+		assertTrue(errorLine.contains("Invalid submodule URL '-"));
 		assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString());
 		assertSame(PacketLineIn.END, r.readString());
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SideBandOutputStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SideBandOutputStreamTest.java
index 4d3e162..b6cf356 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SideBandOutputStreamTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SideBandOutputStreamTest.java
@@ -44,6 +44,7 @@
 package org.eclipse.jgit.transport;
 
 import static java.lang.Integer.valueOf;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.transport.SideBandOutputStream.CH_DATA;
 import static org.eclipse.jgit.transport.SideBandOutputStream.CH_ERROR;
 import static org.eclipse.jgit.transport.SideBandOutputStream.CH_PROGRESS;
@@ -59,7 +60,6 @@
 import java.text.MessageFormat;
 
 import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.lib.Constants;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -259,8 +259,7 @@
 		}
 	}
 
-	private void assertBuffer(String exp) throws IOException {
-		assertEquals(exp, new String(rawOut.toByteArray(),
-				Constants.CHARACTER_ENCODING));
+	private void assertBuffer(String exp) {
+		assertEquals(exp, new String(rawOut.toByteArray(), UTF_8));
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TestProtocolTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TestProtocolTest.java
index 953c9fc..1c4d0cf 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TestProtocolTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TestProtocolTest.java
@@ -46,6 +46,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -238,6 +239,7 @@
 						.setRemote(user1Uri.toString())
 						.setRefSpecs(MASTER)
 						.call();
+				fail("accepted not permitted fetch");
 			} catch (InvalidRemoteException expected) {
 				// Expected.
 			}
@@ -282,6 +284,7 @@
 						.setRemote(user1Uri.toString())
 						.setRefSpecs(HEADS)
 						.call();
+				fail("accepted not permitted push");
 			} catch (TransportException expected) {
 				assertTrue(expected.getMessage().contains(
 						JGitText.get().pushNotPermitted));
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 317ac32..8acbcce 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
@@ -7,6 +7,7 @@
 import static org.hamcrest.Matchers.theInstance;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -27,6 +28,7 @@
 import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
@@ -427,17 +429,7 @@
 			RefFilter refFilter, ProtocolV2Hook hook, String... inputLines)
 			throws Exception {
 
-		ByteArrayOutputStream send = new ByteArrayOutputStream();
-		PacketLineOut pckOut = new PacketLineOut(send);
-		for (String line : inputLines) {
-			if (line == PacketLineIn.END) {
-				pckOut.end();
-			} else if (line == PacketLineIn.DELIM) {
-				pckOut.writeDelim();
-			} else {
-				pckOut.writeString(line);
-			}
-		}
+		ByteArrayInputStream send = linesAsInputStream(inputLines);
 
 		server.getConfig().setString("protocol", null, "version", "2");
 		UploadPack up = new UploadPack(server);
@@ -451,11 +443,28 @@
 		}
 
 		ByteArrayOutputStream recv = new ByteArrayOutputStream();
-		up.upload(new ByteArrayInputStream(send.toByteArray()), recv, null);
+		up.upload(send, recv, null);
 
 		return new ByteArrayInputStream(recv.toByteArray());
 	}
 
+	private static ByteArrayInputStream linesAsInputStream(String... inputLines)
+			throws IOException {
+		try (ByteArrayOutputStream send = new ByteArrayOutputStream()) {
+			PacketLineOut pckOut = new PacketLineOut(send);
+			for (String line : inputLines) {
+				if (line == PacketLineIn.END) {
+					pckOut.end();
+				} else if (line == PacketLineIn.DELIM) {
+					pckOut.writeDelim();
+				} else {
+					pckOut.writeString(line);
+				}
+			}
+			return new ByteArrayInputStream(send.toByteArray());
+		}
+	}
+
 	/*
 	 * Invokes UploadPack with protocol v2 and sends it the given lines.
 	 * Returns UploadPack's output stream, not including the capability
@@ -484,6 +493,8 @@
 
 		private LsRefsV2Request lsRefsRequest;
 
+		private FetchV2Request fetchRequest;
+
 		@Override
 		public void onCapabilities(CapabilitiesV2Request req) {
 			capabilitiesRequest = req;
@@ -493,6 +504,11 @@
 		public void onLsRefs(LsRefsV2Request req) {
 			lsRefsRequest = req;
 		}
+
+		@Override
+		public void onFetch(FetchV2Request req) {
+			fetchRequest = req;
+		}
 	}
 
 	@Test
@@ -501,18 +517,18 @@
 		ByteArrayInputStream recvStream =
 				uploadPackV2Setup(null, null, hook, PacketLineIn.END);
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
-
 		assertThat(hook.capabilitiesRequest, notNullValue());
 		assertThat(pckIn.readString(), is("version 2"));
 		assertThat(
-			Arrays.asList(pckIn.readString(), pckIn.readString()),
-			// TODO(jonathantanmy) This check is written this way
-			// to make it simple to see that we expect this list of
-			// capabilities, but probably should be loosened to
-			// allow additional commands to be added to the list,
-			// and additional capabilities to be added to existing
-			// commands without requiring test changes.
-			hasItems("ls-refs", "fetch=shallow"));
+				Arrays.asList(pckIn.readString(), pckIn.readString(),
+						pckIn.readString()),
+				// TODO(jonathantanmy) This check is written this way
+				// to make it simple to see that we expect this list of
+				// capabilities, but probably should be loosened to
+				// allow additional commands to be added to the list,
+				// and additional capabilities to be added to existing
+				// commands without requiring test changes.
+				hasItems("ls-refs", "fetch=shallow", "server-option"));
 		assertTrue(pckIn.readString() == PacketLineIn.END);
 	}
 
@@ -525,10 +541,11 @@
 
 		assertThat(pckIn.readString(), is("version 2"));
 		assertThat(
-			Arrays.asList(pckIn.readString(), pckIn.readString()),
-			// TODO(jonathantanmy) This check overspecifies the
-			// order of the capabilities of "fetch".
-			hasItems("ls-refs", "fetch=filter shallow"));
+				Arrays.asList(pckIn.readString(), pckIn.readString(),
+						pckIn.readString()),
+				// TODO(jonathantanmy) This check overspecifies the
+				// order of the capabilities of "fetch".
+				hasItems("ls-refs", "fetch=filter shallow", "server-option"));
 		assertTrue(pckIn.readString() == PacketLineIn.END);
 	}
 
@@ -541,10 +558,12 @@
 
 		assertThat(pckIn.readString(), is("version 2"));
 		assertThat(
-			Arrays.asList(pckIn.readString(), pckIn.readString()),
-			// TODO(jonathantanmy) This check overspecifies the
-			// order of the capabilities of "fetch".
-			hasItems("ls-refs", "fetch=ref-in-want shallow"));
+				Arrays.asList(pckIn.readString(), pckIn.readString(),
+						pckIn.readString()),
+				// TODO(jonathantanmy) This check overspecifies the
+				// order of the capabilities of "fetch".
+				hasItems("ls-refs", "fetch=ref-in-want shallow",
+						"server-option"));
 		assertTrue(pckIn.readString() == PacketLineIn.END);
 	}
 
@@ -557,8 +576,9 @@
 
 		assertThat(pckIn.readString(), is("version 2"));
 		assertThat(
-			Arrays.asList(pckIn.readString(), pckIn.readString()),
-			hasItems("ls-refs", "fetch=shallow"));
+				Arrays.asList(pckIn.readString(), pckIn.readString(),
+						pckIn.readString()),
+				hasItems("ls-refs", "fetch=shallow", "server-option"));
 		assertTrue(pckIn.readString() == PacketLineIn.END);
 	}
 
@@ -572,8 +592,9 @@
 
 		assertThat(pckIn.readString(), is("version 2"));
 		assertThat(
-			Arrays.asList(pckIn.readString(), pckIn.readString()),
-			hasItems("ls-refs", "fetch=shallow"));
+				Arrays.asList(pckIn.readString(), pckIn.readString(),
+						pckIn.readString()),
+				hasItems("ls-refs", "fetch=shallow", "server-option"));
 		assertTrue(pckIn.readString() == PacketLineIn.END);
 	}
 
@@ -718,6 +739,21 @@
 			PacketLineIn.END);
 	}
 
+	@Test
+	public void testV2LsRefsServerOptions() throws Exception {
+		String[] lines = { "command=ls-refs\n",
+				"server-option=one\n", "server-option=two\n",
+				PacketLineIn.DELIM,
+				PacketLineIn.END };
+
+		TestV2Hook testHook = new TestV2Hook();
+		uploadPackV2Setup(null, null, testHook, lines);
+
+		LsRefsV2Request req = testHook.lsRefsRequest;
+		assertEquals(2, req.getServerOptions().size());
+		assertThat(req.getServerOptions(), hasItems("one", "two"));
+	}
+
 	/*
 	 * Parse multiplexed packfile output from upload-pack using protocol V2
 	 * into the client repository.
@@ -1191,6 +1227,270 @@
 	}
 
 	@Test
+	public void testV2FetchShallowSince() throws Exception {
+		PersonIdent person = new PersonIdent(remote.getRepository());
+
+		RevCommit beyondBoundary = remote.commit()
+			.committer(new PersonIdent(person, 1510000000, 0)).create();
+		RevCommit boundary = remote.commit().parent(beyondBoundary)
+			.committer(new PersonIdent(person, 1520000000, 0)).create();
+		RevCommit tooOld = remote.commit()
+			.committer(new PersonIdent(person, 1500000000, 0)).create();
+		RevCommit merge = remote.commit().parent(boundary).parent(tooOld)
+			.committer(new PersonIdent(person, 1530000000, 0)).create();
+
+		remote.update("branch1", merge);
+
+		// Report that we only have "boundary" as a shallow boundary.
+		ByteArrayInputStream recvStream = uploadPackV2(
+			"command=fetch\n",
+			PacketLineIn.DELIM,
+			"shallow " + boundary.toObjectId().getName() + "\n",
+			"deepen-since 1510000\n",
+			"want " + merge.toObjectId().getName() + "\n",
+			"have " + boundary.toObjectId().getName() + "\n",
+			"done\n",
+			PacketLineIn.END);
+		PacketLineIn pckIn = new PacketLineIn(recvStream);
+		assertThat(pckIn.readString(), is("shallow-info"));
+
+		// "merge" is shallow because one of its parents is committed
+		// earlier than the given deepen-since time.
+		assertThat(pckIn.readString(), is("shallow " + merge.toObjectId().getName()));
+
+		// "boundary" is unshallow because its parent committed at or
+		// later than the given deepen-since time.
+		assertThat(pckIn.readString(), is("unshallow " + boundary.toObjectId().getName()));
+
+		assertThat(pckIn.readString(), theInstance(PacketLineIn.DELIM));
+		assertThat(pckIn.readString(), is("packfile"));
+		parsePack(recvStream);
+
+		// The server does not send this because it is committed
+		// earlier than the given deepen-since time.
+		assertFalse(client.hasObject(tooOld.toObjectId()));
+
+		// The server does not send this because the client claims to
+		// have it.
+		assertFalse(client.hasObject(boundary.toObjectId()));
+
+		// The server sends both these commits.
+		assertTrue(client.hasObject(beyondBoundary.toObjectId()));
+		assertTrue(client.hasObject(merge.toObjectId()));
+	}
+
+	@Test
+	public void testV2FetchShallowSince_excludedParentWithMultipleChildren() throws Exception {
+		PersonIdent person = new PersonIdent(remote.getRepository());
+
+		RevCommit base = remote.commit()
+			.committer(new PersonIdent(person, 1500000000, 0)).create();
+		RevCommit child1 = remote.commit().parent(base)
+			.committer(new PersonIdent(person, 1510000000, 0)).create();
+		RevCommit child2 = remote.commit().parent(base)
+			.committer(new PersonIdent(person, 1520000000, 0)).create();
+
+		remote.update("branch1", child1);
+		remote.update("branch2", child2);
+
+		ByteArrayInputStream recvStream = uploadPackV2(
+			"command=fetch\n",
+			PacketLineIn.DELIM,
+			"deepen-since 1510000\n",
+			"want " + child1.toObjectId().getName() + "\n",
+			"want " + child2.toObjectId().getName() + "\n",
+			"done\n",
+			PacketLineIn.END);
+		PacketLineIn pckIn = new PacketLineIn(recvStream);
+		assertThat(pckIn.readString(), is("shallow-info"));
+
+		// "base" is excluded, so its children are shallow.
+		assertThat(
+			Arrays.asList(pckIn.readString(), pckIn.readString()),
+			hasItems(
+				"shallow " + child1.toObjectId().getName(),
+				"shallow " + child2.toObjectId().getName()));
+
+		assertThat(pckIn.readString(), theInstance(PacketLineIn.DELIM));
+		assertThat(pckIn.readString(), is("packfile"));
+		parsePack(recvStream);
+
+		// Only the children are sent.
+		assertFalse(client.hasObject(base.toObjectId()));
+		assertTrue(client.hasObject(child1.toObjectId()));
+		assertTrue(client.hasObject(child2.toObjectId()));
+	}
+
+	@Test
+	public void testV2FetchShallowSince_noCommitsSelected() throws Exception {
+		PersonIdent person = new PersonIdent(remote.getRepository());
+
+		RevCommit tooOld = remote.commit()
+			.committer(new PersonIdent(person, 1500000000, 0)).create();
+
+		remote.update("branch1", tooOld);
+
+		thrown.expect(PackProtocolException.class);
+		thrown.expectMessage("No commits selected for shallow request");
+		uploadPackV2(
+			"command=fetch\n",
+			PacketLineIn.DELIM,
+			"deepen-since 1510000\n",
+			"want " + tooOld.toObjectId().getName() + "\n",
+			"done\n",
+			PacketLineIn.END);
+	}
+
+	@Test
+	public void testV2FetchDeepenNot() throws Exception {
+		RevCommit one = remote.commit().message("one").create();
+		RevCommit two = remote.commit().message("two").parent(one).create();
+		RevCommit three = remote.commit().message("three").parent(two).create();
+		RevCommit side = remote.commit().message("side").parent(one).create();
+		RevCommit merge = remote.commit().message("merge")
+			.parent(three).parent(side).create();
+
+		remote.update("branch1", merge);
+		remote.update("side", side);
+
+		// The client is a shallow clone that only has "three", and
+		// wants "merge" while excluding "side".
+		ByteArrayInputStream recvStream = uploadPackV2(
+			"command=fetch\n",
+			PacketLineIn.DELIM,
+			"shallow " + three.toObjectId().getName() + "\n",
+			"deepen-not side\n",
+			"want " + merge.toObjectId().getName() + "\n",
+			"have " + three.toObjectId().getName() + "\n",
+			"done\n",
+			PacketLineIn.END);
+		PacketLineIn pckIn = new PacketLineIn(recvStream);
+		assertThat(pckIn.readString(), is("shallow-info"));
+
+		// "merge" is shallow because "side" is excluded by deepen-not.
+		// "two" is shallow because "one" (as parent of "side") is excluded by deepen-not.
+		assertThat(
+			Arrays.asList(pckIn.readString(), pckIn.readString()),
+			hasItems(
+				"shallow " + merge.toObjectId().getName(),
+				"shallow " + two.toObjectId().getName()));
+
+		// "three" is unshallow because its parent "two" is now available.
+		assertThat(pckIn.readString(), is("unshallow " + three.toObjectId().getName()));
+
+		assertThat(pckIn.readString(), theInstance(PacketLineIn.DELIM));
+		assertThat(pckIn.readString(), is("packfile"));
+		parsePack(recvStream);
+
+		// The server does not send these because they are excluded by
+		// deepen-not.
+		assertFalse(client.hasObject(side.toObjectId()));
+		assertFalse(client.hasObject(one.toObjectId()));
+
+		// The server does not send this because the client claims to
+		// have it.
+		assertFalse(client.hasObject(three.toObjectId()));
+
+		// The server sends both these commits.
+		assertTrue(client.hasObject(merge.toObjectId()));
+		assertTrue(client.hasObject(two.toObjectId()));
+	}
+
+	@Test
+	public void testV2FetchDeepenNot_excludeDescendantOfWant() throws Exception {
+		RevCommit one = remote.commit().message("one").create();
+		RevCommit two = remote.commit().message("two").parent(one).create();
+		RevCommit three = remote.commit().message("three").parent(two).create();
+		RevCommit four = remote.commit().message("four").parent(three).create();
+
+		remote.update("two", two);
+		remote.update("four", four);
+
+		thrown.expect(PackProtocolException.class);
+		thrown.expectMessage("No commits selected for shallow request");
+		uploadPackV2(
+			"command=fetch\n",
+			PacketLineIn.DELIM,
+			"deepen-not four\n",
+			"want " + two.toObjectId().getName() + "\n",
+			"done\n",
+			PacketLineIn.END);
+	}
+
+	@Test
+	public void testV2FetchDeepenNot_supportAnnotatedTags() throws Exception {
+		RevCommit one = remote.commit().message("one").create();
+		RevCommit two = remote.commit().message("two").parent(one).create();
+		RevCommit three = remote.commit().message("three").parent(two).create();
+		RevCommit four = remote.commit().message("four").parent(three).create();
+		RevTag twoTag = remote.tag("twotag", two);
+
+		remote.update("refs/tags/twotag", twoTag);
+		remote.update("four", four);
+
+		ByteArrayInputStream recvStream = uploadPackV2(
+			"command=fetch\n",
+			PacketLineIn.DELIM,
+			"deepen-not twotag\n",
+			"want " + four.toObjectId().getName() + "\n",
+			"done\n",
+			PacketLineIn.END);
+		PacketLineIn pckIn = new PacketLineIn(recvStream);
+		assertThat(pckIn.readString(), is("shallow-info"));
+		assertThat(pckIn.readString(), is("shallow " + three.toObjectId().getName()));
+		assertThat(pckIn.readString(), theInstance(PacketLineIn.DELIM));
+		assertThat(pckIn.readString(), is("packfile"));
+		parsePack(recvStream);
+		assertFalse(client.hasObject(one.toObjectId()));
+		assertFalse(client.hasObject(two.toObjectId()));
+		assertTrue(client.hasObject(three.toObjectId()));
+		assertTrue(client.hasObject(four.toObjectId()));
+	}
+
+	@Test
+	public void testV2FetchDeepenNot_excludedParentWithMultipleChildren() throws Exception {
+		PersonIdent person = new PersonIdent(remote.getRepository());
+
+		RevCommit base = remote.commit()
+			.committer(new PersonIdent(person, 1500000000, 0)).create();
+		RevCommit child1 = remote.commit().parent(base)
+			.committer(new PersonIdent(person, 1510000000, 0)).create();
+		RevCommit child2 = remote.commit().parent(base)
+			.committer(new PersonIdent(person, 1520000000, 0)).create();
+
+		remote.update("base", base);
+		remote.update("branch1", child1);
+		remote.update("branch2", child2);
+
+		ByteArrayInputStream recvStream = uploadPackV2(
+			"command=fetch\n",
+			PacketLineIn.DELIM,
+			"deepen-not base\n",
+			"want " + child1.toObjectId().getName() + "\n",
+			"want " + child2.toObjectId().getName() + "\n",
+			"done\n",
+			PacketLineIn.END);
+		PacketLineIn pckIn = new PacketLineIn(recvStream);
+		assertThat(pckIn.readString(), is("shallow-info"));
+
+		// "base" is excluded, so its children are shallow.
+		assertThat(
+			Arrays.asList(pckIn.readString(), pckIn.readString()),
+			hasItems(
+				"shallow " + child1.toObjectId().getName(),
+				"shallow " + child2.toObjectId().getName()));
+
+		assertThat(pckIn.readString(), theInstance(PacketLineIn.DELIM));
+		assertThat(pckIn.readString(), is("packfile"));
+		parsePack(recvStream);
+
+		// Only the children are sent.
+		assertFalse(client.hasObject(base.toObjectId()));
+		assertTrue(client.hasObject(child1.toObjectId()));
+		assertTrue(client.hasObject(child2.toObjectId()));
+	}
+
+	@Test
 	public void testV2FetchUnrecognizedArgument() throws Exception {
 		thrown.expect(PackProtocolException.class);
 		thrown.expectMessage("unexpected invalid-argument");
@@ -1202,6 +1502,21 @@
 	}
 
 	@Test
+	public void testV2FetchServerOptions() throws Exception {
+		String[] lines = { "command=fetch\n", "server-option=one\n",
+				"server-option=two\n", PacketLineIn.DELIM,
+				PacketLineIn.END };
+
+		TestV2Hook testHook = new TestV2Hook();
+		uploadPackV2Setup(null, null, testHook, lines);
+
+		FetchV2Request req = testHook.fetchRequest;
+		assertNotNull(req);
+		assertEquals(2, req.getServerOptions().size());
+		assertThat(req.getServerOptions(), hasItems("one", "two"));
+	}
+
+	@Test
 	public void testV2FetchFilter() throws Exception {
 		RevBlob big = remote.blob("foobar");
 		RevBlob small = remote.blob("fooba");
@@ -1455,6 +1770,45 @@
 		assertTrue(client.hasObject(three.toObjectId()));
 	}
 
+	@Test
+	public void testGetPeerAgentProtocolV0() throws Exception {
+		RevCommit one = remote.commit().message("1").create();
+		remote.update("one", one);
+
+		UploadPack up = new UploadPack(server);
+		ByteArrayInputStream send = linesAsInputStream(
+				"want " + one.getName() + " agent=JGit-test/1.2.3\n",
+				PacketLineIn.END,
+				"have 11cedf1b796d44207da702f7d420684022fc0f09\n", "done\n");
+
+		ByteArrayOutputStream recv = new ByteArrayOutputStream();
+		up.upload(send, recv, null);
+
+		assertEquals(up.getPeerUserAgent(), "JGit-test/1.2.3");
+	}
+
+	@Test
+	public void testGetPeerAgentProtocolV2() throws Exception {
+		server.getConfig().setString("protocol", null, "version", "2");
+
+		RevCommit one = remote.commit().message("1").create();
+		remote.update("one", one);
+
+		UploadPack up = new UploadPack(server);
+		up.setExtraParameters(Sets.of("version=2"));
+
+		ByteArrayInputStream send = linesAsInputStream(
+				"command=fetch\n", "agent=JGit-test/1.2.4\n",
+				PacketLineIn.DELIM, "want " + one.getName() + "\n",
+				"have 11cedf1b796d44207da702f7d420684022fc0f09\n", "done\n",
+				PacketLineIn.END);
+
+		ByteArrayOutputStream recv = new ByteArrayOutputStream();
+		up.upload(send, recv, null);
+
+		assertEquals(up.getPeerUserAgent(), "JGit-test/1.2.4");
+	}
+
 	private static class RejectAllRefFilter implements RefFilter {
 		@Override
 		public Map<String, Ref> filter(Map<String, Ref> refs) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java
index f2fb022..4750d15 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java
@@ -651,7 +651,8 @@
 			Properties props = Props.discover();
 			props.put(AmazonS3.Keys.PASSWORD, JGIT_PASS);
 			props.put(AmazonS3.Keys.CRYPTO_ALG, algorithm);
-			try (PrintWriter writer = new PrintWriter(JGIT_CONF_FILE)) {
+			try (PrintWriter writer = new PrintWriter(JGIT_CONF_FILE,
+					UTF_8.name())) {
 				props.store(writer, "JGIT S3 connection configuration file.");
 			}
 		}
@@ -665,7 +666,8 @@
 		static void configCreate(Properties source) throws Exception {
 			Properties target = Props.discover();
 			target.putAll(source);
-			try (PrintWriter writer = new PrintWriter(JGIT_CONF_FILE)) {
+			try (PrintWriter writer = new PrintWriter(JGIT_CONF_FILE,
+					UTF_8.name())) {
 				target.store(writer, "JGIT S3 connection configuration file.");
 			}
 		}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/http/JDKHttpConnectionTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/http/JDKHttpConnectionTest.java
new file mode 100644
index 0000000..10ee829
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/http/JDKHttpConnectionTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2018 Matthias Sohn <matthias.sohn@sap.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.transport.http;
+
+import static java.util.Arrays.asList;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.net.HttpURLConnection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class JDKHttpConnectionTest {
+
+	private Map<String, List<String>> headers = new HashMap<>();
+
+	private HttpURLConnection u;
+
+	private JDKHttpConnection c;
+
+	@Before
+	public void setup() {
+		u = mock(HttpURLConnection.class);
+		c = new JDKHttpConnection(u);
+		headers.put("ABC", asList("x"));
+	}
+
+	@Test
+	public void testSingle() {
+		when(u.getHeaderFields()).thenReturn(headers);
+		assertValues("AbC", "x");
+	}
+
+	@Test
+	public void testMultiple1() {
+		headers.put("abc", asList("a"));
+		headers.put("aBC", asList("d", "e"));
+		headers.put("ABc", Collections.emptyList());
+		headers.put("AbC", (List<String>) null);
+		when(u.getHeaderFields()).thenReturn(headers);
+		assertValues("AbC", "a", "d", "e", "x");
+	}
+
+	@Test
+	public void testMultiple2() {
+		headers.put("ab", asList("y", "z", "z"));
+		when(u.getHeaderFields()).thenReturn(headers);
+		assertValues("ab", "z", "y", "z");
+		assertValues("abc", "x");
+		assertValues("aBc", "x");
+		assertValues("AbCd");
+	}
+
+	@Test
+	public void testCommaSeparatedList() {
+		headers.put("abc", asList("a,b,c", "d"));
+		when(u.getHeaderFields()).thenReturn(headers);
+		assertValues("Abc", "a,b,c", "x", "d");
+	}
+
+	private void assertValues(String key, String... values) {
+		List<String> l = new LinkedList<>();
+		List<String> hf = c.getHeaderFields(key);
+		if (hf != null) {
+			l.addAll(hf);
+		}
+		for (String v : values) {
+			if (!l.remove(v)) {
+				fail("value " + v + " not found");
+			}
+		}
+		assertTrue("found unexpected entries " + l, l.isEmpty());
+	}
+
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/AbstractTreeIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/AbstractTreeIteratorTest.java
index 934984f..8afd49a 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/AbstractTreeIteratorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/AbstractTreeIteratorTest.java
@@ -66,7 +66,7 @@
 		return s > 0 ? path.substring(0, s) : "";
 	}
 
-	public class FakeTreeIterator extends WorkingTreeIterator {
+	public static class FakeTreeIterator extends WorkingTreeIterator {
 		public FakeTreeIterator(String pathName, FileMode fileMode) {
 			super(prefix(pathName), new Config().get(WorkingTreeOptions.KEY));
 			mode = fileMode.getBits();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilterTest.java
index cba35d8..ea5db09 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilterTest.java
@@ -42,6 +42,7 @@
  */
 package org.eclipse.jgit.treewalk.filter;
 
+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.assertTrue;
@@ -114,7 +115,7 @@
 	}
 
 	private ObjectId id(String data) {
-		byte[] bytes = data.getBytes();
+		byte[] bytes = data.getBytes(UTF_8);
 		return db.newObjectInserter().idFor(Constants.OBJ_BLOB, bytes);
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/BlockListTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/BlockListTest.java
index 0a3de85..dca9c57 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/BlockListTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/BlockListTest.java
@@ -47,6 +47,7 @@
 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.util.Iterator;
 
@@ -84,18 +85,21 @@
 
 		try {
 			list.get(-1);
+			fail("accepted out-of-bounds index");
 		} catch (IndexOutOfBoundsException badIndex) {
 			assertEquals(String.valueOf(-1), badIndex.getMessage());
 		}
 
 		try {
 			list.get(0);
+			fail("accepted out-of-bounds index");
 		} catch (IndexOutOfBoundsException badIndex) {
 			assertEquals(String.valueOf(0), badIndex.getMessage());
 		}
 
 		try {
 			list.get(4);
+			fail("accepted out-of-bounds index");
 		} catch (IndexOutOfBoundsException badIndex) {
 			assertEquals(String.valueOf(4), badIndex.getMessage());
 		}
@@ -114,6 +118,7 @@
 
 		try {
 			list.get(3);
+			fail("accepted out-of-bounds index");
 		} catch (IndexOutOfBoundsException badIndex) {
 			assertEquals(String.valueOf(3), badIndex.getMessage());
 		}
@@ -125,18 +130,21 @@
 
 		try {
 			list.set(-1, "foo");
+			fail("accepted out-of-bounds index");
 		} catch (IndexOutOfBoundsException badIndex) {
 			assertEquals(String.valueOf(-1), badIndex.getMessage());
 		}
 
 		try {
 			list.set(0, "foo");
+			fail("accepted out-of-bounds index");
 		} catch (IndexOutOfBoundsException badIndex) {
 			assertEquals(String.valueOf(0), badIndex.getMessage());
 		}
 
 		try {
 			list.set(4, "foo");
+			fail("accepted out-of-bounds index");
 		} catch (IndexOutOfBoundsException badIndex) {
 			assertEquals(String.valueOf(4), badIndex.getMessage());
 		}
@@ -161,6 +169,7 @@
 
 		try {
 			list.set(3, "bar");
+			fail("accepted out-of-bounds index");
 		} catch (IndexOutOfBoundsException badIndex) {
 			assertEquals(String.valueOf(3), badIndex.getMessage());
 		}
@@ -323,12 +332,14 @@
 
 		try {
 			list.add(-1, Integer.valueOf(42));
+			fail("accepted out-of-bounds index");
 		} catch (IndexOutOfBoundsException badIndex) {
 			assertEquals(String.valueOf(-1), badIndex.getMessage());
 		}
 
 		try {
 			list.add(4, Integer.valueOf(42));
+			fail("accepted out-of-bounds index");
 		} catch (IndexOutOfBoundsException badIndex) {
 			assertEquals(String.valueOf(4), badIndex.getMessage());
 		}
@@ -341,12 +352,14 @@
 
 		try {
 			list.remove(-1);
+			fail("accepted out-of-bounds index");
 		} catch (IndexOutOfBoundsException badIndex) {
 			assertEquals(String.valueOf(-1), badIndex.getMessage());
 		}
 
 		try {
 			list.remove(4);
+			fail("accepted out-of-bounds index");
 		} catch (IndexOutOfBoundsException badIndex) {
 			assertEquals(String.valueOf(4), badIndex.getMessage());
 		}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java
index e34c3ce..e5fcbf9 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java
@@ -199,7 +199,8 @@
 		assumeSupportedPlatform();
 
 		writeHookFile(PreCommitHook.NAME,
-				"#!/bin/sh\necho \"test $1 $2\"\nread INPUT\necho $INPUT\necho 1>&2 \"stderr\"");
+				"#!/bin/sh\necho \"test $1 $2\"\nread INPUT\necho $INPUT\n"
+						+ "echo $GIT_DIR\necho $GIT_WORK_TREE\necho 1>&2 \"stderr\"");
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		ByteArrayOutputStream err = new ByteArrayOutputStream();
 		ProcessResult res = FS.DETECTED.runHookIfPresent(db,
@@ -208,7 +209,9 @@
 				"arg1", "arg2" },
 				new PrintStream(out), new PrintStream(err), "stdin");
 
-		assertEquals("unexpected hook output", "test arg1 arg2\nstdin\n",
+		assertEquals("unexpected hook output",
+				"test arg1 arg2\nstdin\n" + db.getDirectory().getAbsolutePath()
+						+ '\n' + db.getWorkTree().getAbsolutePath() + '\n',
 				out.toString("UTF-8"));
 		assertEquals("unexpected output on stderr stream", "stderr\n",
 				err.toString("UTF-8"));
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/IOReadLineTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/IOReadLineTest.java
index 928fb2e..fa303ec 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/IOReadLineTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/IOReadLineTest.java
@@ -43,6 +43,7 @@
 
 package org.eclipse.jgit.util;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 
 import java.io.BufferedReader;
@@ -105,7 +106,7 @@
 
 	private Reader newReader(String in) {
 		Reader r = new InputStreamReader(
-				new ByteArrayInputStream(Constants.encode(in)));
+				new ByteArrayInputStream(Constants.encode(in)), UTF_8);
 		if (buffered) {
 			r = new BufferedReader(r);
 		}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_LineMapTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_LineMapTest.java
index 7630c11..e7bfa00 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_LineMapTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_LineMapTest.java
@@ -85,10 +85,11 @@
 	}
 
 	@Test
-	public void testBinary() {
+	public void testNulByte() {
 		final byte[] buf = "xxxfoo\nb\0ar".getBytes(ISO_8859_1);
 		final IntList map = RawParseUtils.lineMap(buf, 3, buf.length);
-		assertArrayEquals(new int[]{Integer.MIN_VALUE, 3, buf.length}, asInts(map));
+		assertArrayEquals(new int[] { Integer.MIN_VALUE, 3, 7, buf.length },
+				asInts(map));
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RunExternalScriptTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RunExternalScriptTest.java
index 7c0985e..19af836 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RunExternalScriptTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RunExternalScriptTest.java
@@ -43,6 +43,7 @@
 
 package org.eclipse.jgit.util;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 
 import java.io.ByteArrayInputStream;
@@ -75,10 +76,10 @@
 		File script = writeTempFile("cat -");
 		int rc = FS.DETECTED.runProcess(
 				new ProcessBuilder("sh", script.getPath()), out, err,
-				new ByteArrayInputStream(inputStr.getBytes()));
+				new ByteArrayInputStream(inputStr.getBytes(UTF_8)));
 		assertEquals(0, rc);
-		assertEquals(inputStr, new String(out.toByteArray()));
-		assertEquals("", new String(err.toByteArray()));
+		assertEquals(inputStr, new String(out.toByteArray(), UTF_8));
+		assertEquals("", new String(err.toByteArray(), UTF_8));
 	}
 
 	@Test
@@ -88,8 +89,8 @@
 				new ProcessBuilder("sh", script.getPath()), out, err,
 				(InputStream) null);
 		assertEquals(0, rc);
-		assertEquals("", new String(out.toByteArray()));
-		assertEquals("", new String(err.toByteArray()));
+		assertEquals("", new String(out.toByteArray(), UTF_8));
+		assertEquals("", new String(err.toByteArray(), UTF_8));
 	}
 
 	@Test
@@ -99,8 +100,8 @@
 				new ProcessBuilder("sh",
 				script.getPath(), "a", "b", "c"), out, err, (InputStream) null);
 		assertEquals(0, rc);
-		assertEquals("3,a,b,c,,,\n", new String(out.toByteArray()));
-		assertEquals("", new String(err.toByteArray()));
+		assertEquals("3,a,b,c,,,\n", new String(out.toByteArray(), UTF_8));
+		assertEquals("", new String(err.toByteArray(), UTF_8));
 	}
 
 	@Test
@@ -110,8 +111,8 @@
 				new ProcessBuilder("sh", script.getPath(), "a", "b", "c"),
 				out, err, (InputStream) null);
 		assertEquals(3, rc);
-		assertEquals("", new String(out.toByteArray()));
-		assertEquals("", new String(err.toByteArray()));
+		assertEquals("", new String(out.toByteArray(), UTF_8));
+		assertEquals("", new String(err.toByteArray(), UTF_8));
 	}
 
 	@Test
@@ -121,8 +122,8 @@
 				new ProcessBuilder("sh", script.getPath()), null, err,
 				(InputStream) null);
 		assertEquals(0, rc);
-		assertEquals("", new String(out.toByteArray()));
-		assertEquals("", new String(err.toByteArray()));
+		assertEquals("", new String(out.toByteArray(), UTF_8));
+		assertEquals("", new String(err.toByteArray(), UTF_8));
 	}
 
 	@Test
@@ -132,8 +133,8 @@
 				new ProcessBuilder("sh", script.getPath()), null, err,
 				(InputStream) null);
 		assertEquals(0, rc);
-		assertEquals("", new String(out.toByteArray()));
-		assertEquals("hi" + LF, new String(err.toByteArray()));
+		assertEquals("", new String(out.toByteArray(), UTF_8));
+		assertEquals("hi" + LF, new String(err.toByteArray(), UTF_8));
 	}
 
 	@Test
@@ -142,10 +143,10 @@
 		File script = writeTempFile("echo $#,$1,$2,$3,$4,$5,$6 >&2 ; cat -; exit 5");
 		int rc = FS.DETECTED.runProcess(
 				new ProcessBuilder("sh", script.getPath(), "a", "b", "c"),
-				out, err, new ByteArrayInputStream(inputStr.getBytes()));
+				out, err, new ByteArrayInputStream(inputStr.getBytes(UTF_8)));
 		assertEquals(5, rc);
-		assertEquals(inputStr, new String(out.toByteArray()));
-		assertEquals("3,a,b,c,,," + LF, new String(err.toByteArray()));
+		assertEquals(inputStr, new String(out.toByteArray(), UTF_8));
+		assertEquals("3,a,b,c,,," + LF, new String(err.toByteArray(), UTF_8));
 	}
 
 	@Test(expected = IOException.class)
@@ -172,10 +173,11 @@
 		File script = writeTempFile("cat -");
 		ProcessBuilder pb = new ProcessBuilder("sh", script.getPath());
 		ExecutionResult res = FS.DETECTED.execute(pb,
-				new ByteArrayInputStream(inputStr.getBytes()));
+				new ByteArrayInputStream(inputStr.getBytes(UTF_8)));
 		assertEquals(0, res.getRc());
-		assertEquals(inputStr, new String(res.getStdout().toByteArray()));
-		assertEquals("", new String(res.getStderr().toByteArray()));
+		assertEquals(inputStr,
+				new String(res.getStdout().toByteArray(), UTF_8));
+		assertEquals("", new String(res.getStderr().toByteArray(), UTF_8));
 	}
 
 	@Test
@@ -184,8 +186,9 @@
 		ProcessBuilder pb = new ProcessBuilder("sh", script.getPath());
 		ExecutionResult res = FS.DETECTED.execute(pb, null);
 		assertEquals(0, res.getRc());
-		assertEquals("", new String(res.getStdout().toByteArray()));
-		assertEquals("hi" + LF, new String(res.getStderr().toByteArray()));
+		assertEquals("", new String(res.getStdout().toByteArray(), UTF_8));
+		assertEquals("hi" + LF,
+				new String(res.getStderr().toByteArray(), UTF_8));
 	}
 
 	@Test
@@ -197,11 +200,12 @@
 		ProcessBuilder pb = new ProcessBuilder("sh", script.getPath(), "a",
 				"b", "c");
 		ExecutionResult res = FS.DETECTED.execute(pb,
-				new ByteArrayInputStream(inputStr.getBytes()));
+				new ByteArrayInputStream(inputStr.getBytes(UTF_8)));
 		assertEquals(5, res.getRc());
-		assertEquals(inputStr, new String(res.getStdout().toByteArray()));
+		assertEquals(inputStr,
+				new String(res.getStdout().toByteArray(), UTF_8));
 		assertEquals("3,a,b,c,,," + LF,
-				new String(res.getStderr().toByteArray()));
+				new String(res.getStderr().toByteArray(), UTF_8));
 	}
 
 	private File writeTempFile(String body) throws IOException {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFInputStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFInputStreamTest.java
index 1272e16..8f77c55 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFInputStreamTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFInputStreamTest.java
@@ -43,6 +43,8 @@
 
 package org.eclipse.jgit.util.io;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -89,8 +91,8 @@
 
 	private void assertNoCrLfHelper(String expect, String input)
 			throws IOException {
-		byte[] inbytes = input.getBytes();
-		byte[] expectBytes = expect.getBytes();
+		byte[] inbytes = input.getBytes(UTF_8);
+		byte[] expectBytes = expect.getBytes(UTF_8);
 		for (int i = 0; i < 5; ++i) {
 			byte[] buf = new byte[i];
 			try (ByteArrayInputStream bis = new ByteArrayInputStream(inbytes);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFOutputStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFOutputStreamTest.java
index 0655827..3a3dc81 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFOutputStreamTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFOutputStreamTest.java
@@ -44,6 +44,8 @@
 
 package org.eclipse.jgit.util.io;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -91,8 +93,8 @@
 
 	private void assertNoCrLfHelper(String expect, String input)
 			throws IOException {
-		byte[] inbytes = input.getBytes();
-		byte[] expectBytes = expect.getBytes();
+		byte[] inbytes = input.getBytes(UTF_8);
+		byte[] expectBytes = expect.getBytes(UTF_8);
 		for (int i = -4; i < 5; ++i) {
 			int size = Math.abs(i);
 			byte[] buf = new byte[size];
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/TimeoutOutputStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/TimeoutOutputStreamTest.java
index a63b1cb..c35f90c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/TimeoutOutputStreamTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/TimeoutOutputStreamTest.java
@@ -282,7 +282,7 @@
 		return System.currentTimeMillis();
 	}
 
-	private final class FullPipeInputStream extends PipedInputStream {
+	private static final class FullPipeInputStream extends PipedInputStream {
 		FullPipeInputStream(PipedOutputStream src) throws IOException {
 			super(src);
 			src.write(new byte[PIPE_SIZE]);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/UnionInputStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/UnionInputStreamTest.java
index b824fae..a6e0eed 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/UnionInputStreamTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/UnionInputStreamTest.java
@@ -167,6 +167,8 @@
 
 		u.add(new ByteArrayInputStream(new byte[] { 20, 30 }) {
 			@Override
+			@SuppressWarnings("UnsynchronizedOverridesSynchronized")
+			// This is only used in tests and is thread-safe
 			public long skip(long n) {
 				return 0;
 			}
@@ -259,6 +261,11 @@
 			public int read() throws IOException {
 				throw new IOException("Expected");
 			}
+
+			@Override
+			public int read(byte b[], int off, int len) throws IOException {
+				throw new IOException("Expected");
+			}
 		};
 		@SuppressWarnings("resource" /* java 7 */)
 		final UnionInputStream u = new UnionInputStream(
diff --git a/org.eclipse.jgit.ui/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.ui/.settings/org.eclipse.jdt.core.prefs
index 89394ec..525ac67 100644
--- a/org.eclipse.jgit.ui/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.ui/.settings/org.eclipse.jdt.core.prefs
@@ -91,7 +91,7 @@
 org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
 org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
 org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning
-org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error
 org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
diff --git a/org.eclipse.jgit.ui/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.ui/.settings/org.eclipse.mylyn.team.ui.prefs
index 0cba949..2fca432 100644
--- a/org.eclipse.jgit.ui/.settings/org.eclipse.mylyn.team.ui.prefs
+++ b/org.eclipse.jgit.ui/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -1,3 +1,3 @@
 #Tue Jul 19 20:11:28 CEST 2011
-commit.comment.template=${task.description} \n\nBug\: ${task.key}
+commit.comment.template=${task.description}\n\nBug\: ${task.key}
 eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.ui/META-INF/MANIFEST.MF b/org.eclipse.jgit.ui/META-INF/MANIFEST.MF
index 18d0bd5..380de32 100644
--- a/org.eclipse.jgit.ui/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ui/META-INF/MANIFEST.MF
@@ -4,14 +4,14 @@
 Bundle-Name: %plugin_name
 Automatic-Module-Name: org.eclipse.jgit.ui
 Bundle-SymbolicName: org.eclipse.jgit.ui
-Bundle-Version: 5.1.9.qualifier
+Bundle-Version: 5.2.3.qualifier
 Bundle-Vendor: %provider_name
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Export-Package: org.eclipse.jgit.awtui;version="5.1.9"
-Import-Package: org.eclipse.jgit.errors;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.lib;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.nls;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.revplot;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.revwalk;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.transport;version="[5.1.9,5.2.0)",
- org.eclipse.jgit.util;version="[5.1.9,5.2.0)"
+Export-Package: org.eclipse.jgit.awtui;version="5.2.3"
+Import-Package: org.eclipse.jgit.errors;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.lib;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.nls;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.revplot;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.revwalk;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.transport;version="[5.2.3,5.3.0)",
+ org.eclipse.jgit.util;version="[5.2.3,5.3.0)"
diff --git a/org.eclipse.jgit.ui/pom.xml b/org.eclipse.jgit.ui/pom.xml
index 0260d77..0cb3d65 100644
--- a/org.eclipse.jgit.ui/pom.xml
+++ b/org.eclipse.jgit.ui/pom.xml
@@ -52,7 +52,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>5.1.9-SNAPSHOT</version>
+    <version>5.2.3-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.ui</artifactId>
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
index 524c591..4131901 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -3,8 +3,8 @@
     <resource path="META-INF/MANIFEST.MF">
         <filter id="924844039">
             <message_arguments>
-                <message_argument value="5.1.8"/>
-                <message_argument value="5.1.0"/>
+                <message_argument value="5.2.3"/>
+                <message_argument value="5.2.0"/>
             </message_arguments>
         </filter>
     </resource>
@@ -22,22 +22,6 @@
             </message_arguments>
         </filter>
     </resource>
-    <resource path="src/org/eclipse/jgit/lib/GitmoduleEntry.java" type="org.eclipse.jgit.lib.GitmoduleEntry">
-        <filter id="1109393411">
-            <message_arguments>
-                <message_argument value="4.7.5"/>
-                <message_argument value="org.eclipse.jgit.lib.GitmoduleEntry"/>
-            </message_arguments>
-        </filter>
-    </resource>
-    <resource path="src/org/eclipse/jgit/lib/ObjectChecker.java" type="org.eclipse.jgit.lib.ObjectChecker">
-        <filter id="1142947843">
-            <message_arguments>
-                <message_argument value="4.7.5"/>
-                <message_argument value="getGitsubmodules()"/>
-            </message_arguments>
-        </filter>
-    </resource>
     <resource path="src/org/eclipse/jgit/storage/pack/PackConfig.java" type="org.eclipse.jgit.storage.pack.PackConfig">
         <filter id="336658481">
             <message_arguments>
diff --git a/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs
index 13c32a6..ef6f5e7 100644
--- a/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs
@@ -91,7 +91,7 @@
 org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
 org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
 org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning
-org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error
 org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
diff --git a/org.eclipse.jgit/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit/.settings/org.eclipse.mylyn.team.ui.prefs
index 0cba949..2fca432 100644
--- a/org.eclipse.jgit/.settings/org.eclipse.mylyn.team.ui.prefs
+++ b/org.eclipse.jgit/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -1,3 +1,3 @@
 #Tue Jul 19 20:11:28 CEST 2011
-commit.comment.template=${task.description} \n\nBug\: ${task.key}
+commit.comment.template=${task.description}\n\nBug\: ${task.key}
 eclipse.preferences.version=1
diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF
index 470871c..b03ff4a 100644
--- a/org.eclipse.jgit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit/META-INF/MANIFEST.MF
@@ -3,12 +3,12 @@
 Bundle-Name: %plugin_name
 Automatic-Module-Name: org.eclipse.jgit
 Bundle-SymbolicName: org.eclipse.jgit
-Bundle-Version: 5.1.9.qualifier
+Bundle-Version: 5.2.3.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
-Export-Package: org.eclipse.jgit.annotations;version="5.1.9",
- org.eclipse.jgit.api;version="5.1.9";
+Export-Package: org.eclipse.jgit.annotations;version="5.2.3",
+ org.eclipse.jgit.api;version="5.2.3";
   uses:="org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.diff,
@@ -22,65 +22,73 @@
    org.eclipse.jgit.submodule,
    org.eclipse.jgit.transport,
    org.eclipse.jgit.merge",
- org.eclipse.jgit.api.errors;version="5.1.9";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.errors",
- org.eclipse.jgit.attributes;version="5.1.9",
- org.eclipse.jgit.blame;version="5.1.9";
+ org.eclipse.jgit.api.errors;version="5.2.3";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.errors",
+ org.eclipse.jgit.attributes;version="5.2.3",
+ org.eclipse.jgit.blame;version="5.2.3";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.diff",
- org.eclipse.jgit.diff;version="5.1.9";
+ org.eclipse.jgit.diff;version="5.2.3";
   uses:="org.eclipse.jgit.patch,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.util",
- org.eclipse.jgit.dircache;version="5.1.9";
+ org.eclipse.jgit.dircache;version="5.2.3";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.util,
    org.eclipse.jgit.events,
    org.eclipse.jgit.attributes",
- org.eclipse.jgit.errors;version="5.1.9";
+ org.eclipse.jgit.errors;version="5.2.3";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.internal.storage.pack,
    org.eclipse.jgit.transport,
    org.eclipse.jgit.dircache",
- org.eclipse.jgit.events;version="5.1.9";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.fnmatch;version="5.1.9",
- org.eclipse.jgit.gitrepo;version="5.1.9";
+ org.eclipse.jgit.events;version="5.2.3";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.fnmatch;version="5.2.3",
+ org.eclipse.jgit.gitrepo;version="5.2.3";
   uses:="org.eclipse.jgit.api,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
    org.xml.sax.helpers,
    org.xml.sax",
- org.eclipse.jgit.gitrepo.internal;version="5.1.9";x-internal:=true,
- org.eclipse.jgit.hooks;version="5.1.9";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.ignore;version="5.1.9",
- org.eclipse.jgit.ignore.internal;version="5.1.9";x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal;version="5.1.9";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test",
- org.eclipse.jgit.internal.fsck;version="5.1.9";x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.ketch;version="5.1.9";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.dfs;version="5.1.9";
+ org.eclipse.jgit.gitrepo.internal;version="5.2.3";x-internal:=true,
+ org.eclipse.jgit.hooks;version="5.2.3";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.ignore;version="5.2.3",
+ org.eclipse.jgit.ignore.internal;version="5.2.3";x-friends:="org.eclipse.jgit.test",
+ org.eclipse.jgit.internal;version="5.2.3";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test",
+ org.eclipse.jgit.internal.fsck;version="5.2.3";x-friends:="org.eclipse.jgit.test",
+ org.eclipse.jgit.internal.ketch;version="5.2.3";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.internal.revwalk;version="5.2.3";x-internal:=true,
+ org.eclipse.jgit.internal.storage.dfs;version="5.2.3";
   x-friends:="org.eclipse.jgit.test,
    org.eclipse.jgit.http.server,
    org.eclipse.jgit.http.test,
    org.eclipse.jgit.lfs.test",
- org.eclipse.jgit.internal.storage.file;version="5.1.9";
+ org.eclipse.jgit.internal.storage.file;version="5.2.3";
   x-friends:="org.eclipse.jgit.test,
    org.eclipse.jgit.junit,
    org.eclipse.jgit.junit.http,
    org.eclipse.jgit.http.server,
    org.eclipse.jgit.lfs,
    org.eclipse.jgit.pgm,
-   org.eclipse.jgit.pgm.test",
- org.eclipse.jgit.internal.storage.io;version="5.1.9";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.pack;version="5.1.9";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.reftable;version="5.1.9";
-  x-friends:="org.eclipse.jgit.http.test,org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.reftree;version="5.1.9";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.lib;version="5.1.9";
+   org.eclipse.jgit.pgm.test,
+   org.eclipse.jgit.ssh.apache",
+ org.eclipse.jgit.internal.storage.io;version="5.2.3";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.internal.storage.pack;version="5.2.3";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.internal.storage.reftable;version="5.2.3";
+  x-friends:="org.eclipse.jgit.http.test,
+   org.eclipse.jgit.junit,
+   org.eclipse.jgit.test,
+   org.eclipse.jgit.pgm",
+ org.eclipse.jgit.internal.storage.reftree;version="5.2.3";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.internal.submodule;version="5.2.3";x-internal:=true,
+ org.eclipse.jgit.internal.transport.parser;version="5.2.3";x-friends:="org.eclipse.jgit.test",
+ org.eclipse.jgit.internal.transport.ssh;version="5.2.3";x-friends:="org.eclipse.jgit.ssh.apache",
+ org.eclipse.jgit.lib;version="5.2.3";
   uses:="org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.util,
@@ -90,33 +98,33 @@
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.transport,
    org.eclipse.jgit.submodule",
- org.eclipse.jgit.lib.internal;version="5.1.9";x-internal:=true,
- org.eclipse.jgit.merge;version="5.1.9";
+ org.eclipse.jgit.lib.internal;version="5.2.3";x-internal:=true,
+ org.eclipse.jgit.merge;version="5.2.3";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.diff,
    org.eclipse.jgit.dircache,
    org.eclipse.jgit.api",
- org.eclipse.jgit.nls;version="5.1.9",
- org.eclipse.jgit.notes;version="5.1.9";
+ org.eclipse.jgit.nls;version="5.2.3",
+ org.eclipse.jgit.notes;version="5.2.3";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.merge",
- org.eclipse.jgit.patch;version="5.1.9";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.diff",
- org.eclipse.jgit.revplot;version="5.1.9";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.revwalk",
- org.eclipse.jgit.revwalk;version="5.1.9";
+ org.eclipse.jgit.patch;version="5.2.3";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.diff",
+ org.eclipse.jgit.revplot;version="5.2.3";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.revwalk",
+ org.eclipse.jgit.revwalk;version="5.2.3";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.diff,
    org.eclipse.jgit.revwalk.filter",
- org.eclipse.jgit.revwalk.filter;version="5.1.9";uses:="org.eclipse.jgit.revwalk,org.eclipse.jgit.lib,org.eclipse.jgit.util",
- org.eclipse.jgit.storage.file;version="5.1.9";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.util",
- org.eclipse.jgit.storage.pack;version="5.1.9";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.submodule;version="5.1.9";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.treewalk.filter,org.eclipse.jgit.treewalk",
- org.eclipse.jgit.transport;version="5.1.9";
+ org.eclipse.jgit.revwalk.filter;version="5.2.3";uses:="org.eclipse.jgit.revwalk,org.eclipse.jgit.lib,org.eclipse.jgit.util",
+ org.eclipse.jgit.storage.file;version="5.2.3";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.util",
+ org.eclipse.jgit.storage.pack;version="5.2.3";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.submodule;version="5.2.3";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.treewalk.filter,org.eclipse.jgit.treewalk",
+ org.eclipse.jgit.transport;version="5.2.3";
   uses:="org.eclipse.jgit.transport.resolver,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.internal.storage.pack,
@@ -128,24 +136,24 @@
    org.eclipse.jgit.transport.http,
    org.eclipse.jgit.errors,
    org.eclipse.jgit.storage.pack",
- org.eclipse.jgit.transport.http;version="5.1.9";uses:="javax.net.ssl",
- org.eclipse.jgit.transport.resolver;version="5.1.9";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport",
- org.eclipse.jgit.treewalk;version="5.1.9";
+ org.eclipse.jgit.transport.http;version="5.2.3";uses:="javax.net.ssl",
+ org.eclipse.jgit.transport.resolver;version="5.2.3";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport",
+ org.eclipse.jgit.treewalk;version="5.2.3";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.attributes,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.util,
    org.eclipse.jgit.dircache",
- org.eclipse.jgit.treewalk.filter;version="5.1.9";uses:="org.eclipse.jgit.treewalk",
- org.eclipse.jgit.util;version="5.1.9";
+ org.eclipse.jgit.treewalk.filter;version="5.2.3";uses:="org.eclipse.jgit.treewalk",
+ org.eclipse.jgit.util;version="5.2.3";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.transport.http,
    org.eclipse.jgit.storage.file,
    org.ietf.jgss",
- org.eclipse.jgit.util.io;version="5.1.9",
- org.eclipse.jgit.util.sha1;version="5.1.9",
- org.eclipse.jgit.util.time;version="5.1.9"
+ org.eclipse.jgit.util.io;version="5.2.3",
+ org.eclipse.jgit.util.sha1;version="5.2.3",
+ org.eclipse.jgit.util.time;version="5.2.3"
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)",
  com.jcraft.jsch;version="[0.1.37,0.2.0)",
diff --git a/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
index 8885e64..8e22086 100644
--- a/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit - Sources
 Bundle-SymbolicName: org.eclipse.jgit.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 5.1.9.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit;version="5.1.9.qualifier";roots="."
+Bundle-Version: 5.2.3.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit;version="5.2.3.qualifier";roots="."
diff --git a/org.eclipse.jgit/pom.xml b/org.eclipse.jgit/pom.xml
index eedb5e8..c74e26f 100644
--- a/org.eclipse.jgit/pom.xml
+++ b/org.eclipse.jgit/pom.xml
@@ -53,7 +53,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>5.1.9-SNAPSHOT</version>
+    <version>5.2.3-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit</artifactId>
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
index b7805b1..12fded8 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -302,7 +302,7 @@
 expectedReceivedContentType=expected Content-Type {0}; received Content-Type {1}
 expectedReportForRefNotReceived={0}: expected report for ref {1} not received
 failedAtomicFileCreation=Atomic file creation failed, number of hard links to file {0} was not 2 but {1}"
-failedToDetermineFilterDefinition=An exception occured while determining filter definitions
+failedToDetermineFilterDefinition=An exception occurred while determining filter definitions
 failedUpdatingRefs=failed updating refs
 failureDueToOneOfTheFollowing=Failure due to one of the following:
 failureUpdatingFETCH_HEAD=Failure updating FETCH_HEAD: {0}
@@ -468,6 +468,7 @@
 newlineInQuotesNotAllowed=Newline in quotes not allowed
 noApplyInDelete=No apply in delete
 noClosingBracket=No closing {0} found for {1} at index {2}.
+noCommitsSelectedForShallow=No commits selected for shallow request
 noCredentialsProvider=Authentication is required but no CredentialsProvider has been registered
 noHEADExistsAndNoExplicitStartingRevisionWasSpecified=No HEAD exists and no explicit starting revision was specified
 noHMACsupport=No {0} support: {1}
@@ -638,8 +639,8 @@
 squashCommitNotUpdatingHEAD=Squash commit -- not updating HEAD
 sshCommandFailed=Execution of ssh command ''{0}'' failed with error ''{1}''
 sshUserNameError=Jsch error: failed to set SSH user name correctly to ''{0}''; using ''{1}'' picked up from SSH config file.
-sslFailureExceptionMessage=Secure connection to {0} could not be stablished because of SSL problems
-sslFailureInfo=A secure connection to {0}\ncould not be established because the server''s certificate could not be validated.
+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}
 sslFailureTrustExplanation=Do you want to skip SSL verification for this server?
 sslTrustAlways=Always skip SSL verification for this server from now on
@@ -671,7 +672,6 @@
 submoduleUrlInvalid=Invalid submodule URL ''{0}''
 submodulesNotSupported=Submodules are not supported
 supportOnlyPackIndexVersion2=Only support index version 2
-symlinkCannotBeWrittenAsTheLinkTarget=Symlink "{0}" cannot be written as the link target cannot be read from within Java.
 systemConfigFileInvalid=System wide config file {0} is invalid {1}
 tagAlreadyExists=tag ''{0}'' already exists
 tagNameInvalid=tag name {0} is invalid
@@ -771,7 +771,9 @@
 uriNotFoundWithMessage={0} not found: {1}
 URINotSupported=URI not supported: {0}
 userConfigFileInvalid=User config file {0} invalid {1}
+validatingGitModules=Validating .gitmodules files
 walkFailure=Walk failure.
+wantNoSpaceWithCapabilities=No space between oid and first capability in first want line
 wantNotValid=want {0} not valid
 weeksAgo={0} weeks ago
 windowSizeMustBeLesserThanLimit=Window size must be < limit
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java
index 5b84032..c6f3c67 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java
@@ -42,11 +42,14 @@
  */
 package org.eclipse.jgit.api;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.File;
 import java.io.FileOutputStream;
-import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
 import java.nio.file.StandardCopyOption;
 import java.text.MessageFormat;
 import java.util.ArrayList;
@@ -258,7 +261,8 @@
 		if (sb.length() > 0) {
 			sb.deleteCharAt(sb.length() - 1);
 		}
-		try (FileWriter fw = new FileWriter(f)) {
+		try (Writer fw = new OutputStreamWriter(new FileOutputStream(f),
+				UTF_8)) {
 			fw.write(sb.toString());
 		}
 
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 11edb10..455a2e6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
@@ -269,7 +269,7 @@
 			try {
 				dco = new DirCacheCheckout(repo, headTree, dc,
 						newCommit.getTree());
-				dco.setFailOnConflict(true);
+				dco.setFailOnConflict(!force);
 				dco.setProgressMonitor(monitor);
 				try {
 					dco.checkout();
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 5c06bac..73af8ba 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
@@ -283,12 +283,11 @@
 		config.addURI(u);
 
 		final String dst = (bare ? Constants.R_HEADS : Constants.R_REMOTES
-				+ config.getName() + "/") + "*"; //$NON-NLS-1$//$NON-NLS-2$
-		RefSpec refSpec = new RefSpec();
-		refSpec = refSpec.setForceUpdate(true);
-		refSpec = refSpec.setSourceDestination(Constants.R_HEADS + "*", dst); //$NON-NLS-1$
+				+ config.getName() + '/') + '*';
+		boolean fetchAll = cloneAllBranches || branchesToClone == null
+				|| branchesToClone.isEmpty();
 
-		config.addFetchRefSpec(refSpec);
+		config.setFetchRefSpecs(calculateRefSpecs(fetchAll, dst));
 		config.update(clonedRepo.getConfig());
 
 		clonedRepo.getConfig().save();
@@ -297,27 +296,25 @@
 		FetchCommand command = new FetchCommand(clonedRepo);
 		command.setRemote(remote);
 		command.setProgressMonitor(monitor);
-		command.setTagOpt(TagOpt.FETCH_TAGS);
+		command.setTagOpt(fetchAll ? TagOpt.FETCH_TAGS : TagOpt.AUTO_FOLLOW);
 		configure(command);
 
-		List<RefSpec> specs = calculateRefSpecs(dst);
-		command.setRefSpecs(specs);
-
 		return command.call();
 	}
 
-	private List<RefSpec> calculateRefSpecs(String dst) {
+	private List<RefSpec> calculateRefSpecs(boolean fetchAll, String dst) {
 		RefSpec wcrs = new RefSpec();
 		wcrs = wcrs.setForceUpdate(true);
-		wcrs = wcrs.setSourceDestination(Constants.R_HEADS + "*", dst); //$NON-NLS-1$
+		wcrs = wcrs.setSourceDestination(Constants.R_HEADS + '*', dst);
 		List<RefSpec> specs = new ArrayList<>();
-		if (cloneAllBranches)
-			specs.add(wcrs);
-		else if (branchesToClone != null
-				&& branchesToClone.size() > 0) {
-			for (String selectedRef : branchesToClone)
-				if (wcrs.matchSource(selectedRef))
+		if (!fetchAll) {
+			for (String selectedRef : branchesToClone) {
+				if (wcrs.matchSource(selectedRef)) {
 					specs.add(wcrs.expandFromSource(selectedRef));
+				}
+			}
+		} else {
+			specs.add(wcrs);
 		}
 		return specs;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java
index 28a27a9..29a51a0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java
@@ -44,6 +44,10 @@
  */
 package org.eclipse.jgit.api;
 
+import static org.eclipse.jgit.lib.Constants.HEAD;
+import static org.eclipse.jgit.lib.Constants.R_HEADS;
+import static org.eclipse.jgit.lib.Constants.R_REMOTES;
+
 import java.io.IOException;
 import java.text.MessageFormat;
 import java.util.ArrayList;
@@ -56,7 +60,6 @@
 import org.eclipse.jgit.api.errors.JGitInternalException;
 import org.eclipse.jgit.api.errors.RefNotFoundException;
 import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
@@ -113,17 +116,18 @@
 			Collection<Ref> refs = new ArrayList<>();
 
 			// Also return HEAD if it's detached
-			Ref head = repo.exactRef(Constants.HEAD);
-			if (head != null && head.getLeaf().getName().equals(Constants.HEAD))
+			Ref head = repo.exactRef(HEAD);
+			if (head != null && head.getLeaf().getName().equals(HEAD)) {
 				refs.add(head);
+			}
 
 			if (listMode == null) {
-				refs.addAll(getRefs(Constants.R_HEADS));
+				refs.addAll(repo.getRefDatabase().getRefsByPrefix(R_HEADS));
 			} else if (listMode == ListMode.REMOTE) {
-				refs.addAll(getRefs(Constants.R_REMOTES));
+				refs.addAll(repo.getRefDatabase().getRefsByPrefix(R_REMOTES));
 			} else {
-				refs.addAll(getRefs(Constants.R_HEADS));
-				refs.addAll(getRefs(Constants.R_REMOTES));
+				refs.addAll(repo.getRefDatabase().getRefsByPrefix(R_HEADS,
+						R_REMOTES));
 			}
 			resultRefs = new ArrayList<>(filterRefs(refs));
 		} catch (IOException e) {
@@ -185,8 +189,4 @@
 		this.containsCommitish = containsCommitish;
 		return this;
 	}
-
-	private Collection<Ref> getRefs(String prefix) throws IOException {
-		return repo.getRefDatabase().getRefsByPrefix(prefix);
-	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
index 1783c41..9653c36 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
@@ -43,6 +43,8 @@
  */
 package org.eclipse.jgit.api;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -1015,8 +1017,7 @@
 			df.setRepository(repo);
 			df.format(commitToPick.getParent(0), commitToPick);
 		}
-		rebaseState.createFile(PATCH, new String(bos.toByteArray(),
-				Constants.CHARACTER_ENCODING));
+		rebaseState.createFile(PATCH, new String(bos.toByteArray(), UTF_8));
 		rebaseState.createFile(STOPPED_SHA,
 				repo.newObjectReader()
 				.abbreviate(
@@ -1733,7 +1734,7 @@
 				throws IOException {
 			File file = new File(parentDir, name);
 			try (FileOutputStream fos = new FileOutputStream(file)) {
-				fos.write(content.getBytes(Constants.CHARACTER_ENCODING));
+				fos.write(content.getBytes(UTF_8));
 				fos.write('\n');
 			}
 		}
@@ -1741,7 +1742,7 @@
 		private static void appendToFile(File file, String content)
 				throws IOException {
 			try (FileOutputStream fos = new FileOutputStream(file, true)) {
-				fos.write(content.getBytes(Constants.CHARACTER_ENCODING));
+				fos.write(content.getBytes(UTF_8));
 				fos.write('\n');
 			}
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java
index 244a156..f92455a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java
@@ -179,21 +179,6 @@
 			// Use the path as the default.
 			name = path;
 		}
-		if (name.contains("/../") || name.contains("\\..\\") //$NON-NLS-1$ //$NON-NLS-2$
-				|| name.startsWith("../") || name.startsWith("..\\") //$NON-NLS-1$ //$NON-NLS-2$
-				|| name.endsWith("/..") || name.endsWith("\\..")) { //$NON-NLS-1$ //$NON-NLS-2$
-			// Submodule names are used to store the submodule repositories
-			// under $GIT_DIR/modules. Having ".." in submodule names makes a
-			// vulnerability (CVE-2018-11235
-			// https://bugs.eclipse.org/bugs/show_bug.cgi?id=535027#c0)
-			// Reject the names with them. The callers need to make sure the
-			// names free from these. We don't automatically replace these
-			// characters or canonicalize by regarding the name as a file path.
-			// Since Path class is platform dependent, we manually check '/' and
-			// '\\' patterns here.
-			throw new IllegalArgumentException(MessageFormat
-					.format(JGitText.get().invalidNameContainsDotDot, name));
-		}
 
 		try {
 			SubmoduleValidator.assertValidSubmoduleName(name);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportConfigCallback.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportConfigCallback.java
index f60926c..d73453c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportConfigCallback.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportConfigCallback.java
@@ -68,5 +68,5 @@
 	 * @param transport
 	 *            a {@link org.eclipse.jgit.transport.Transport} object.
 	 */
-	public void configure(Transport transport);
+	void configure(Transport transport);
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java
index f1d7d7b..2d1cde1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java
@@ -63,7 +63,7 @@
 	 * @throws java.io.IOException
 	 *             if an error is raised while parsing the attributes file
 	 */
-	public AttributesNode getInfoAttributesNode() throws IOException;
+	AttributesNode getInfoAttributesNode() throws IOException;
 
 	/**
 	 * Retrieve the {@link org.eclipse.jgit.attributes.AttributesNode} that
@@ -76,6 +76,6 @@
 	 *             attributes file
 	 * @see CoreConfig#getAttributesFile()
 	 */
-	public AttributesNode getGlobalAttributesNode() throws IOException;
+	AttributesNode getGlobalAttributesNode() throws IOException;
 
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java
index 1545e35..7b51f6d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java
@@ -53,5 +53,5 @@
 	 *
 	 * @return the currently active attributes
 	 */
-	public Attributes getAttributes();
+	Attributes getAttributes();
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommand.java
index c4357d1..0bb4516 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommand.java
@@ -95,7 +95,7 @@
 	 *         -1. -1 means that the {@link java.io.InputStream} is completely
 	 *         processed.
 	 * @throws java.io.IOException
-	 *             when {@link java.io.IOException} occured while reading from
+	 *             when {@link java.io.IOException} occurred while reading from
 	 *             {@link #in} or writing to {@link #out}
 	 */
 	public abstract int run() throws IOException;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommandFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommandFactory.java
index 11b76b0..78573d2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommandFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommandFactory.java
@@ -69,7 +69,7 @@
 	 *             thrown when the command constructor throws an
 	 *             java.io.IOException
 	 */
-	public FilterCommand create(Repository db, InputStream in,
-			OutputStream out) throws IOException;
+	FilterCommand create(Repository db, InputStream in, OutputStream out)
+			throws IOException;
 
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
index ca1e3ab..db6073f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
@@ -522,7 +522,7 @@
 			builder.finish();
 
 			// init progress reporting
-			int numTotal = removed.size() + updated.size();
+			int numTotal = removed.size() + updated.size() + conflicts.size();
 			monitor.beginTask(JGitText.get().checkingOutFiles, numTotal);
 
 			performingCheckout = true;
@@ -597,6 +597,33 @@
 				}
 				throw ex;
 			}
+			for (String conflict : conflicts) {
+				// the conflicts are likely to have multiple entries in the
+				// dircache, we only want to check out the one for the "theirs"
+				// tree
+				int entryIdx = dc.findEntry(conflict);
+				if (entryIdx >= 0) {
+					while (entryIdx < dc.getEntryCount()) {
+						DirCacheEntry entry = dc.getEntry(entryIdx);
+						if (!entry.getPathString().equals(conflict)) {
+							break;
+						}
+						if (entry.getStage() == DirCacheEntry.STAGE_3) {
+							checkoutEntry(repo, entry, objectReader, false,
+									null);
+							break;
+						}
+						++entryIdx;
+					}
+				}
+
+				monitor.update(1);
+				if (monitor.isCancelled()) {
+					throw new CanceledException(MessageFormat.format(
+							JGitText.get().operationCanceled,
+							JGitText.get().checkingOutFiles));
+				}
+			}
 			monitor.endTask();
 
 			// commit the index builder - a new index is persisted
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java
index 19c916f..6196e75 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java
@@ -44,6 +44,8 @@
 
 package org.eclipse.jgit.dircache;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Collections;
@@ -75,7 +77,7 @@
 public class DirCacheIterator extends AbstractTreeIterator {
 	/** Byte array holding ".gitattributes" string */
 	private static final byte[] DOT_GIT_ATTRIBUTES_BYTES = Constants.DOT_GIT_ATTRIBUTES
-			.getBytes();
+			.getBytes(UTF_8);
 
 	/** The cache this iterator was created to walk. */
 	protected final DirCache cache;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedEvent.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedEvent.java
index 9fbcc4d..afd7889 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedEvent.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedEvent.java
@@ -94,7 +94,8 @@
 	 *
 	 * @return the set
 	 */
-	public @NonNull Collection<String> getModified() {
+	@NonNull
+	public Collection<String> getModified() {
 		Collection<String> result = modified;
 		if (result == null) {
 			result = Collections.emptyList();
@@ -109,7 +110,8 @@
 	 *
 	 * @return the set
 	 */
-	public @NonNull Collection<String> getDeleted() {
+	@NonNull
+	public Collection<String> getDeleted() {
 		Collection<String> result = deleted;
 		if (result == null) {
 			result = Collections.emptyList();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/Head.java b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/Head.java
index 49839f8..e8f6844 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/Head.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/Head.java
@@ -54,5 +54,5 @@
 	 *            the character which decides which heads are returned.
 	 * @return a list of heads based on the input.
 	 */
-	public abstract List<Head> getNextHeads(char c);
+	List<Head> getNextHeads(char c);
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java
index 26e783d..8e46341 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java
@@ -358,7 +358,8 @@
 	 *
 	 * @return filtered projects list reference, never null
 	 */
-	public @NonNull List<RepoProject> getFilteredProjects() {
+	@NonNull
+	public List<RepoProject> getFilteredProjects() {
 		return filteredProjects;
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
index 45a239d..e9d86df 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
@@ -42,6 +42,7 @@
  */
 package org.eclipse.jgit.gitrepo;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.lib.Constants.DEFAULT_REMOTE_NAME;
 import static org.eclipse.jgit.lib.Constants.R_REMOTES;
 
@@ -58,12 +59,14 @@
 import java.util.StringJoiner;
 import java.util.TreeMap;
 
+import org.eclipse.jgit.annotations.NonNull;
 import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.GitCommand;
 import org.eclipse.jgit.api.SubmoduleAddCommand;
 import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
 import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.InvalidRefNameException;
 import org.eclipse.jgit.api.errors.JGitInternalException;
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.DirCacheBuilder;
@@ -79,7 +82,6 @@
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
@@ -89,6 +91,7 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.TreeWalk;
 import org.eclipse.jgit.util.FileUtils;
 
 /**
@@ -143,7 +146,9 @@
 		 * @param uri
 		 *            The URI of the remote repository
 		 * @param ref
-		 *            The ref (branch/tag/etc.) to read
+		 *            Name of the ref to lookup. May be a short-hand form, e.g.
+		 *            "master" which is is automatically expanded to
+		 *            "refs/heads/master" if "refs/heads/master" already exists.
 		 * @return the sha1 of the remote repository, or null if the ref does
 		 *         not exist.
 		 * @throws GitAPIException
@@ -164,13 +169,93 @@
 		 * @throws GitAPIException
 		 * @throws IOException
 		 * @since 3.5
+		 *
+		 * @deprecated Use {@link #readFileWithMode(String, String, String)}
+		 *             instead
 		 */
-		public byte[] readFile(String uri, String ref, String path)
+		@Deprecated
+		public default byte[] readFile(String uri, String ref, String path)
+				throws GitAPIException, IOException {
+			return readFileWithMode(uri, ref, path).getContents();
+		}
+
+		/**
+		 * Read contents and mode (i.e. permissions) of the file from a remote
+		 * repository.
+		 *
+		 * @param uri
+		 *            The URI of the remote repository
+		 * @param ref
+		 *            Name of the ref to lookup. May be a short-hand form, e.g.
+		 *            "master" which is is automatically expanded to
+		 *            "refs/heads/master" if "refs/heads/master" already exists.
+		 * @param path
+		 *            The relative path (inside the repo) to the file to read
+		 * @return The contents and file mode of the file in the given
+		 *         repository and branch. Never null.
+		 * @throws GitAPIException
+		 *             If the ref have an invalid or ambiguous name, or it does
+		 *             not exist in the repository,
+		 * @throws IOException
+		 *             If the object does not exist or is too large
+		 * @since 5.2
+		 */
+		@NonNull
+		public RemoteFile readFileWithMode(String uri, String ref, String path)
 				throws GitAPIException, IOException;
 	}
 
+	/**
+	 * Read-only view of contents and file mode (i.e. permissions) for a file in
+	 * a remote repository.
+	 *
+	 * @since 5.2
+	 */
+	public static final class RemoteFile {
+		@NonNull
+		private final byte[] contents;
+
+		@NonNull
+		private final FileMode fileMode;
+
+		/**
+		 * @param contents
+		 *            Raw contents of the file.
+		 * @param fileMode
+		 *            Git file mode for this file (e.g. executable or regular)
+		 */
+		public RemoteFile(@NonNull byte[] contents,
+				@NonNull FileMode fileMode) {
+			this.contents = Objects.requireNonNull(contents);
+			this.fileMode = Objects.requireNonNull(fileMode);
+		}
+
+		/**
+		 * Contents of the file.
+		 * <p>
+		 * Callers who receive this reference must not modify its contents (as
+		 * it can point to internal cached data).
+		 *
+		 * @return Raw contents of the file. Do not modify it.
+		 */
+		@NonNull
+		public byte[] getContents() {
+			return contents;
+		}
+
+		/**
+		 * @return Git file mode for this file (e.g. executable or regular)
+		 */
+		@NonNull
+		public FileMode getFileMode() {
+			return fileMode;
+		}
+
+	}
+
 	/** A default implementation of {@link RemoteReader} callback. */
 	public static class DefaultRemoteReader implements RemoteReader {
+
 		@Override
 		public ObjectId sha1(String uri, String ref) throws GitAPIException {
 			Map<String, Ref> map = Git
@@ -182,38 +267,30 @@
 		}
 
 		@Override
-		public byte[] readFile(String uri, String ref, String path)
+		public RemoteFile readFileWithMode(String uri, String ref, String path)
 				throws GitAPIException, IOException {
 			File dir = FileUtils.createTempDir("jgit_", ".git", null); //$NON-NLS-1$ //$NON-NLS-2$
 			try (Git git = Git.cloneRepository().setBare(true).setDirectory(dir)
 					.setURI(uri).call()) {
-				return readFileFromRepo(git.getRepository(), ref, path);
+				Repository repo = git.getRepository();
+				ObjectId refCommitId = sha1(uri, ref);
+				if (refCommitId == null) {
+					throw new InvalidRefNameException(MessageFormat
+							.format(JGitText.get().refNotResolved, ref));
+				}
+				RevCommit commit = repo.parseCommit(refCommitId);
+				TreeWalk tw = TreeWalk.forPath(repo, path, commit.getTree());
+
+				// TODO(ifrade): Cope better with big files (e.g. using
+				// InputStream instead of byte[])
+				return new RemoteFile(
+						tw.getObjectReader().open(tw.getObjectId(0))
+								.getCachedBytes(Integer.MAX_VALUE),
+						tw.getFileMode(0));
 			} finally {
 				FileUtils.delete(dir, FileUtils.RECURSIVE);
 			}
 		}
-
-		/**
-		 * Read a file from the repository
-		 *
-		 * @param repo
-		 *            The repository containing the file
-		 * @param ref
-		 *            The ref (branch/tag/etc.) to read
-		 * @param path
-		 *            The relative path (inside the repo) to the file to read
-		 * @return the file's content
-		 * @throws GitAPIException
-		 * @throws IOException
-		 * @since 3.5
-		 */
-		protected byte[] readFileFromRepo(Repository repo,
-				String ref, String path) throws GitAPIException, IOException {
-			try (ObjectReader reader = repo.newObjectReader()) {
-				ObjectId oid = repo.resolve(ref + ":" + path); //$NON-NLS-1$
-				return reader.open(oid).getBytes(Integer.MAX_VALUE);
-			}
-		}
 	}
 
 	@SuppressWarnings("serial")
@@ -586,12 +663,13 @@
 						builder.add(dcEntry);
 
 						for (CopyFile copyfile : proj.getCopyFiles()) {
-							byte[] src = callback.readFile(
+							RemoteFile rf = callback.readFileWithMode(
 								url, proj.getRevision(), copyfile.src);
-							objectId = inserter.insert(Constants.OBJ_BLOB, src);
+							objectId = inserter.insert(Constants.OBJ_BLOB,
+									rf.getContents());
 							dcEntry = new DirCacheEntry(copyfile.dest);
 							dcEntry.setObjectId(objectId);
-							dcEntry.setFileMode(FileMode.REGULAR_FILE);
+							dcEntry.setFileMode(rf.getFileMode());
 							builder.add(dcEntry);
 						}
 						for (LinkFile linkfile : proj.getLinkFiles()) {
@@ -606,8 +684,7 @@
 							}
 
 							objectId = inserter.insert(Constants.OBJ_BLOB,
-								link.getBytes(
-									Constants.CHARACTER_ENCODING));
+									link.getBytes(UTF_8));
 							dcEntry = new DirCacheEntry(linkfile.dest);
 							dcEntry.setObjectId(objectId);
 							dcEntry.setFileMode(FileMode.SYMLINK);
@@ -620,7 +697,7 @@
 				// create a new DirCacheEntry for .gitmodules file.
 				final DirCacheEntry dcEntry = new DirCacheEntry(Constants.DOT_GIT_MODULES);
 				ObjectId objectId = inserter.insert(Constants.OBJ_BLOB,
-						content.getBytes(Constants.CHARACTER_ENCODING));
+						content.getBytes(UTF_8));
 				dcEntry.setObjectId(objectId);
 				dcEntry.setFileMode(FileMode.REGULAR_FILE);
 				builder.add(dcEntry);
@@ -629,7 +706,7 @@
 					// create a new DirCacheEntry for .gitattributes file.
 					final DirCacheEntry dcEntryAttr = new DirCacheEntry(Constants.DOT_GIT_ATTRIBUTES);
 					ObjectId attrId = inserter.insert(Constants.OBJ_BLOB,
-							attributes.toString().getBytes(Constants.CHARACTER_ENCODING));
+							attributes.toString().getBytes(UTF_8));
 					dcEntryAttr.setObjectId(attrId);
 					dcEntryAttr.setFileMode(FileMode.REGULAR_FILE);
 					builder.add(dcEntryAttr);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java
index 7ba83c7..d79dfa8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java
@@ -136,6 +136,7 @@
 				FileChannel channel = input.getChannel();
 				output.getChannel().transferFrom(channel, 0, channel.size());
 			}
+			destFile.setExecutable(srcFile.canExecute());
 		}
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java
index 051a1d1..431944f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java
@@ -164,12 +164,7 @@
 	 */
 	public void setRefs(Collection<RemoteRefUpdate> toRefs) {
 		StringBuilder b = new StringBuilder();
-		boolean first = true;
 		for (RemoteRefUpdate u : toRefs) {
-			if (!first)
-				b.append("\n"); //$NON-NLS-1$
-			else
-				first = false;
 			b.append(u.getSrcRef());
 			b.append(" "); //$NON-NLS-1$
 			b.append(u.getNewObjectId().getName());
@@ -179,6 +174,7 @@
 			ObjectId ooid = u.getExpectedOldObjectId();
 			b.append((ooid == null) ? ObjectId.zeroId().getName() : ooid
 					.getName());
+			b.append("\n"); //$NON-NLS-1$
 		}
 		refs = b.toString();
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java
index 9b255b4..41923ee 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java
@@ -443,7 +443,7 @@
 		if (in_brackets > 0)
 			throw new InvalidPatternException("Not closed bracket?", pattern); //$NON-NLS-1$
 		try {
-			return Pattern.compile(sb.toString());
+			return Pattern.compile(sb.toString(), Pattern.DOTALL);
 		} catch (PatternSyntaxException e) {
 			throw new InvalidPatternException(
 					MessageFormat.format(JGitText.get().invalidIgnoreRule,
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 da0ba4a..8f884b4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -529,6 +529,7 @@
 	/***/ public String newlineInQuotesNotAllowed;
 	/***/ public String noApplyInDelete;
 	/***/ public String noClosingBracket;
+	/***/ public String noCommitsSelectedForShallow;
 	/***/ public String noCredentialsProvider;
 	/***/ public String noHEADExistsAndNoExplicitStartingRevisionWasSpecified;
 	/***/ public String noHMACsupport;
@@ -729,10 +730,8 @@
 	/***/ public String submoduleNameInvalid;
 	/***/ public String submoduleParentRemoteUrlInvalid;
 	/***/ public String submodulePathInvalid;
-	/***/ public String submodulesNotSupported;
 	/***/ public String submoduleUrlInvalid;
 	/***/ public String supportOnlyPackIndexVersion2;
-	/***/ public String symlinkCannotBeWrittenAsTheLinkTarget;
 	/***/ public String systemConfigFileInvalid;
 	/***/ public String tagAlreadyExists;
 	/***/ public String tagNameInvalid;
@@ -832,7 +831,9 @@
 	/***/ public String uriNotFoundWithMessage;
 	/***/ public String URINotSupported;
 	/***/ public String userConfigFileInvalid;
+	/***/ public String validatingGitModules;
 	/***/ public String walkFailure;
+	/***/ public String wantNoSpaceWithCapabilities;
 	/***/ public String wantNotValid;
 	/***/ public String weeksAgo;
 	/***/ public String windowSizeMustBeLesserThanLimit;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckError.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckError.java
index 131b004..335ac66 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckError.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckError.java
@@ -61,20 +61,21 @@
 
 		final int type;
 
-		ObjectChecker.ErrorType errorType;
+		@Nullable
+		final ObjectChecker.ErrorType errorType;
 
 		/**
 		 * @param id
 		 *            the object identifier.
 		 * @param type
 		 *            type of the object.
+		 * @param errorType
+		 *            kind of error
 		 */
-		public CorruptObject(ObjectId id, int type) {
+		public CorruptObject(ObjectId id, int type,
+				@Nullable ObjectChecker.ErrorType errorType) {
 			this.id = id;
 			this.type = type;
-		}
-
-		void setErrorType(ObjectChecker.ErrorType errorType) {
 			this.errorType = errorType;
 		}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckPackParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckPackParser.java
index 5397ba4..50594df 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckPackParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckPackParser.java
@@ -174,12 +174,8 @@
 		try {
 			super.verifySafeObject(id, type, data);
 		} catch (CorruptObjectException e) {
-			// catch the exception and continue parse the pack file
-			CorruptObject o = new CorruptObject(id.toObjectId(), type);
-			if (e.getErrorType() != null) {
-				o.setErrorType(e.getErrorType());
-			}
-			corruptObjects.add(o);
+			corruptObjects.add(
+					new CorruptObject(id.toObjectId(), type, e.getErrorType()));
 		}
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsFsck.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsFsck.java
index 3f96d09..c0e24c0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsFsck.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsFsck.java
@@ -43,6 +43,7 @@
 
 package org.eclipse.jgit.internal.storage.dfs;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
 import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
 
@@ -54,12 +55,18 @@
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.internal.fsck.FsckError;
 import org.eclipse.jgit.internal.fsck.FsckError.CorruptIndex;
+import org.eclipse.jgit.internal.fsck.FsckError.CorruptObject;
 import org.eclipse.jgit.internal.fsck.FsckPackParser;
 import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
+import org.eclipse.jgit.internal.submodule.SubmoduleValidator;
+import org.eclipse.jgit.internal.submodule.SubmoduleValidator.SubmoduleValidationException;
+import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.GitmoduleEntry;
 import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ObjectChecker;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectLoader;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.revwalk.ObjectWalk;
@@ -102,6 +109,7 @@
 
 		FsckError errors = new FsckError();
 		if (!connectivityOnly) {
+			objChecker.reset();
 			checkPacks(pm, errors);
 		}
 		checkConnectivity(pm, errors);
@@ -128,6 +136,8 @@
 				}
 			}
 		}
+
+		checkGitModules(pm, errors);
 	}
 
 	private void verifyPack(ProgressMonitor pm, FsckError errors, DfsReader ctx,
@@ -142,6 +152,28 @@
 		fpp.verifyIndex(pack.getPackIndex(ctx));
 	}
 
+	private void checkGitModules(ProgressMonitor pm, FsckError errors)
+			throws IOException {
+		pm.beginTask(JGitText.get().validatingGitModules,
+				objChecker.getGitsubmodules().size());
+		for (GitmoduleEntry entry : objChecker.getGitsubmodules()) {
+			AnyObjectId blobId = entry.getBlobId();
+			ObjectLoader blob = objdb.open(blobId, Constants.OBJ_BLOB);
+
+			try {
+				SubmoduleValidator.assertValidGitModulesFile(
+						new String(blob.getBytes(), UTF_8));
+			} catch (SubmoduleValidationException e) {
+				CorruptObject co = new FsckError.CorruptObject(
+						blobId.toObjectId(), Constants.OBJ_BLOB,
+						e.getFsckMessageId());
+				errors.getCorruptObjects().add(co);
+			}
+			pm.update(1);
+		}
+		pm.endTask();
+	}
+
 	private void checkConnectivity(ProgressMonitor pm, FsckError errors)
 			throws IOException {
 		pm.beginTask(JGitText.get().countingObjects, ProgressMonitor.UNKNOWN);
@@ -179,6 +211,9 @@
 	 * Use a customized object checker instead of the default one. Caller can
 	 * specify a skip list to ignore some errors.
 	 *
+	 * It will be reset at the start of each {{@link #check(ProgressMonitor)}
+	 * call.
+	 *
 	 * @param objChecker
 	 *            A customized object checker.
 	 */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReadableChannel.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReadableChannel.java
index 9b98250..d5e1722 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReadableChannel.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReadableChannel.java
@@ -57,7 +57,7 @@
 	 * @throws java.io.IOException
 	 *             the channel's current position cannot be obtained.
 	 */
-	public long position() throws IOException;
+	long position() throws IOException;
 
 	/**
 	 * Seek the current position of the channel to a new offset.
@@ -70,7 +70,7 @@
 	 *             channel only supports block aligned IO and the current
 	 *             position is not block aligned.
 	 */
-	public void position(long newPosition) throws IOException;
+	void position(long newPosition) throws IOException;
 
 	/**
 	 * Get the total size of the channel.
@@ -83,7 +83,7 @@
 	 * @throws java.io.IOException
 	 *             the size cannot be determined.
 	 */
-	public long size() throws IOException;
+	long size() throws IOException;
 
 	/**
 	 * Get the recommended alignment for reads.
@@ -102,7 +102,7 @@
 	 * @return recommended alignment size for randomly positioned reads. Does
 	 *         not need to be a power of 2.
 	 */
-	public int blockSize();
+	int blockSize();
 
 	/**
 	 * Recommend the channel maintain a read-ahead buffer.
@@ -131,5 +131,5 @@
 	 * @throws java.io.IOException
 	 *             if the read ahead cannot be adjusted.
 	 */
-	public void setReadAheadBytes(int bufferSize) throws IOException;
+	void setReadAheadBytes(int bufferSize) throws IOException;
 }
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 e35b9c9..3552266 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
@@ -43,6 +43,7 @@
 
 package org.eclipse.jgit.internal.storage.file;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
 import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
 
@@ -50,7 +51,6 @@
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
-import java.io.FileReader;
 import java.io.IOException;
 import java.nio.file.AtomicMoveNotSupportedException;
 import java.nio.file.Files;
@@ -1036,8 +1036,8 @@
 	}
 
 	private static BufferedReader open(File f)
-			throws FileNotFoundException {
-		return new BufferedReader(new FileReader(f));
+			throws IOException, FileNotFoundException {
+		return Files.newBufferedReader(f.toPath(), UTF_8);
 	}
 
 	private AlternateHandle openAlternate(String location)
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 7069588..964cc4d 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
@@ -207,7 +207,7 @@
 	}
 
 	/** An entry in the old PackBitmapIndex. */
-	public final class Entry extends ObjectId {
+	public static final class Entry extends ObjectId {
 		private final int flags;
 
 		Entry(AnyObjectId src, int flags) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectReuseAsIs.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectReuseAsIs.java
index f759e23..2c80623 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectReuseAsIs.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectReuseAsIs.java
@@ -79,7 +79,7 @@
 	 *            the Git type of the object that will be packed.
 	 * @return a new instance for this object.
 	 */
-	public ObjectToPack newObjectToPack(AnyObjectId objectId, int type);
+	ObjectToPack newObjectToPack(AnyObjectId objectId, int type);
 
 	/**
 	 * Select the best object representation for a packer.
@@ -114,7 +114,7 @@
 	 * @throws java.io.IOException
 	 *             the repository cannot be accessed. Packing will abort.
 	 */
-	public void selectObjectRepresentation(PackWriter packer,
+	void selectObjectRepresentation(PackWriter packer,
 			ProgressMonitor monitor, Iterable<ObjectToPack> objects)
 			throws IOException, MissingObjectException;
 
@@ -155,7 +155,7 @@
 	 *             the stream cannot be written to, or one or more required
 	 *             objects cannot be accessed from the object database.
 	 */
-	public void writeObjects(PackOutputStream out, List<ObjectToPack> list)
+	void writeObjects(PackOutputStream out, List<ObjectToPack> list)
 			throws IOException;
 
 	/**
@@ -200,7 +200,7 @@
 	 *             the stream's write method threw an exception. Packing will
 	 *             abort.
 	 */
-	public void copyObjectAsIs(PackOutputStream out, ObjectToPack otp,
+	void copyObjectAsIs(PackOutputStream out, ObjectToPack otp,
 			boolean validate) throws IOException,
 			StoredObjectRepresentationNotAvailableException;
 
@@ -216,7 +216,7 @@
 	 * @throws java.io.IOException
 	 *             the pack cannot be read, or stream did not accept a write.
 	 */
-	public abstract void copyPackAsIs(PackOutputStream out, CachedPack pack)
+	void copyPackAsIs(PackOutputStream out, CachedPack pack)
 			throws IOException;
 
 	/**
@@ -234,6 +234,6 @@
 	 *             Callers may choose to ignore this and continue as-if there
 	 *             were no cached packs.
 	 */
-	public Collection<CachedPack> getCachedPacksAndUpdate(
+	Collection<CachedPack> getCachedPacksAndUpdate(
 			BitmapBuilder needBitmap) throws IOException;
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackOutputStream.java
index 7f38a7b..eb777be 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackOutputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackOutputStream.java
@@ -187,6 +187,7 @@
 	 * @throws java.io.IOException
 	 *             the underlying stream refused to accept the header.
 	 */
+	@SuppressWarnings("ShortCircuitBoolean")
 	public final void writeHeader(ObjectToPack otp, long rawLength)
 			throws IOException {
 		ObjectToPack b = otp.getDeltaBase();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/submodule/SubmoduleValidator.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/submodule/SubmoduleValidator.java
index 3651631..7b872b1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/submodule/SubmoduleValidator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/submodule/SubmoduleValidator.java
@@ -45,13 +45,17 @@
 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PATH;
 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_URL;
 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_SUBMODULE_SECTION;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.GITMODULES_NAME;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.GITMODULES_PARSE;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.GITMODULES_PATH;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.GITMODULES_URL;
 
-import java.io.IOException;
 import java.text.MessageFormat;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectChecker;
 
 /**
  * Validations for the git submodule fields (name, path, uri).
@@ -66,15 +70,30 @@
 	 */
 	public static class SubmoduleValidationException extends Exception {
 
+		private static final long serialVersionUID = 1L;
+
+		private final ObjectChecker.ErrorType fsckMessageId;
+
 		/**
 		 * @param message
 		 *            Description of the problem
+		 * @param fsckMessageId
+		 *            Error identifier, following the git fsck fsck.<msg-id>
+		 *            format
 		 */
-		public SubmoduleValidationException(String message) {
+		SubmoduleValidationException(String message,
+				ObjectChecker.ErrorType fsckMessageId) {
 			super(message);
+			this.fsckMessageId = fsckMessageId;
 		}
 
-		private static final long serialVersionUID = 1L;
+
+		/**
+		 * @return the error identifier
+		 */
+		public ObjectChecker.ErrorType getFsckMessageId() {
+			return fsckMessageId;
+		}
 	}
 
 	/**
@@ -100,13 +119,15 @@
 			// Since Path class is platform dependent, we manually check '/' and
 			// '\\' patterns here.
 			throw new SubmoduleValidationException(MessageFormat
-					.format(JGitText.get().invalidNameContainsDotDot, name));
+					.format(JGitText.get().invalidNameContainsDotDot, name),
+					GITMODULES_NAME);
 		}
 
 		if (name.startsWith("-")) { //$NON-NLS-1$
 			throw new SubmoduleValidationException(
 					MessageFormat.format(
-							JGitText.get().submoduleNameInvalid, name));
+							JGitText.get().submoduleNameInvalid, name),
+					GITMODULES_NAME);
 		}
 	}
 
@@ -123,7 +144,8 @@
 		if (uri.startsWith("-")) { //$NON-NLS-1$
 			throw new SubmoduleValidationException(
 					MessageFormat.format(
-							JGitText.get().submoduleUrlInvalid, uri));
+							JGitText.get().submoduleUrlInvalid, uri),
+					GITMODULES_URL);
 		}
 	}
 
@@ -140,19 +162,22 @@
 		if (path.startsWith("-")) { //$NON-NLS-1$
 			throw new SubmoduleValidationException(
 					MessageFormat.format(
-							JGitText.get().submodulePathInvalid, path));
+							JGitText.get().submodulePathInvalid, path),
+					GITMODULES_PATH);
 		}
 	}
 
 	/**
+	 * Validate a .gitmodules file
+	 *
 	 * @param gitModulesContents
 	 *            Contents of a .gitmodule file. They will be parsed internally.
-	 * @throws IOException
-	 *             If the contents
+	 * @throws SubmoduleValidationException
+	 *             if the contents don't look like a configuration file or field
+	 *             values are not valid
 	 */
 	public static void assertValidGitModulesFile(String gitModulesContents)
-			throws IOException {
-		// Validate .gitmodules file
+			throws SubmoduleValidationException {
 		Config c = new Config();
 		try {
 			c.fromText(gitModulesContents);
@@ -173,12 +198,9 @@
 				}
 			}
 		} catch (ConfigInvalidException e) {
-			throw new IOException(
-					MessageFormat.format(
-							JGitText.get().invalidGitModules,
-							e));
-		} catch (SubmoduleValidationException e) {
-			throw new IOException(e.getMessage(), e);
+			throw new SubmoduleValidationException(
+					JGitText.get().invalidGitModules,
+					GITMODULES_PARSE);
 		}
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/parser/FirstWant.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/parser/FirstWant.java
new file mode 100644
index 0000000..2dae021
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/parser/FirstWant.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2018, Google LLC.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.internal.transport.parser;
+
+import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.errors.PackProtocolException;
+import org.eclipse.jgit.internal.JGitText;
+
+/**
+ * In the pack negotiation phase (protocol v0/v1), the client sends a list of
+ * wants. The first "want" line is special, as it (can) have a list of
+ * capabilities appended.
+ *
+ * E.g. "want oid cap1 cap2 cap3"
+ *
+ * Do not confuse this line with the first one in the reference advertisement,
+ * which is sent by the server, looks like
+ * "b8f7c471373b8583ced0025cfad8c9916c484b76 HEAD\0 cap1 cap2 cap3" and is
+ * parsed by the BasePackConnection.readAdvertisedRefs method.
+ *
+ * This class parses the input want line and holds the results: the actual want
+ * line and the capabilities.
+ *
+ * @since 5.2
+ */
+public class FirstWant {
+	private final String line;
+
+	private final Set<String> capabilities;
+
+	@Nullable
+	private final String agent;
+
+	private static final String AGENT_PREFIX = OPTION_AGENT + '=';
+
+	/**
+	 * Parse the first want line in the protocol v0/v1 pack negotiation.
+	 *
+	 * @param line
+	 *            line from the client.
+	 * @return an instance of FirstWant
+	 * @throws PackProtocolException
+	 *             if the line doesn't follow the protocol format.
+	 */
+	public static FirstWant fromLine(String line) throws PackProtocolException {
+		String wantLine;
+		Set<String> capabilities;
+		String agent = null;
+
+		if (line.length() > 45) {
+			String opt = line.substring(45);
+			if (!opt.startsWith(" ")) { //$NON-NLS-1$
+				throw new PackProtocolException(JGitText.get().wantNoSpaceWithCapabilities);
+			}
+			opt = opt.substring(1);
+
+			HashSet<String> opts = new HashSet<>();
+			for (String clientCapability : opt.split(" ")) { //$NON-NLS-1$
+				if (clientCapability.startsWith(AGENT_PREFIX)) {
+					agent = clientCapability.substring(AGENT_PREFIX.length());
+				} else {
+					opts.add(clientCapability);
+				}
+			}
+			wantLine = line.substring(0, 45);
+			capabilities = Collections.unmodifiableSet(opts);
+		} else {
+			wantLine = line;
+			capabilities = Collections.emptySet();
+		}
+
+		return new FirstWant(wantLine, capabilities, agent);
+	}
+
+	private FirstWant(String line, Set<String> capabilities,
+			@Nullable String agent) {
+		this.line = line;
+		this.capabilities = capabilities;
+		this.agent = agent;
+	}
+
+	/** @return non-capabilities part of the line. */
+	public String getLine() {
+		return line;
+	}
+
+	/**
+	 * @return capabilities parsed from the line as an immutable set (excluding
+	 *         agent).
+	 */
+	public Set<String> getCapabilities() {
+		return capabilities;
+	}
+
+	/** @return client user agent parsed from the line. */
+	@Nullable
+	public String getAgent() {
+		return agent;
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java
new file mode 100644
index 0000000..e8a6ba7
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java
@@ -0,0 +1,922 @@
+/*
+ * Copyright (C) 2008, 2017, Google Inc.
+ * Copyright (C) 2017, 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+
+package org.eclipse.jgit.internal.transport.ssh;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.errors.InvalidPatternException;
+import org.eclipse.jgit.fnmatch.FileNameMatcher;
+import org.eclipse.jgit.transport.SshConstants;
+import org.eclipse.jgit.util.StringUtils;
+import org.eclipse.jgit.util.SystemReader;
+
+/**
+ * Fairly complete configuration parser for the openssh ~/.ssh/config file.
+ * <p>
+ * Both JSch 0.1.54 and Apache MINA sshd 2.1.0 have parsers for this, but both
+ * are buggy. Therefore we implement our own parser to read an openssh
+ * configuration file.
+ * </p>
+ * <p>
+ * Limitations compared to the full openssh 7.5 parser:
+ * </p>
+ * <ul>
+ * <li>This parser does not handle Match or Include keywords.
+ * <li>This parser does not do host name canonicalization.
+ * </ul>
+ * <p>
+ * Note that openssh's readconf.c is a validating parser; this parser does not
+ * validate entries.
+ * </p>
+ * <p>
+ * This config does %-substitutions for the following tokens:
+ * </p>
+ * <ul>
+ * <li>%% - single %
+ * <li>%C - short-hand for %l%h%p%r.
+ * <li>%d - home directory path
+ * <li>%h - remote host name
+ * <li>%L - local host name without domain
+ * <li>%l - FQDN of the local host
+ * <li>%n - host name as specified in {@link #lookup(String, int, String)}
+ * <li>%p - port number; if not given in {@link #lookup(String, int, String)}
+ * replaced only if set in the config
+ * <li>%r - remote user name; if not given in
+ * {@link #lookup(String, int, String)} replaced only if set in the config
+ * <li>%u - local user name
+ * </ul>
+ * <p>
+ * %i is not handled; Java has no concept of a "user ID". %T is always replaced
+ * by NONE.
+ * </p>
+ *
+ * @see <a href="http://man.openbsd.org/OpenBSD-current/man5/ssh_config.5">man
+ *      ssh-config</a>
+ */
+public class OpenSshConfigFile {
+
+	/**
+	 * "Host" name of the HostEntry for the default options before the first
+	 * host block in a config file.
+	 */
+	private static final String DEFAULT_NAME = ""; //$NON-NLS-1$
+
+	/** The user's home directory, as key files may be relative to here. */
+	private final File home;
+
+	/** The .ssh/config file we read and monitor for updates. */
+	private final File configFile;
+
+	/** User name of the user on the host OS. */
+	private final String localUserName;
+
+	/** Modification time of {@link #configFile} when it was last loaded. */
+	private long lastModified;
+
+	/**
+	 * Encapsulates entries read out of the configuration file, and a cache of
+	 * fully resolved entries created from that.
+	 */
+	private static class State {
+		// Keyed by pattern; if a "Host" line has multiple patterns, we generate
+		// duplicate HostEntry objects
+		Map<String, HostEntry> entries = new LinkedHashMap<>();
+
+		// Keyed by user@hostname:port
+		Map<String, HostEntry> hosts = new HashMap<>();
+
+		@Override
+		@SuppressWarnings("nls")
+		public String toString() {
+			return "State [entries=" + entries + ", hosts=" + hosts + "]";
+		}
+	}
+
+	/** State read from the config file, plus the cache. */
+	private State state;
+
+	/**
+	 * Creates a new {@link OpenSshConfigFile} that will read the config from
+	 * file {@code config} use the given file {@code home} as "home" directory.
+	 *
+	 * @param home
+	 *            user's home directory for the purpose of ~ replacement
+	 * @param config
+	 *            file to load.
+	 * @param localUserName
+	 *            user name of the current user on the local host OS
+	 */
+	public OpenSshConfigFile(@NonNull File home, @NonNull File config,
+			@NonNull String localUserName) {
+		this.home = home;
+		this.configFile = config;
+		this.localUserName = localUserName;
+		state = new State();
+	}
+
+	/**
+	 * Locate the configuration for a specific host request.
+	 *
+	 * @param hostName
+	 *            the name the user has supplied to the SSH tool. This may be a
+	 *            real host name, or it may just be a "Host" block in the
+	 *            configuration file.
+	 * @param port
+	 *            the user supplied; <= 0 if none
+	 * @param userName
+	 *            the user supplied, may be {@code null} or empty if none given
+	 * @return r configuration for the requested name.
+	 */
+	@NonNull
+	public HostEntry lookup(@NonNull String hostName, int port,
+			String userName) {
+		final State cache = refresh();
+		String cacheKey = toCacheKey(hostName, port, userName);
+		HostEntry h = cache.hosts.get(cacheKey);
+		if (h != null) {
+			return h;
+		}
+		HostEntry fullConfig = new HostEntry();
+		// Initialize with default entries at the top of the file, before the
+		// first Host block.
+		fullConfig.merge(cache.entries.get(DEFAULT_NAME));
+		for (Map.Entry<String, HostEntry> e : cache.entries.entrySet()) {
+			String pattern = e.getKey();
+			if (isHostMatch(pattern, hostName)) {
+				fullConfig.merge(e.getValue());
+			}
+		}
+		fullConfig.substitute(hostName, port, userName, localUserName, home);
+		cache.hosts.put(cacheKey, fullConfig);
+		return fullConfig;
+	}
+
+	@NonNull
+	private String toCacheKey(@NonNull String hostName, int port,
+			String userName) {
+		String key = hostName;
+		if (port > 0) {
+			key = key + ':' + Integer.toString(port);
+		}
+		if (userName != null && !userName.isEmpty()) {
+			key = userName + '@' + key;
+		}
+		return key;
+	}
+
+	private synchronized State refresh() {
+		final long mtime = configFile.lastModified();
+		if (mtime != lastModified) {
+			State newState = new State();
+			try (BufferedReader br = Files
+					.newBufferedReader(configFile.toPath(), UTF_8)) {
+				newState.entries = parse(br);
+			} catch (IOException | RuntimeException none) {
+				// Ignore -- we'll set and return an empty state
+			}
+			lastModified = mtime;
+			state = newState;
+		}
+		return state;
+	}
+
+	private Map<String, HostEntry> parse(BufferedReader reader)
+			throws IOException {
+		final Map<String, HostEntry> entries = new LinkedHashMap<>();
+		final List<HostEntry> current = new ArrayList<>(4);
+		String line;
+
+		// The man page doesn't say so, but the openssh parser (readconf.c)
+		// starts out in active mode and thus always applies any lines that
+		// occur before the first host block. We gather those options in a
+		// HostEntry for DEFAULT_NAME.
+		HostEntry defaults = new HostEntry();
+		current.add(defaults);
+		entries.put(DEFAULT_NAME, defaults);
+
+		while ((line = reader.readLine()) != null) {
+			line = line.trim();
+			if (line.isEmpty() || line.startsWith("#")) { //$NON-NLS-1$
+				continue;
+			}
+			String[] parts = line.split("[ \t]*[= \t]", 2); //$NON-NLS-1$
+			// Although the ssh-config man page doesn't say so, the openssh
+			// parser does allow quoted keywords.
+			String keyword = dequote(parts[0].trim());
+			// man 5 ssh-config says lines had the format "keyword arguments",
+			// with no indication that arguments were optional. However, let's
+			// not crap out on missing arguments. See bug 444319.
+			String argValue = parts.length > 1 ? parts[1].trim() : ""; //$NON-NLS-1$
+
+			if (StringUtils.equalsIgnoreCase(SshConstants.HOST, keyword)) {
+				current.clear();
+				for (String name : parseList(argValue)) {
+					if (name == null || name.isEmpty()) {
+						// null should not occur, but better be safe than sorry.
+						continue;
+					}
+					HostEntry c = entries.get(name);
+					if (c == null) {
+						c = new HostEntry();
+						entries.put(name, c);
+					}
+					current.add(c);
+				}
+				continue;
+			}
+
+			if (current.isEmpty()) {
+				// We received an option outside of a Host block. We
+				// don't know who this should match against, so skip.
+				continue;
+			}
+
+			if (HostEntry.isListKey(keyword)) {
+				List<String> args = validate(keyword, parseList(argValue));
+				for (HostEntry entry : current) {
+					entry.setValue(keyword, args);
+				}
+			} else if (!argValue.isEmpty()) {
+				argValue = validate(keyword, dequote(argValue));
+				for (HostEntry entry : current) {
+					entry.setValue(keyword, argValue);
+				}
+			}
+		}
+
+		return entries;
+	}
+
+	/**
+	 * Splits the argument into a list of whitespace-separated elements.
+	 * Elements containing whitespace must be quoted and will be de-quoted.
+	 *
+	 * @param argument
+	 *            argument part of the configuration line as read from the
+	 *            config file
+	 * @return a {@link List} of elements, possibly empty and possibly
+	 *         containing empty elements, but not containing {@code null}
+	 */
+	private List<String> parseList(String argument) {
+		List<String> result = new ArrayList<>(4);
+		int start = 0;
+		int length = argument.length();
+		while (start < length) {
+			// Skip whitespace
+			if (Character.isSpaceChar(argument.charAt(start))) {
+				start++;
+				continue;
+			}
+			if (argument.charAt(start) == '"') {
+				int stop = argument.indexOf('"', ++start);
+				if (stop < start) {
+					// No closing double quote: skip
+					break;
+				}
+				result.add(argument.substring(start, stop));
+				start = stop + 1;
+			} else {
+				int stop = start + 1;
+				while (stop < length
+						&& !Character.isSpaceChar(argument.charAt(stop))) {
+					stop++;
+				}
+				result.add(argument.substring(start, stop));
+				start = stop + 1;
+			}
+		}
+		return result;
+	}
+
+	/**
+	 * Hook to perform validation on a single value, or to sanitize it. If this
+	 * throws an (unchecked) exception, parsing of the file is abandoned.
+	 *
+	 * @param key
+	 *            of the entry
+	 * @param value
+	 *            as read from the config file
+	 * @return the validated and possibly sanitized value
+	 */
+	protected String validate(String key, String value) {
+		if (String.CASE_INSENSITIVE_ORDER.compare(key,
+				SshConstants.PREFERRED_AUTHENTICATIONS) == 0) {
+			return stripWhitespace(value);
+		}
+		return value;
+	}
+
+	/**
+	 * Hook to perform validation on values, or to sanitize them. If this throws
+	 * an (unchecked) exception, parsing of the file is abandoned.
+	 *
+	 * @param key
+	 *            of the entry
+	 * @param value
+	 *            list of arguments as read from the config file
+	 * @return a {@link List} of values, possibly empty and possibly containing
+	 *         empty elements, but not containing {@code null}
+	 */
+	protected List<String> validate(String key, List<String> value) {
+		return value;
+	}
+
+	private static boolean isHostMatch(String pattern, String name) {
+		if (pattern.startsWith("!")) { //$NON-NLS-1$
+			return !patternMatchesHost(pattern.substring(1), name);
+		} else {
+			return patternMatchesHost(pattern, name);
+		}
+	}
+
+	private static boolean patternMatchesHost(String pattern, String name) {
+		if (pattern.indexOf('*') >= 0 || pattern.indexOf('?') >= 0) {
+			final FileNameMatcher fn;
+			try {
+				fn = new FileNameMatcher(pattern, null);
+			} catch (InvalidPatternException e) {
+				return false;
+			}
+			fn.append(name);
+			return fn.isMatch();
+		} else {
+			// Not a pattern but a full host name
+			return pattern.equals(name);
+		}
+	}
+
+	private static String dequote(String value) {
+		if (value.startsWith("\"") && value.endsWith("\"") //$NON-NLS-1$ //$NON-NLS-2$
+				&& value.length() > 1)
+			return value.substring(1, value.length() - 1);
+		return value;
+	}
+
+	private static String stripWhitespace(String value) {
+		final StringBuilder b = new StringBuilder();
+		for (int i = 0; i < value.length(); i++) {
+			if (!Character.isSpaceChar(value.charAt(i)))
+				b.append(value.charAt(i));
+		}
+		return b.toString();
+	}
+
+	private static File toFile(String path, File home) {
+		if (path.startsWith("~/") || path.startsWith("~" + File.separator)) { //$NON-NLS-1$ //$NON-NLS-2$
+			return new File(home, path.substring(2));
+		}
+		File ret = new File(path);
+		if (ret.isAbsolute()) {
+			return ret;
+		}
+		return new File(home, path);
+	}
+
+	/**
+	 * Converts a positive value into an {@code int}.
+	 *
+	 * @param value
+	 *            to convert
+	 * @return the value, or -1 if it wasn't a positive integral value
+	 */
+	public static int positive(String value) {
+		if (value != null) {
+			try {
+				return Integer.parseUnsignedInt(value);
+			} catch (NumberFormatException e) {
+				// Ignore
+			}
+		}
+		return -1;
+	}
+
+	/**
+	 * Converts a ssh config flag value (yes/true/on - no/false/off) into an
+	 * {@code boolean}.
+	 *
+	 * @param value
+	 *            to convert
+	 * @return {@code true} if {@code value} is "yes", "on", or "true";
+	 *         {@code false} otherwise
+	 */
+	public static boolean flag(String value) {
+		if (value == null) {
+			return false;
+		}
+		return SshConstants.YES.equals(value) || SshConstants.ON.equals(value)
+				|| SshConstants.TRUE.equals(value);
+	}
+
+	/**
+	 * Retrieves the local user name as given in the constructor.
+	 *
+	 * @return the user name
+	 */
+	public String getLocalUserName() {
+		return localUserName;
+	}
+
+	/**
+	 * A host entry from the ssh config file. Any merging of global values and
+	 * of several matching host entries, %-substitutions, and ~ replacement have
+	 * all been done.
+	 */
+	public static class HostEntry {
+
+		/**
+		 * Keys that can be specified multiple times, building up a list. (I.e.,
+		 * those are the keys that do not follow the general rule of "first
+		 * occurrence wins".)
+		 */
+		private static final Set<String> MULTI_KEYS = new TreeSet<>(
+				String.CASE_INSENSITIVE_ORDER);
+
+		static {
+			MULTI_KEYS.add(SshConstants.CERTIFICATE_FILE);
+			MULTI_KEYS.add(SshConstants.IDENTITY_FILE);
+			MULTI_KEYS.add(SshConstants.LOCAL_FORWARD);
+			MULTI_KEYS.add(SshConstants.REMOTE_FORWARD);
+			MULTI_KEYS.add(SshConstants.SEND_ENV);
+		}
+
+		/**
+		 * Keys that take a whitespace-separated list of elements as argument.
+		 * Because the dequote-handling is different, we must handle those in
+		 * the parser. There are a few other keys that take comma-separated
+		 * lists as arguments, but for the parser those are single arguments
+		 * that must be quoted if they contain whitespace, and taking them apart
+		 * is the responsibility of the user of those keys.
+		 */
+		private static final Set<String> LIST_KEYS = new TreeSet<>(
+				String.CASE_INSENSITIVE_ORDER);
+
+		static {
+			LIST_KEYS.add(SshConstants.CANONICAL_DOMAINS);
+			LIST_KEYS.add(SshConstants.GLOBAL_KNOWN_HOSTS_FILE);
+			LIST_KEYS.add(SshConstants.SEND_ENV);
+			LIST_KEYS.add(SshConstants.USER_KNOWN_HOSTS_FILE);
+		}
+
+		private Map<String, String> options;
+
+		private Map<String, List<String>> multiOptions;
+
+		private Map<String, List<String>> listOptions;
+
+		/**
+		 * Retrieves the value of a single-valued key, or the first is the key
+		 * has multiple values. Keys are case-insensitive, so
+		 * {@code getValue("HostName") == getValue("HOSTNAME")}.
+		 *
+		 * @param key
+		 *            to get the value of
+		 * @return the value, or {@code null} if none
+		 */
+		public String getValue(String key) {
+			String result = options != null ? options.get(key) : null;
+			if (result == null) {
+				// Let's be lenient and return at least the first value from
+				// a list-valued or multi-valued key.
+				List<String> values = listOptions != null ? listOptions.get(key)
+						: null;
+				if (values == null) {
+					values = multiOptions != null ? multiOptions.get(key)
+							: null;
+				}
+				if (values != null && !values.isEmpty()) {
+					result = values.get(0);
+				}
+			}
+			return result;
+		}
+
+		/**
+		 * Retrieves the values of a multi or list-valued key. Keys are
+		 * case-insensitive, so
+		 * {@code getValue("HostName") == getValue("HOSTNAME")}.
+		 *
+		 * @param key
+		 *            to get the values of
+		 * @return a possibly empty list of values
+		 */
+		public List<String> getValues(String key) {
+			List<String> values = listOptions != null ? listOptions.get(key)
+					: null;
+			if (values == null) {
+				values = multiOptions != null ? multiOptions.get(key) : null;
+			}
+			if (values == null || values.isEmpty()) {
+				return new ArrayList<>();
+			}
+			return new ArrayList<>(values);
+		}
+
+		/**
+		 * Sets the value of a single-valued key if it not set yet, or adds a
+		 * value to a multi-valued key. If the value is {@code null}, the key is
+		 * removed altogether, whether it is single-, list-, or multi-valued.
+		 *
+		 * @param key
+		 *            to modify
+		 * @param value
+		 *            to set or add
+		 */
+		public void setValue(String key, String value) {
+			if (value == null) {
+				if (multiOptions != null) {
+					multiOptions.remove(key);
+				}
+				if (listOptions != null) {
+					listOptions.remove(key);
+				}
+				if (options != null) {
+					options.remove(key);
+				}
+				return;
+			}
+			if (MULTI_KEYS.contains(key)) {
+				if (multiOptions == null) {
+					multiOptions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+				}
+				List<String> values = multiOptions.get(key);
+				if (values == null) {
+					values = new ArrayList<>(4);
+					multiOptions.put(key, values);
+				}
+				values.add(value);
+			} else {
+				if (options == null) {
+					options = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+				}
+				if (!options.containsKey(key)) {
+					options.put(key, value);
+				}
+			}
+		}
+
+		/**
+		 * Sets the values of a multi- or list-valued key.
+		 *
+		 * @param key
+		 *            to set
+		 * @param values
+		 *            a non-empty list of values
+		 */
+		public void setValue(String key, List<String> values) {
+			if (values.isEmpty()) {
+				return;
+			}
+			// Check multi-valued keys first; because of the replacement
+			// strategy, they must take precedence over list-valued keys
+			// which always follow the "first occurrence wins" strategy.
+			//
+			// Note that SendEnv is a multi-valued list-valued key. (It's
+			// rather immaterial for JGit, though.)
+			if (MULTI_KEYS.contains(key)) {
+				if (multiOptions == null) {
+					multiOptions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+				}
+				List<String> items = multiOptions.get(key);
+				if (items == null) {
+					items = new ArrayList<>(values);
+					multiOptions.put(key, items);
+				} else {
+					items.addAll(values);
+				}
+			} else {
+				if (listOptions == null) {
+					listOptions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+				}
+				if (!listOptions.containsKey(key)) {
+					listOptions.put(key, values);
+				}
+			}
+		}
+
+		/**
+		 * Does the key take a whitespace-separated list of values?
+		 *
+		 * @param key
+		 *            to check
+		 * @return {@code true} if the key is a list-valued key.
+		 */
+		public static boolean isListKey(String key) {
+			return LIST_KEYS.contains(key.toUpperCase(Locale.ROOT));
+		}
+
+		void merge(HostEntry entry) {
+			if (entry == null) {
+				// Can occur if we could not read the config file
+				return;
+			}
+			if (entry.options != null) {
+				if (options == null) {
+					options = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+				}
+				for (Map.Entry<String, String> item : entry.options
+						.entrySet()) {
+					if (!options.containsKey(item.getKey())) {
+						options.put(item.getKey(), item.getValue());
+					}
+				}
+			}
+			if (entry.listOptions != null) {
+				if (listOptions == null) {
+					listOptions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+				}
+				for (Map.Entry<String, List<String>> item : entry.listOptions
+						.entrySet()) {
+					if (!listOptions.containsKey(item.getKey())) {
+						listOptions.put(item.getKey(), item.getValue());
+					}
+				}
+
+			}
+			if (entry.multiOptions != null) {
+				if (multiOptions == null) {
+					multiOptions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+				}
+				for (Map.Entry<String, List<String>> item : entry.multiOptions
+						.entrySet()) {
+					List<String> values = multiOptions.get(item.getKey());
+					if (values == null) {
+						values = new ArrayList<>(item.getValue());
+						multiOptions.put(item.getKey(), values);
+					} else {
+						values.addAll(item.getValue());
+					}
+				}
+			}
+		}
+
+		private List<String> substitute(List<String> values, String allowed,
+				Replacer r) {
+			List<String> result = new ArrayList<>(values.size());
+			for (String value : values) {
+				result.add(r.substitute(value, allowed));
+			}
+			return result;
+		}
+
+		private List<String> replaceTilde(List<String> values, File home) {
+			List<String> result = new ArrayList<>(values.size());
+			for (String value : values) {
+				result.add(toFile(value, home).getPath());
+			}
+			return result;
+		}
+
+		void substitute(String originalHostName, int port, String userName,
+				String localUserName, File home) {
+			int p = port >= 0 ? port : positive(getValue(SshConstants.PORT));
+			if (p < 0) {
+				p = SshConstants.SSH_DEFAULT_PORT;
+			}
+			String u = userName != null && !userName.isEmpty() ? userName
+					: getValue(SshConstants.USER);
+			if (u == null || u.isEmpty()) {
+				u = localUserName;
+			}
+			Replacer r = new Replacer(originalHostName, p, u, localUserName,
+					home);
+			if (options != null) {
+				// HOSTNAME first
+				String hostName = options.get(SshConstants.HOST_NAME);
+				if (hostName == null || hostName.isEmpty()) {
+					options.put(SshConstants.HOST_NAME, originalHostName);
+				} else {
+					hostName = r.substitute(hostName, "h"); //$NON-NLS-1$
+					options.put(SshConstants.HOST_NAME, hostName);
+					r.update('h', hostName);
+				}
+			}
+			if (multiOptions != null) {
+				List<String> values = multiOptions
+						.get(SshConstants.IDENTITY_FILE);
+				if (values != null) {
+					values = substitute(values, "dhlru", r); //$NON-NLS-1$
+					values = replaceTilde(values, home);
+					multiOptions.put(SshConstants.IDENTITY_FILE, values);
+				}
+				values = multiOptions.get(SshConstants.CERTIFICATE_FILE);
+				if (values != null) {
+					values = substitute(values, "dhlru", r); //$NON-NLS-1$
+					values = replaceTilde(values, home);
+					multiOptions.put(SshConstants.CERTIFICATE_FILE, values);
+				}
+			}
+			if (listOptions != null) {
+				List<String> values = listOptions
+						.get(SshConstants.USER_KNOWN_HOSTS_FILE);
+				if (values != null) {
+					values = replaceTilde(values, home);
+					listOptions.put(SshConstants.USER_KNOWN_HOSTS_FILE, values);
+				}
+			}
+			if (options != null) {
+				// HOSTNAME already done above
+				String value = options.get(SshConstants.IDENTITY_AGENT);
+				if (value != null) {
+					value = r.substitute(value, "dhlru"); //$NON-NLS-1$
+					value = toFile(value, home).getPath();
+					options.put(SshConstants.IDENTITY_AGENT, value);
+				}
+				value = options.get(SshConstants.CONTROL_PATH);
+				if (value != null) {
+					value = r.substitute(value, "ChLlnpru"); //$NON-NLS-1$
+					value = toFile(value, home).getPath();
+					options.put(SshConstants.CONTROL_PATH, value);
+				}
+				value = options.get(SshConstants.LOCAL_COMMAND);
+				if (value != null) {
+					value = r.substitute(value, "CdhlnprTu"); //$NON-NLS-1$
+					options.put(SshConstants.LOCAL_COMMAND, value);
+				}
+				value = options.get(SshConstants.REMOTE_COMMAND);
+				if (value != null) {
+					value = r.substitute(value, "Cdhlnpru"); //$NON-NLS-1$
+					options.put(SshConstants.REMOTE_COMMAND, value);
+				}
+				value = options.get(SshConstants.PROXY_COMMAND);
+				if (value != null) {
+					value = r.substitute(value, "hpr"); //$NON-NLS-1$
+					options.put(SshConstants.PROXY_COMMAND, value);
+				}
+			}
+			// Match is not implemented and would need to be done elsewhere
+			// anyway.
+		}
+
+		/**
+		 * Retrieves an unmodifiable map of all single-valued options, with
+		 * case-insensitive lookup by keys.
+		 *
+		 * @return all single-valued options
+		 */
+		@NonNull
+		public Map<String, String> getOptions() {
+			if (options == null) {
+				return Collections.emptyMap();
+			}
+			return Collections.unmodifiableMap(options);
+		}
+
+		/**
+		 * Retrieves an unmodifiable map of all multi-valued options, with
+		 * case-insensitive lookup by keys.
+		 *
+		 * @return all multi-valued options
+		 */
+		@NonNull
+		public Map<String, List<String>> getMultiValuedOptions() {
+			if (listOptions == null && multiOptions == null) {
+				return Collections.emptyMap();
+			}
+			Map<String, List<String>> allValues = new TreeMap<>(
+					String.CASE_INSENSITIVE_ORDER);
+			if (multiOptions != null) {
+				allValues.putAll(multiOptions);
+			}
+			if (listOptions != null) {
+				allValues.putAll(listOptions);
+			}
+			return Collections.unmodifiableMap(allValues);
+		}
+
+		@Override
+		@SuppressWarnings("nls")
+		public String toString() {
+			return "HostEntry [options=" + options + ", multiOptions="
+					+ multiOptions + ", listOptions=" + listOptions + "]";
+		}
+	}
+
+	private static class Replacer {
+		private final Map<Character, String> replacements = new HashMap<>();
+
+		public Replacer(String host, int port, String user,
+				String localUserName, File home) {
+			replacements.put(Character.valueOf('%'), "%"); //$NON-NLS-1$
+			replacements.put(Character.valueOf('d'), home.getPath());
+			replacements.put(Character.valueOf('h'), host);
+			String localhost = SystemReader.getInstance().getHostname();
+			replacements.put(Character.valueOf('l'), localhost);
+			int period = localhost.indexOf('.');
+			if (period > 0) {
+				localhost = localhost.substring(0, period);
+			}
+			replacements.put(Character.valueOf('L'), localhost);
+			replacements.put(Character.valueOf('n'), host);
+			replacements.put(Character.valueOf('p'), Integer.toString(port));
+			replacements.put(Character.valueOf('r'), user == null ? "" : user); //$NON-NLS-1$
+			replacements.put(Character.valueOf('u'), localUserName);
+			replacements.put(Character.valueOf('C'),
+					substitute("%l%h%p%r", "hlpr")); //$NON-NLS-1$ //$NON-NLS-2$
+			replacements.put(Character.valueOf('T'), "NONE"); //$NON-NLS-1$
+		}
+
+		public void update(char key, String value) {
+			replacements.put(Character.valueOf(key), value);
+			if ("lhpr".indexOf(key) >= 0) { //$NON-NLS-1$
+				replacements.put(Character.valueOf('C'),
+						substitute("%l%h%p%r", "hlpr")); //$NON-NLS-1$ //$NON-NLS-2$
+			}
+		}
+
+		public String substitute(String input, String allowed) {
+			if (input == null || input.length() <= 1
+					|| input.indexOf('%') < 0) {
+				return input;
+			}
+			StringBuilder builder = new StringBuilder();
+			int start = 0;
+			int length = input.length();
+			while (start < length) {
+				int percent = input.indexOf('%', start);
+				if (percent < 0 || percent + 1 >= length) {
+					builder.append(input.substring(start));
+					break;
+				}
+				String replacement = null;
+				char ch = input.charAt(percent + 1);
+				if (ch == '%' || allowed.indexOf(ch) >= 0) {
+					replacement = replacements.get(Character.valueOf(ch));
+				}
+				if (replacement == null) {
+					builder.append(input.substring(start, percent + 2));
+				} else {
+					builder.append(input.substring(start, percent))
+							.append(replacement);
+				}
+				start = percent + 2;
+			}
+			return builder.toString();
+		}
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	@SuppressWarnings("nls")
+	public String toString() {
+		return "OpenSshConfig [home=" + home + ", configFile=" + configFile
+				+ ", lastModified=" + lastModified + ", state=" + state + "]";
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncObjectLoaderQueue.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncObjectLoaderQueue.java
index b4ea0e9..659c67c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncObjectLoaderQueue.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncObjectLoaderQueue.java
@@ -78,7 +78,7 @@
 	 * @throws java.io.IOException
 	 *             the object store cannot be accessed.
 	 */
-	public boolean next() throws MissingObjectException, IOException;
+	boolean next() throws MissingObjectException, IOException;
 
 	/**
 	 * Get the current object, null if the implementation lost track.
@@ -87,14 +87,14 @@
 	 *         Implementations may for performance reasons discard the caller's
 	 *         ObjectId and provider their own through {@link #getObjectId()}.
 	 */
-	public T getCurrent();
+	T getCurrent();
 
 	/**
 	 * Get the ObjectId of the current object. Never null.
 	 *
 	 * @return the ObjectId of the current object. Never null.
 	 */
-	public ObjectId getObjectId();
+	ObjectId getObjectId();
 
 	/**
 	 * Obtain a loader to read the object.
@@ -115,5 +115,5 @@
 	 * @throws java.io.IOException
 	 *             the object store cannot be accessed.
 	 */
-	public ObjectLoader open() throws IOException;
+	ObjectLoader open() throws IOException;
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncObjectSizeQueue.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncObjectSizeQueue.java
index 03efcd2..6b8642f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncObjectSizeQueue.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncObjectSizeQueue.java
@@ -73,7 +73,7 @@
 	 * @throws java.io.IOException
 	 *             the object store cannot be accessed.
 	 */
-	public boolean next() throws MissingObjectException, IOException;
+	boolean next() throws MissingObjectException, IOException;
 
 	/**
 	 * <p>getCurrent.</p>
@@ -82,19 +82,19 @@
 	 *         Implementations may for performance reasons discard the caller's
 	 *         ObjectId and provider their own through {@link #getObjectId()}.
 	 */
-	public T getCurrent();
+	T getCurrent();
 
 	/**
 	 * Get the ObjectId of the current object. Never null.
 	 *
 	 * @return the ObjectId of the current object. Never null.
 	 */
-	public ObjectId getObjectId();
+	ObjectId getObjectId();
 
 	/**
 	 * Get the size of the current object.
 	 *
 	 * @return the size of the current object.
 	 */
-	public long getSize();
+	long getSize();
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncOperation.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncOperation.java
index 00555b0..27b9c20 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncOperation.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncOperation.java
@@ -68,10 +68,10 @@
 	 * @return false if the task could not be cancelled, typically because it
 	 *         has already completed normally; true otherwise
 	 */
-	public boolean cancel(boolean mayInterruptIfRunning);
+	boolean cancel(boolean mayInterruptIfRunning);
 
 	/**
 	 * Release resources used by the operation, including cancellation.
 	 */
-	public void release();
+	void release();
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobObjectChecker.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobObjectChecker.java
index 3fa3168..7878351 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobObjectChecker.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobObjectChecker.java
@@ -55,7 +55,7 @@
  */
 public interface BlobObjectChecker {
 	/** No-op implementation of {@link BlobObjectChecker}. */
-	public static final BlobObjectChecker NULL_CHECKER =
+	BlobObjectChecker NULL_CHECKER =
 			new BlobObjectChecker() {
 				@Override
 				public void update(byte[] in, int p, int len) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CheckoutEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CheckoutEntry.java
index cfc0cc8..84ff0a8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CheckoutEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CheckoutEntry.java
@@ -12,13 +12,13 @@
 	 *
 	 * @return the name of the branch before checkout
 	 */
-	public abstract String getFromBranch();
+	String getFromBranch();
 
 	/**
 	 * Get the name of the branch after checkout
 	 *
 	 * @return the name of the branch after checkout
 	 */
-	public abstract String getToBranch();
+	String getToBranch();
 
 }
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 b666f21..4726975 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
@@ -62,7 +62,6 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
-import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.events.ConfigChangedEvent;
 import org.eclipse.jgit.events.ConfigChangedListener;
@@ -868,7 +867,7 @@
 
 		boolean lastWasMatch = false;
 		for (ConfigLine e : srcState.entryList) {
-			if (e.match(section, subsection)) {
+			if (e.includedFrom == null && e.match(section, subsection)) {
 				// Skip this record, it's for the section we are removing.
 				lastWasMatch = true;
 				continue;
@@ -923,7 +922,7 @@
 		//
 		while (entryIndex < entries.size() && valueIndex < values.size()) {
 			final ConfigLine e = entries.get(entryIndex);
-			if (e.match(section, subsection, name)) {
+			if (e.includedFrom == null && e.match(section, subsection, name)) {
 				entries.set(entryIndex, e.forValue(values.get(valueIndex++)));
 				insertPosition = entryIndex + 1;
 			}
@@ -935,7 +934,8 @@
 		if (valueIndex == values.size() && entryIndex < entries.size()) {
 			while (entryIndex < entries.size()) {
 				final ConfigLine e = entries.get(entryIndex++);
-				if (e.match(section, subsection, name))
+				if (e.includedFrom == null
+						&& e.match(section, subsection, name))
 					entries.remove(--entryIndex);
 			}
 		}
@@ -948,7 +948,8 @@
 				// is already a section available that matches. Insert
 				// after the last key of that section.
 				//
-				insertPosition = findSectionEnd(entries, section, subsection);
+				insertPosition = findSectionEnd(entries, section, subsection,
+						true);
 			}
 			if (insertPosition < 0) {
 				// We didn't find any matching section header for this key,
@@ -985,9 +986,14 @@
 	}
 
 	private static int findSectionEnd(final List<ConfigLine> entries,
-			final String section, final String subsection) {
+			final String section, final String subsection,
+			boolean skipIncludedLines) {
 		for (int i = 0; i < entries.size(); i++) {
 			ConfigLine e = entries.get(i);
+			if (e.includedFrom != null && skipIncludedLines) {
+				continue;
+			}
+
 			if (e.match(section, subsection, null)) {
 				i++;
 				while (i < entries.size()) {
@@ -1011,6 +1017,8 @@
 	public String toText() {
 		final StringBuilder out = new StringBuilder();
 		for (ConfigLine e : state.get().entryList) {
+			if (e.includedFrom != null)
+				continue;
 			if (e.prefix != null)
 				out.append(e.prefix);
 			if (e.section != null && e.name == null) {
@@ -1060,11 +1068,11 @@
 	 *             made to {@code this}.
 	 */
 	public void fromText(String text) throws ConfigInvalidException {
-		state.set(newState(fromTextRecurse(text, 1)));
+		state.set(newState(fromTextRecurse(text, 1, null)));
 	}
 
-	private List<ConfigLine> fromTextRecurse(String text, int depth)
-			throws ConfigInvalidException {
+	private List<ConfigLine> fromTextRecurse(String text, int depth,
+			String includedFrom) throws ConfigInvalidException {
 		if (depth > MAX_DEPTH) {
 			throw new ConfigInvalidException(
 					JGitText.get().tooManyIncludeRecursions);
@@ -1073,6 +1081,7 @@
 		final StringReader in = new StringReader(text);
 		ConfigLine last = null;
 		ConfigLine e = new ConfigLine();
+		e.includedFrom = includedFrom;
 		for (;;) {
 			int input = in.read();
 			if (-1 == input) {
@@ -1088,7 +1097,7 @@
 				if (e.section != null)
 					last = e;
 				e = new ConfigLine();
-
+				e.includedFrom = includedFrom;
 			} else if (e.suffix != null) {
 				// Everything up until the end-of-line is in the suffix.
 				e.suffix += c;
@@ -1148,7 +1157,6 @@
 	 *             if something went wrong while reading the config
 	 * @since 4.10
 	 */
-	@Nullable
 	protected byte[] readIncludedConfig(String relPath)
 			throws ConfigInvalidException {
 		return null;
@@ -1173,7 +1181,7 @@
 			decoded = RawParseUtils.decode(bytes);
 		}
 		try {
-			newEntries.addAll(fromTextRecurse(decoded, depth + 1));
+			newEntries.addAll(fromTextRecurse(decoded, depth + 1, line.value));
 		} catch (ConfigInvalidException e) {
 			throw new ConfigInvalidException(MessageFormat
 					.format(JGitText.get().cannotReadFile, line.value), e);
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 d4a0280..9763fb7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
@@ -119,6 +119,36 @@
 	 */
 	public static final String CONFIG_FILTER_SECTION = "filter";
 
+	/**
+	 * The "gpg" section
+	 * @since 5.2
+	 */
+	public static final String CONFIG_GPG_SECTION = "gpg";
+
+	/**
+	 * The "format" key
+	 * @since 5.2
+	 */
+	public static final String CONFIG_KEY_FORMAT = "format";
+
+	/**
+	 * The "signingKey" key
+	 * @since 5.2
+	 */
+	public static final String CONFIG_KEY_SIGNINGKEY = "signingKey";
+
+	/**
+	 * The "commit" section
+	 * @since 5.2
+	 */
+	public static final String CONFIG_COMMIT_SECTION = "commit";
+
+	/**
+	 * The "gpgSign" key
+	 * @since 5.2
+	 */
+	public static final String CONFIG_KEY_GPGSIGN = "gpgSign";
+
 	/** The "algorithm" key */
 	public static final String CONFIG_KEY_ALGORITHM = "algorithm";
 
@@ -432,4 +462,18 @@
 	 * @since 4.11
 	 */
 	public static final String CONFIG_SECTION_LFS = "lfs";
+
+	/**
+	 * The "i18n" section
+	 *
+	 * @since 5.2
+	 */
+	public static final String CONFIG_SECTION_I18N = "i18n";
+
+	/**
+	 * The "logOutputEncoding" key
+	 *
+	 * @since 5.2
+	 */
+	public static final String CONFIG_KEY_LOG_OUTPUT_ENCODING = "logOutputEncoding";
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigLine.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigLine.java
index 937ba92..e623a8c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigLine.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigLine.java
@@ -73,6 +73,9 @@
 	/** The text content after entry. */
 	String suffix;
 
+	/** The source from which this line was included from. */
+	String includedFrom;
+
 	ConfigLine forValue(String newValue) {
 		final ConfigLine e = new ConfigLine();
 		e.prefix = prefix;
@@ -81,6 +84,7 @@
 		e.name = name;
 		e.value = newValue;
 		e.suffix = suffix;
+		e.includedFrom = includedFrom;
 		return e;
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
index ed00554..4c55196 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
@@ -232,11 +232,17 @@
 	 *
 	 * @deprecated Use {@link java.nio.charset.StandardCharsets#UTF_8} directly
 	 *             instead.
-	 **/
+	 */
 	@Deprecated
 	public static final Charset CHARSET;
 
-	/** Native character encoding for commit messages, file names... */
+	/**
+	 * Native character encoding for commit messages, file names...
+	 *
+	 * @deprecated Use {@link java.nio.charset.StandardCharsets#UTF_8} directly
+	 *             instead.
+	 */
+	@Deprecated
 	public static final String CHARACTER_ENCODING;
 
 	/** Default main branch name */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
index 891c7f2..6a66cf6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
@@ -293,7 +293,8 @@
 
 	/** {@inheritDoc} */
 	@Override
-	public @NonNull List<RefSpec> getRefSpecs(Config config, String section,
+	@NonNull
+	public List<RefSpec> getRefSpecs(Config config, String section,
 			String subsection, String name) {
 		String[] values = config.getStringList(section, subsection, name);
 		List<RefSpec> result = new ArrayList<>(values.length);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgConfig.java
new file mode 100644
index 0000000..a09bc00
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgConfig.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2018, Salesforce.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.lib;
+
+/**
+ * Typed access to GPG related configuration options.
+ *
+ * @since 5.2
+ */
+public class GpgConfig {
+
+	/**
+	 * Config values for gpg.format.
+	 */
+	public enum GpgFormat implements Config.ConfigEnum {
+
+		/** Value for openpgp */
+		OPENPGP("openpgp"), //$NON-NLS-1$
+		/** Value for x509 */
+		X509("x509"); //$NON-NLS-1$
+
+		private final String configValue;
+
+		private GpgFormat(String configValue) {
+			this.configValue = configValue;
+		}
+
+		@Override
+		public boolean matchConfigValue(String s) {
+			return configValue.equals(s);
+		}
+
+		@Override
+		public String toConfigValue() {
+			return configValue;
+		}
+	}
+
+	private final Config config;
+
+	/**
+	 * Create a new GPG config, which will read configuration from config.
+	 *
+	 * @param config
+	 *            the config to read from
+	 */
+	public GpgConfig(Config config) {
+		this.config = config;
+	}
+
+	/**
+	 * Retrieves the config value of gpg.format.
+	 *
+	 * @return the {@link org.eclipse.jgit.lib.GpgConfig.GpgFormat}
+	 */
+	public GpgFormat getKeyFormat() {
+		return config.getEnum(GpgFormat.values(),
+				ConfigConstants.CONFIG_GPG_SECTION, null,
+				ConfigConstants.CONFIG_KEY_FORMAT, GpgFormat.OPENPGP);
+	}
+
+	/**
+	 * Retrieves the config value of user.signingKey.
+	 *
+	 * @return the value of user.signingKey (may be <code>null</code>)
+	 */
+	public String getSigningKey() {
+		return config.getString(ConfigConstants.CONFIG_USER_SECTION, null,
+				ConfigConstants.CONFIG_KEY_SIGNINGKEY);
+	}
+
+	/**
+	 * Retrieves the config value of commit.gpgSign.
+	 *
+	 * @return the value of commit.gpgSign (defaults to <code>false</code>)
+	 */
+	public boolean isSignCommits() {
+		return config.getBoolean(ConfigConstants.CONFIG_COMMIT_SECTION,
+				ConfigConstants.CONFIG_KEY_GPGSIGN, false);
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
index 94b9ddc..f37c310 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
@@ -47,7 +47,11 @@
 
 package org.eclipse.jgit.lib;
 
+import java.io.File;
 import java.io.IOException;
+import java.nio.file.DirectoryIteratorException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -261,6 +265,8 @@
 
 	private Set<String> missing = new HashSet<>();
 
+	private Set<String> missingSubmodules = new HashSet<>();
+
 	private Set<String> modified = new HashSet<>();
 
 	private Set<String> untracked = new HashSet<>();
@@ -501,9 +507,15 @@
 				if (dirCacheIterator != null) {
 					if (workingTreeIterator == null) {
 						// in index, not in workdir => missing
-						if (!isEntryGitLink(dirCacheIterator)
-								|| ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL)
-							missing.add(treeWalk.getPathString());
+						boolean isGitLink = isEntryGitLink(dirCacheIterator);
+						if (!isGitLink
+								|| ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL) {
+							String path = treeWalk.getPathString();
+							missing.add(path);
+							if (isGitLink) {
+								missingSubmodules.add(path);
+							}
+						}
 					} else {
 						if (workingTreeIterator.isModified(
 								dirCacheIterator.getDirCacheEntry(), true,
@@ -543,8 +555,8 @@
 							smw.getPath()), e);
 				}
 				try (Repository subRepo = smw.getRepository()) {
+					String subRepoPath = smw.getPath();
 					if (subRepo != null) {
-						String subRepoPath = smw.getPath();
 						ObjectId subHead = subRepo.resolve("HEAD"); //$NON-NLS-1$
 						if (subHead != null
 								&& !subHead.equals(smw.getObjectId())) {
@@ -573,6 +585,21 @@
 								recordFileMode(subRepoPath, FileMode.GITLINK);
 							}
 						}
+					} else if (missingSubmodules.remove(subRepoPath)) {
+						// If the directory is there and empty but the submodule
+						// repository in .git/modules doesn't exist yet it isn't
+						// "missing".
+						File gitDir = new File(
+								new File(repository.getDirectory(),
+										Constants.MODULES),
+								subRepoPath);
+						if (!gitDir.isDirectory()) {
+							File dir = SubmoduleWalk.getSubmoduleDirectory(
+									repository, subRepoPath);
+							if (dir.isDirectory() && !hasFiles(dir)) {
+								missing.remove(subRepoPath);
+							}
+						}
 					}
 				}
 			}
@@ -592,6 +619,15 @@
 			return true;
 	}
 
+	private boolean hasFiles(File directory) {
+		try (DirectoryStream<java.nio.file.Path> dir = Files
+				.newDirectoryStream(directory.toPath())) {
+			return dir.iterator().hasNext();
+		} catch (DirectoryIteratorException | IOException e) {
+			return false;
+		}
+	}
+
 	private void recordFileMode(String path, FileMode mode) {
 		Set<String> values = fileModes.get(mode);
 		if (path != null) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
index d37fb21..127f019 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
@@ -109,7 +109,8 @@
  * the caller can provide both of these validations on its own.
  * <p>
  * Instances of this class are not thread safe, but they may be reused to
- * perform multiple object validations.
+ * perform multiple object validations, calling {@link #reset()} between them to
+ * clear the internal state (e.g. {@link #getGitsubmodules()})
  */
 public class ObjectChecker {
 	/** Header "tree " */
@@ -173,6 +174,13 @@
 		/***/ BAD_TIMEZONE,
 		/***/ MISSING_EMAIL,
 		/***/ MISSING_SPACE_BEFORE_DATE,
+		/** @since 5.2 */ GITMODULES_BLOB,
+		/** @since 5.2 */ GITMODULES_LARGE,
+		/** @since 5.2 */ GITMODULES_NAME,
+		/** @since 5.2 */ GITMODULES_PARSE,
+		/** @since 5.2 */ GITMODULES_PATH,
+		/** @since 5.2 */ GITMODULES_SYMLINK,
+		/** @since 5.2 */ GITMODULES_URL,
 		/***/ UNKNOWN_TYPE,
 
 		// These are unique to JGit.
@@ -1251,4 +1259,19 @@
 	public List<GitmoduleEntry> getGitsubmodules() {
 		return gitsubmodules;
 	}
+
+	/**
+	 * Reset the invocation-specific state from this instance. Specifically this
+	 * clears the list of .gitmodules files encountered (see
+	 * {@link #getGitsubmodules()})
+	 *
+	 * Configurations like errors to filter, skip lists or the specified O.S.
+	 * (set via {@link #setSafeForMacOS(boolean)} or
+	 * {@link #setSafeForWindows(boolean)}) are NOT cleared.
+	 *
+	 * @since 5.2
+	 */
+	public void reset() {
+		gitsubmodules.clear();
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ProgressMonitor.java
index d81ee45..9d8d71a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ProgressMonitor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ProgressMonitor.java
@@ -49,7 +49,7 @@
  */
 public interface ProgressMonitor {
 	/** Constant indicating the total work units cannot be predicted. */
-	public static final int UNKNOWN = 0;
+	int UNKNOWN = 0;
 
 	/**
 	 * Advise the monitor of the total number of subtasks.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java
index b000558..faabbf8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java
@@ -61,7 +61,7 @@
  */
 public interface Ref {
 	/** Location where a {@link Ref} is stored. */
-	public static enum Storage {
+	enum Storage {
 		/**
 		 * The ref does not exist yet, updating it may create it.
 		 * <p>
@@ -131,7 +131,7 @@
 	 * @return name of this ref.
 	 */
 	@NonNull
-	public String getName();
+	String getName();
 
 	/**
 	 * Test if this reference is a symbolic reference.
@@ -144,7 +144,7 @@
 	 * @return true if this is a symbolic reference; false if this reference
 	 *         contains its own ObjectId.
 	 */
-	public abstract boolean isSymbolic();
+	boolean isSymbolic();
 
 	/**
 	 * Traverse target references until {@link #isSymbolic()} is false.
@@ -163,7 +163,7 @@
 	 * @return the reference that actually stores the ObjectId value.
 	 */
 	@NonNull
-	public abstract Ref getLeaf();
+	Ref getLeaf();
 
 	/**
 	 * Get the reference this reference points to, or {@code this}.
@@ -178,7 +178,7 @@
 	 * @return the target reference, or {@code this}.
 	 */
 	@NonNull
-	public abstract Ref getTarget();
+	Ref getTarget();
 
 	/**
 	 * Cached value of this ref.
@@ -188,7 +188,7 @@
 	 *         symbolic ref pointing to an unborn branch.
 	 */
 	@Nullable
-	public abstract ObjectId getObjectId();
+	ObjectId getObjectId();
 
 	/**
 	 * Cached value of <code>ref^{}</code> (the ref peeled to commit).
@@ -198,14 +198,14 @@
 	 *         does not refer to an annotated tag.
 	 */
 	@Nullable
-	public abstract ObjectId getPeeledObjectId();
+	ObjectId getPeeledObjectId();
 
 	/**
 	 * Whether the Ref represents a peeled tag.
 	 *
 	 * @return whether the Ref represents a peeled tag.
 	 */
-	public abstract boolean isPeeled();
+	boolean isPeeled();
 
 	/**
 	 * How was this ref obtained?
@@ -216,5 +216,5 @@
 	 * @return type of ref.
 	 */
 	@NonNull
-	public abstract Storage getStorage();
+	Storage getStorage();
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
index 3170787..68929b4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
@@ -415,6 +415,31 @@
 	}
 
 	/**
+	 * Returns refs whose names start with one of the given prefixes.
+	 * <p>
+	 * The default implementation uses {@link #getRefsByPrefix(String)}.
+	 * Implementors of {@link RefDatabase} should override this method directly
+	 * if a better implementation is possible.
+	 *
+	 * @param prefixes
+	 *            strings that names of refs should start with.
+	 * @return immutable list of refs whose names start with one of
+	 *         {@code prefixes}. Refs can be unsorted and may contain duplicates
+	 *         if the prefixes overlap.
+	 * @throws java.io.IOException
+	 *             the reference space cannot be accessed.
+	 * @since 5.2
+	 */
+	@NonNull
+	public List<Ref> getRefsByPrefix(String... prefixes) throws IOException {
+		List<Ref> result = new ArrayList<>();
+		for (String prefix : prefixes) {
+			result.addAll(getRefsByPrefix(prefix));
+		}
+		return Collections.unmodifiableList(result);
+	}
+
+	/**
 	 * Check if any refs exist in the ref database.
 	 * <p>
 	 * This uses the same definition of refs as {@link #getRefs()}. In
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
index 5cd593e..fc3ea84 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
@@ -823,7 +823,7 @@
 	 * Handle the abstraction of storing a ref update. This is because both
 	 * updating and deleting of a ref have merge testing in common.
 	 */
-	private abstract class Store {
+	private static abstract class Store {
 		abstract Result execute(Result status) throws IOException;
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogEntry.java
index 51f2ea0..824bbc4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogEntry.java
@@ -57,7 +57,7 @@
 	 *
 	 * @since 4.9
 	 */
-	public static final String PREFIX_CREATED = "created"; //$NON-NLS-1$
+	String PREFIX_CREATED = "created"; //$NON-NLS-1$
 
 	/**
 	 * Prefix used in reflog messages when the ref was updated with a fast
@@ -69,7 +69,7 @@
 	 *
 	 * @since 4.9
 	 */
-	public static final String PREFIX_FAST_FORWARD = "fast-forward"; //$NON-NLS-1$
+	String PREFIX_FAST_FORWARD = "fast-forward"; //$NON-NLS-1$
 
 	/**
 	 * Prefix used in reflog messages when the ref was force updated.
@@ -80,35 +80,35 @@
 	 *
 	 * @since 4.9
 	 */
-	public static final String PREFIX_FORCED_UPDATE = "forced-update"; //$NON-NLS-1$
+	String PREFIX_FORCED_UPDATE = "forced-update"; //$NON-NLS-1$
 
 	/**
 	 * Get the commit id before the change
 	 *
 	 * @return the commit id before the change
 	 */
-	public abstract ObjectId getOldId();
+	ObjectId getOldId();
 
 	/**
 	 * Get the commit id after the change
 	 *
 	 * @return the commit id after the change
 	 */
-	public abstract ObjectId getNewId();
+	ObjectId getNewId();
 
 	/**
 	 * Get user performing the change
 	 *
 	 * @return user performing the change
 	 */
-	public abstract PersonIdent getWho();
+	PersonIdent getWho();
 
 	/**
 	 * Get textual description of the change
 	 *
 	 * @return textual description of the change
 	 */
-	public abstract String getComment();
+	String getComment();
 
 	/**
 	 * Parse checkout
@@ -117,6 +117,6 @@
 	 *         information about a branch switch, or null if the entry is not a
 	 *         checkout
 	 */
-	public abstract CheckoutEntry parseCheckout();
+	CheckoutEntry parseCheckout();
 
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java
index f97b07e..4f104d2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java
@@ -59,7 +59,7 @@
 	 * @return the latest reflog entry, or null if no log
 	 * @throws java.io.IOException
 	 */
-	public abstract ReflogEntry getLastEntry() throws IOException;
+	ReflogEntry getLastEntry() throws IOException;
 
 	/**
 	 * Get all reflog entries in reverse order
@@ -67,7 +67,7 @@
 	 * @return all reflog entries in reverse order
 	 * @throws java.io.IOException
 	 */
-	public abstract List<ReflogEntry> getReverseEntries() throws IOException;
+	List<ReflogEntry> getReverseEntries() throws IOException;
 
 	/**
 	 * Get specific entry in the reflog relative to the last entry which is
@@ -77,7 +77,7 @@
 	 * @return reflog entry or null if not found
 	 * @throws java.io.IOException
 	 */
-	public abstract ReflogEntry getReverseEntry(int number) throws IOException;
+	ReflogEntry getReverseEntry(int number) throws IOException;
 
 	/**
 	 * Get all reflog entries in reverse order
@@ -87,7 +87,5 @@
 	 * @return all reflog entries in reverse order
 	 * @throws java.io.IOException
 	 */
-	public abstract List<ReflogEntry> getReverseEntries(int max)
-			throws IOException;
-
+	List<ReflogEntry> getReverseEntries(int max) throws IOException;
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
index d73c05e..77d268a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
@@ -49,6 +49,7 @@
 package org.eclipse.jgit.lib;
 
 import static org.eclipse.jgit.lib.Constants.LOCK_SUFFIX;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.io.BufferedOutputStream;
 import java.io.File;
@@ -1965,7 +1966,7 @@
 	private void writeCommitMsg(File msgFile, String msg) throws IOException {
 		if (msg != null) {
 			try (FileOutputStream fos = new FileOutputStream(msgFile)) {
-				fos.write(msg.getBytes(Constants.CHARACTER_ENCODING));
+				fos.write(msg.getBytes(UTF_8));
 			}
 		} else {
 			FileUtils.delete(msgFile, FileUtils.SKIP_MISSING);
@@ -1980,7 +1981,6 @@
 	 *         empty
 	 * @throws IOException
 	 */
-	@Nullable
 	private byte[] readGitDirectoryFile(String filename) throws IOException {
 		File file = new File(getDirectory(), filename);
 		try {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java
index 036917e..4796708 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java
@@ -45,6 +45,7 @@
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -63,7 +64,7 @@
 	 * that are LF-separated lines.
 	 *
 	 * @param out
-	 *            the outputstream where to write the textual presentation
+	 *            the output stream where to write the textual presentation
 	 * @param res
 	 *            the merge result which should be presented
 	 * @param seqName
@@ -72,13 +73,44 @@
 	 *            " or "&gt;&gt;&gt;&gt;&gt;&gt;&gt; " conflict markers. The
 	 *            names for the sequences are given in this list
 	 * @param charsetName
-	 *            the name of the characterSet used when writing conflict
+	 *            the name of the character set used when writing conflict
 	 *            metadata
 	 * @throws java.io.IOException
+	 * @deprecated Use
+	 *             {@link #formatMerge(OutputStream, MergeResult, List, Charset)}
+	 *             instead.
 	 */
+	@Deprecated
 	public void formatMerge(OutputStream out, MergeResult<RawText> res,
 			List<String> seqName, String charsetName) throws IOException {
-		new MergeFormatterPass(out, res, seqName, charsetName).formatMerge();
+		formatMerge(out, res, seqName, Charset.forName(charsetName));
+	}
+
+	/**
+	 * Formats the results of a merge of {@link org.eclipse.jgit.diff.RawText}
+	 * objects in a Git conformant way. This method also assumes that the
+	 * {@link org.eclipse.jgit.diff.RawText} objects being merged are line
+	 * oriented files which use LF as delimiter. This method will also use LF to
+	 * separate chunks and conflict metadata, therefore it fits only to texts
+	 * that are LF-separated lines.
+	 *
+	 * @param out
+	 *            the output stream where to write the textual presentation
+	 * @param res
+	 *            the merge result which should be presented
+	 * @param seqName
+	 *            When a conflict is reported each conflicting range will get a
+	 *            name. This name is following the "&lt;&lt;&lt;&lt;&lt;&lt;&lt;
+	 *            " or "&gt;&gt;&gt;&gt;&gt;&gt;&gt; " conflict markers. The
+	 *            names for the sequences are given in this list
+	 * @param charset
+	 *            the character set used when writing conflict metadata
+	 * @throws java.io.IOException
+	 * @since 5.2
+	 */
+	public void formatMerge(OutputStream out, MergeResult<RawText> res,
+			List<String> seqName, Charset charset) throws IOException {
+		new MergeFormatterPass(out, res, seqName, charset).formatMerge();
 	}
 
 	/**
@@ -100,17 +132,51 @@
 	 * @param theirsName
 	 *            the name ranges from theirs should get
 	 * @param charsetName
-	 *            the name of the characterSet used when writing conflict
+	 *            the name of the character set used when writing conflict
 	 *            metadata
 	 * @throws java.io.IOException
+	 * @deprecated use
+	 *             {@link #formatMerge(OutputStream, MergeResult, String, String, String, Charset)}
+	 *             instead.
+	 */
+	@Deprecated
+	public void formatMerge(OutputStream out, MergeResult res, String baseName,
+			String oursName, String theirsName, String charsetName) throws IOException {
+		formatMerge(out, res, baseName, oursName, theirsName,
+				Charset.forName(charsetName));
+	}
+
+	/**
+	 * Formats the results of a merge of exactly two
+	 * {@link org.eclipse.jgit.diff.RawText} objects in a Git conformant way.
+	 * This convenience method accepts the names for the three sequences (base
+	 * and the two merged sequences) as explicit parameters and doesn't require
+	 * the caller to specify a List
+	 *
+	 * @param out
+	 *            the {@link java.io.OutputStream} where to write the textual
+	 *            presentation
+	 * @param res
+	 *            the merge result which should be presented
+	 * @param baseName
+	 *            the name ranges from the base should get
+	 * @param oursName
+	 *            the name ranges from ours should get
+	 * @param theirsName
+	 *            the name ranges from theirs should get
+	 * @param charset
+	 *            the character set used when writing conflict metadata
+	 * @throws java.io.IOException
+	 * @since 5.2
 	 */
 	@SuppressWarnings("unchecked")
 	public void formatMerge(OutputStream out, MergeResult res, String baseName,
-			String oursName, String theirsName, String charsetName) throws IOException {
+			String oursName, String theirsName, Charset charset)
+			throws IOException {
 		List<String> names = new ArrayList<>(3);
 		names.add(baseName);
 		names.add(oursName);
 		names.add(theirsName);
-		formatMerge(out, res, names, charsetName);
+		formatMerge(out, res, names, charset);
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java
index 060f068..e1a8d31 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java
@@ -46,6 +46,7 @@
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.nio.charset.Charset;
 import java.util.List;
 
 import org.eclipse.jgit.diff.RawText;
@@ -59,19 +60,33 @@
 
 	private final List<String> seqName;
 
-	private final String charsetName;
+	private final Charset charset;
 
 	private final boolean threeWayMerge;
 
 	private String lastConflictingName; // is set to non-null whenever we are in
 										// a conflict
 
-	MergeFormatterPass(OutputStream out, MergeResult<RawText> res, List<String> seqName,
-			String charsetName) {
+	/**
+	 * @param out
+	 *            the {@link java.io.OutputStream} where to write the textual
+	 *            presentation
+	 * @param res
+	 *            the merge result which should be presented
+	 * @param seqName
+	 *            When a conflict is reported each conflicting range will get a
+	 *            name. This name is following the "&lt;&lt;&lt;&lt;&lt;&lt;&lt;
+	 *            " or "&gt;&gt;&gt;&gt;&gt;&gt;&gt; " conflict markers. The
+	 *            names for the sequences are given in this list
+	 * @param charset
+	 *            the character set used when writing conflict metadata
+	 */
+	MergeFormatterPass(OutputStream out, MergeResult<RawText> res,
+			List<String> seqName, Charset charset) {
 		this.out = new EolAwareOutputStream(out);
 		this.res = res;
 		this.seqName = seqName;
-		this.charsetName = charsetName;
+		this.charset = charset;
 		this.threeWayMerge = (res.getSequences().size() == 3);
 	}
 
@@ -133,7 +148,7 @@
 
 	private void writeln(String s) throws IOException {
 		out.beginln();
-		out.write((s + "\n").getBytes(charsetName)); //$NON-NLS-1$
+		out.write((s + "\n").getBytes(charset)); //$NON-NLS-1$
 	}
 
 	private void writeLine(RawText seq, int i) throws IOException {
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 f60c95f..412d9bb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
@@ -46,10 +46,10 @@
  */
 package org.eclipse.jgit.merge;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm.HISTOGRAM;
 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DIFF_SECTION;
 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_ALGORITHM;
-import static org.eclipse.jgit.lib.Constants.CHARACTER_ENCODING;
 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
 
 import java.io.BufferedOutputStream;
@@ -1026,7 +1026,7 @@
 				db != null ? nonNullRepo().getDirectory() : null, inCoreLimit);
 		try {
 			new MergeFormatter().formatMerge(buf, result,
-					Arrays.asList(commitNames), CHARACTER_ENCODING);
+					Arrays.asList(commitNames), UTF_8);
 			buf.close();
 		} catch (IOException e) {
 			buf.destroy();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AsyncRevObjectQueue.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AsyncRevObjectQueue.java
index d263184..98654f1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AsyncRevObjectQueue.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AsyncRevObjectQueue.java
@@ -66,5 +66,5 @@
 	 * @throws java.io.IOException
 	 *             the object store cannot be accessed.
 	 */
-	public RevObject next() throws MissingObjectException, IOException;
+	RevObject next() throws MissingObjectException, IOException;
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthGenerator.java
index eaec305..5154920 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthGenerator.java
@@ -48,6 +48,7 @@
 
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.ObjectId;
 
 /**
  * Only produce commits which are below a specified depth.
@@ -59,6 +60,8 @@
 
 	private final int depth;
 
+	private final int deepenSince;
+
 	private final RevWalk walk;
 
 	/**
@@ -79,6 +82,11 @@
 	private final RevFlag REINTERESTING;
 
 	/**
+	 * Commits reachable from commits that the client specified using --shallow-exclude.
+	 */
+	private final RevFlag DEEPEN_NOT;
+
+	/**
 	 * @param w
 	 * @param s Parent generator
 	 * @throws MissingObjectException
@@ -91,8 +99,10 @@
 		walk = (RevWalk)w;
 
 		this.depth = w.getDepth();
+		this.deepenSince = w.getDeepenSince();
 		this.UNSHALLOW = w.getUnshallowFlag();
 		this.REINTERESTING = w.getReinterestingFlag();
+		this.DEEPEN_NOT = w.getDeepenNotFlag();
 
 		s.shareFreeList(pending);
 
@@ -105,6 +115,37 @@
 			if (((DepthWalk.Commit) c).getDepth() == 0)
 				pending.add(c);
 		}
+
+		// Mark DEEPEN_NOT on all deepen-not commits and their ancestors.
+		// TODO(jonathantanmy): This implementation is somewhat
+		// inefficient in that any "deepen-not <ref>" in the request
+		// results in all commits reachable from that ref being parsed
+		// and marked, even if the commit topology is such that it is
+		// not necessary.
+		for (ObjectId oid : w.getDeepenNots()) {
+			RevCommit c;
+			try {
+				c = walk.parseCommit(oid);
+			} catch (IncorrectObjectTypeException notCommit) {
+				// The C Git implementation silently tolerates
+				// non-commits, so do the same here.
+				continue;
+			}
+
+			FIFORevQueue queue = new FIFORevQueue();
+			queue.add(c);
+			while ((c = queue.next()) != null) {
+				if (c.has(DEEPEN_NOT)) {
+					continue;
+				}
+
+				walk.parseHeaders(c);
+				c.add(DEEPEN_NOT);
+				for (RevCommit p : c.getParents()) {
+					queue.add(p);
+				}
+			}
+		}
 	}
 
 	@Override
@@ -132,6 +173,14 @@
 			if ((c.flags & RevWalk.PARSED) == 0)
 				c.parseHeaders(walk);
 
+			if (c.getCommitTime() < deepenSince) {
+				continue;
+			}
+
+			if (c.has(DEEPEN_NOT)) {
+				continue;
+			}
+
 			int newDepth = c.depth + 1;
 
 			for (RevCommit p : c.parents) {
@@ -142,12 +191,29 @@
 				// this depth is guaranteed to be the smallest value that
 				// any path could produce.
 				if (dp.depth == -1) {
+					boolean failsDeepenSince = false;
+					if (deepenSince != 0) {
+						if ((p.flags & RevWalk.PARSED) == 0) {
+							p.parseHeaders(walk);
+						}
+						failsDeepenSince =
+							p.getCommitTime() < deepenSince;
+					}
+
 					dp.depth = newDepth;
 
-					// If the parent is not too deep, add it to the queue
-					// so that we can produce it later
-					if (newDepth <= depth)
+					// If the parent is not too deep and was not excluded, add
+					// it to the queue so that we can produce it later
+					if (newDepth <= depth && !failsDeepenSince &&
+							!p.has(DEEPEN_NOT)) {
 						pending.add(p);
+					} else {
+						dp.makesChildBoundary = true;
+					}
+				}
+
+				if (dp.makesChildBoundary) {
+					c.isBoundary = true;
 				}
 
 				// If the current commit has become unshallowed, everything
@@ -160,8 +226,7 @@
 				}
 			}
 
-			// Produce all commits less than the depth cutoff
-			boolean produce = c.depth <= depth;
+			boolean produce = true;
 
 			// Unshallow commits are uninteresting, but still need to be sent
 			// up to the PackWriter so that it will exclude objects correctly.
@@ -169,6 +234,10 @@
 			if ((c.flags & RevWalk.UNINTERESTING) != 0 && !c.has(UNSHALLOW))
 				produce = false;
 
+			if (c.getCommitTime() < deepenSince) {
+				produce = false;
+			}
+
 			if (produce)
 				return c;
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java
index 06a5272..0201f0b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java
@@ -45,10 +45,14 @@
 package org.eclipse.jgit.revwalk;
 
 import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
 
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.Repository;
 
@@ -61,7 +65,24 @@
 	 *
 	 * @return Depth to filter to.
 	 */
-	public int getDepth();
+	int getDepth();
+
+	/**
+	 * @return the deepen-since value; if not 0, this walk only returns commits
+	 *         whose commit time is at or after this limit
+	 * @since 5.2
+	 */
+	default int getDeepenSince() {
+		return 0;
+	}
+
+	/**
+	 * @return the objects specified by the client using --shallow-exclude
+	 * @since 5.2
+	 */
+	default List<ObjectId> getDeepenNots() {
+		return Collections.emptyList();
+	}
 
 	/** @return flag marking commits that should become unshallow. */
 	/**
@@ -69,26 +90,49 @@
 	 *
 	 * @return flag marking commits that should become unshallow.
 	 */
-	public RevFlag getUnshallowFlag();
+	RevFlag getUnshallowFlag();
 
 	/**
 	 * Get flag marking commits that are interesting again.
 	 *
 	 * @return flag marking commits that are interesting again.
 	 */
-	public RevFlag getReinterestingFlag();
+	RevFlag getReinterestingFlag();
+
+	/**
+	 * @return flag marking commits that are to be excluded because of --shallow-exclude
+	 * @since 5.2
+	 */
+	RevFlag getDeepenNotFlag();
 
 	/** RevCommit with a depth (in commits) from a root. */
 	public static class Commit extends RevCommit {
 		/** Depth of this commit in the graph, via shortest path. */
 		int depth;
 
+		boolean isBoundary;
+
+		/**
+		 * True if this commit was excluded due to a shallow fetch
+		 * setting. All its children are thus boundary commits.
+		 */
+		boolean makesChildBoundary;
+
 		/** @return depth of this commit, as found by the shortest path. */
 		public int getDepth() {
 			return depth;
 		}
 
 		/**
+		 * @return true if at least one of this commit's parents was excluded
+		 *         due to a shallow fetch setting, false otherwise
+		 * @since 5.2
+		 */
+		public boolean isBoundary() {
+			return isBoundary;
+		}
+
+		/**
 		 * Initialize a new commit.
 		 *
 		 * @param id
@@ -104,10 +148,16 @@
 	public class RevWalk extends org.eclipse.jgit.revwalk.RevWalk implements DepthWalk {
 		private final int depth;
 
+		private int deepenSince;
+
+		private List<ObjectId> deepenNots;
+
 		private final RevFlag UNSHALLOW;
 
 		private final RevFlag REINTERESTING;
 
+		private final RevFlag DEEPEN_NOT;
+
 		/**
 		 * @param repo Repository to walk
 		 * @param depth Maximum depth to return
@@ -116,8 +166,10 @@
 			super(repo);
 
 			this.depth = depth;
+			this.deepenNots = Collections.emptyList();
 			this.UNSHALLOW = newFlag("UNSHALLOW"); //$NON-NLS-1$
 			this.REINTERESTING = newFlag("REINTERESTING"); //$NON-NLS-1$
+			this.DEEPEN_NOT = newFlag("DEEPEN_NOT"); //$NON-NLS-1$
 		}
 
 		/**
@@ -128,8 +180,10 @@
 			super(or);
 
 			this.depth = depth;
+			this.deepenNots = Collections.emptyList();
 			this.UNSHALLOW = newFlag("UNSHALLOW"); //$NON-NLS-1$
 			this.REINTERESTING = newFlag("REINTERESTING"); //$NON-NLS-1$
+			this.DEEPEN_NOT = newFlag("DEEPEN_NOT"); //$NON-NLS-1$
 		}
 
 		/**
@@ -159,6 +213,39 @@
 		}
 
 		@Override
+		public int getDeepenSince() {
+			return deepenSince;
+		}
+
+		/**
+		 * Sets the deepen-since value.
+		 *
+		 * @param limit
+		 *            new deepen-since value
+		 * @since 5.2
+		 */
+		public void setDeepenSince(int limit) {
+			deepenSince = limit;
+		}
+
+		@Override
+		public List<ObjectId> getDeepenNots() {
+			return deepenNots;
+		}
+
+		/**
+		 * Mark objects that the client specified using
+		 * --shallow-exclude. Objects that are not commits have no
+		 * effect.
+		 *
+		 * @param deepenNots specified objects
+		 * @since 5.2
+		 */
+		public void setDeepenNots(List<ObjectId> deepenNots) {
+			this.deepenNots = Objects.requireNonNull(deepenNots);
+		}
+
+		@Override
 		public RevFlag getUnshallowFlag() {
 			return UNSHALLOW;
 		}
@@ -168,12 +255,19 @@
 			return REINTERESTING;
 		}
 
+		@Override
+		public RevFlag getDeepenNotFlag() {
+			return DEEPEN_NOT;
+		}
+
 		/**
 		 * @since 4.5
 		 */
 		@Override
 		public ObjectWalk toObjectWalkWithSameObjects() {
 			ObjectWalk ow = new ObjectWalk(reader, depth);
+			ow.deepenSince = deepenSince;
+			ow.deepenNots = deepenNots;
 			ow.objects = objects;
 			ow.freeFlags = freeFlags;
 			return ow;
@@ -184,10 +278,16 @@
 	public class ObjectWalk extends org.eclipse.jgit.revwalk.ObjectWalk implements DepthWalk {
 		private final int depth;
 
+		private int deepenSince;
+
+		private List<ObjectId> deepenNots;
+
 		private final RevFlag UNSHALLOW;
 
 		private final RevFlag REINTERESTING;
 
+		private final RevFlag DEEPEN_NOT;
+
 		/**
 		 * @param repo Repository to walk
 		 * @param depth Maximum depth to return
@@ -196,8 +296,10 @@
 			super(repo);
 
 			this.depth = depth;
+			this.deepenNots = Collections.emptyList();
 			this.UNSHALLOW = newFlag("UNSHALLOW"); //$NON-NLS-1$
 			this.REINTERESTING = newFlag("REINTERESTING"); //$NON-NLS-1$
+			this.DEEPEN_NOT = newFlag("DEEPEN_NOT"); //$NON-NLS-1$
 		}
 
 		/**
@@ -208,8 +310,10 @@
 			super(or);
 
 			this.depth = depth;
+			this.deepenNots = Collections.emptyList();
 			this.UNSHALLOW = newFlag("UNSHALLOW"); //$NON-NLS-1$
 			this.REINTERESTING = newFlag("REINTERESTING"); //$NON-NLS-1$
+			this.DEEPEN_NOT = newFlag("DEEPEN_NOT"); //$NON-NLS-1$
 		}
 
 		/**
@@ -263,6 +367,16 @@
 		}
 
 		@Override
+		public int getDeepenSince() {
+			return deepenSince;
+		}
+
+		@Override
+		public List<ObjectId> getDeepenNots() {
+			return deepenNots;
+		}
+
+		@Override
 		public RevFlag getUnshallowFlag() {
 			return UNSHALLOW;
 		}
@@ -271,5 +385,10 @@
 		public RevFlag getReinterestingFlag() {
 			return REINTERESTING;
 		}
+
+		@Override
+		public RevFlag getDeepenNotFlag() {
+			return DEEPEN_NOT;
+		}
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
index 86ecd8e..af4ec1f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
@@ -407,7 +407,7 @@
 	 * @return contents of the gpg signature; null if the commit was not signed.
 	 * @since 5.1
 	 */
-	public final @Nullable byte[] getRawGpgSignature() {
+	public final byte[] getRawGpgSignature() {
 		final byte[] raw = buffer;
 		final byte[] header = {'g', 'p', 'g', 's', 'i', 'g'};
 		final int start = RawParseUtils.headerStart(header, raw, 0);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
index 4d555d2..400ea33 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
@@ -54,6 +54,7 @@
 import java.util.List;
 
 import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.LargeObjectException;
@@ -1336,6 +1337,22 @@
 	}
 
 	/**
+	 * Like {@link #next()}, but if a checked exception is thrown during the
+	 * walk it is rethrown as a {@link RevWalkException}.
+	 *
+	 * @throws RevWalkException if an {@link IOException} was thrown.
+	 * @return next most recent commit; null if traversal is over.
+	 */
+	@Nullable
+	private RevCommit nextForIterator() {
+		try {
+			return next();
+		} catch (IOException e) {
+			throw new RevWalkException(e);
+		}
+	}
+
+	/**
 	 * {@inheritDoc}
 	 * <p>
 	 * Returns an Iterator over the commits of this walker.
@@ -1353,16 +1370,7 @@
 	 */
 	@Override
 	public Iterator<RevCommit> iterator() {
-		final RevCommit first;
-		try {
-			first = RevWalk.this.next();
-		} catch (MissingObjectException e) {
-			throw new RevWalkException(e);
-		} catch (IncorrectObjectTypeException e) {
-			throw new RevWalkException(e);
-		} catch (IOException e) {
-			throw new RevWalkException(e);
-		}
+		RevCommit first = nextForIterator();
 
 		return new Iterator<RevCommit>() {
 			RevCommit next = first;
@@ -1374,17 +1382,9 @@
 
 			@Override
 			public RevCommit next() {
-				try {
-					final RevCommit r = next;
-					next = RevWalk.this.next();
-					return r;
-				} catch (MissingObjectException e) {
-					throw new RevWalkException(e);
-				} catch (IncorrectObjectTypeException e) {
-					throw new RevWalkException(e);
-				} catch (IOException e) {
-					throw new RevWalkException(e);
-				}
+				RevCommit r = next;
+				next = nextForIterator();
+				return r;
 			}
 
 			@Override
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java
index 93b3baa..2b31ebd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java
@@ -57,7 +57,6 @@
 import java.io.IOException;
 import java.text.MessageFormat;
 
-import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.errors.LockFailedException;
 import org.eclipse.jgit.internal.JGitText;
@@ -281,7 +280,6 @@
 	 * @since 4.10
 	 */
 	@Override
-	@Nullable
 	protected byte[] readIncludedConfig(String relPath)
 			throws ConfigInvalidException {
 		final File file;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java
index ed05c73..72b4255 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java
@@ -55,7 +55,7 @@
 	 * {@link UploadPack#setAdvertisedRefs(java.util.Map)} and
 	 * {@link BaseReceivePack#setAdvertisedRefs(java.util.Map,java.util.Set)}.
 	 */
-	public static final AdvertiseRefsHook DEFAULT = new AdvertiseRefsHook() {
+	AdvertiseRefsHook DEFAULT = new AdvertiseRefsHook() {
 		@Override
 		public void advertiseRefs(UploadPack uploadPack) {
 			// Do nothing.
@@ -77,7 +77,7 @@
 	 * @throws org.eclipse.jgit.transport.ServiceMayNotContinueException
 	 *             abort; the message will be sent to the user.
 	 */
-	public void advertiseRefs(UploadPack uploadPack)
+	void advertiseRefs(UploadPack uploadPack)
 			throws ServiceMayNotContinueException;
 
 	/**
@@ -90,6 +90,6 @@
 	 * @throws org.eclipse.jgit.transport.ServiceMayNotContinueException
 	 *             abort; the message will be sent to the user.
 	 */
-	public void advertiseRefs(BaseReceivePack receivePack)
+	void advertiseRefs(BaseReceivePack receivePack)
 			throws ServiceMayNotContinueException;
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java
index f6ec4b9..c5661e5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java
@@ -542,7 +542,7 @@
 			}
 			buf = b.toByteArray();
 			if (buf.length > 0) {
-				err.initCause(new IOException("\n" + new String(buf))); //$NON-NLS-1$
+				err.initCause(new IOException("\n" + new String(buf, UTF_8))); //$NON-NLS-1$
 			}
 		}
 		return err;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
index d3419bc..0376336 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
@@ -72,12 +72,14 @@
 
 import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.errors.InvalidObjectIdException;
+import org.eclipse.jgit.errors.LargeObjectException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.errors.PackProtocolException;
 import org.eclipse.jgit.errors.TooLargePackException;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.internal.storage.file.PackLock;
 import org.eclipse.jgit.internal.submodule.SubmoduleValidator;
+import org.eclipse.jgit.internal.submodule.SubmoduleValidator.SubmoduleValidationException;
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.BatchRefUpdate;
 import org.eclipse.jgit.lib.Config;
@@ -1528,8 +1530,12 @@
 			AnyObjectId blobId = entry.getBlobId();
 			ObjectLoader blob = odb.open(blobId, Constants.OBJ_BLOB);
 
-			SubmoduleValidator.assertValidGitModulesFile(
-					new String(blob.getBytes(), UTF_8));
+			try {
+				SubmoduleValidator.assertValidGitModulesFile(
+						new String(blob.getBytes(), UTF_8));
+			} catch (LargeObjectException | SubmoduleValidationException e) {
+				throw new IOException(e);
+			}
 		}
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java
index d4c514e..19a1ab0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java
@@ -68,7 +68,7 @@
 	 *         modifiable. The collection can be empty if the remote side has no
 	 *         refs (it is an empty/newly created repository).
 	 */
-	public Map<String, Ref> getRefsMap();
+	Map<String, Ref> getRefsMap();
 
 	/**
 	 * Get the complete list of refs advertised as available for fetching or
@@ -82,7 +82,7 @@
 	 *         collection can be empty if the remote side has no refs (it is an
 	 *         empty/newly created repository).
 	 */
-	public Collection<Ref> getRefs();
+	Collection<Ref> getRefs();
 
 	/**
 	 * Get a single advertised ref by name.
@@ -95,7 +95,7 @@
 	 *            name of the ref to obtain.
 	 * @return the requested ref; null if the remote did not advertise this ref.
 	 */
-	public Ref getRef(String name);
+	Ref getRef(String name);
 
 	/**
 	 * {@inheritDoc}
@@ -115,7 +115,7 @@
 	 * the signature to prevent them from doing so.
 	 */
 	@Override
-	public void close();
+	void close();
 
 	/**
 	 * Get the additional messages, if any, returned by the remote process.
@@ -132,7 +132,7 @@
 	 *         newline (LF) character. The empty string is returned if the
 	 *         remote produced no additional messages.
 	 */
-	public String getMessages();
+	String getMessages();
 
 	/**
 	 * User agent advertised by the remote server.
@@ -141,5 +141,5 @@
 	 *         server does not advertise this version.
 	 * @since 4.0
 	 */
-	public String getPeerUserAgent();
+	String getPeerUserAgent();
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java
index f0c45d5..1eb7cbd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java
@@ -109,7 +109,7 @@
 	 *             protocol error, or error on remote side, or connection was
 	 *             already used for fetch.
 	 */
-	public void fetch(final ProgressMonitor monitor,
+	void fetch(final ProgressMonitor monitor,
 			final Collection<Ref> want, final Set<ObjectId> have)
 			throws TransportException;
 
@@ -151,7 +151,7 @@
 	 *             already used for fetch.
 	 * @since 3.0
 	 */
-	public void fetch(final ProgressMonitor monitor,
+	void fetch(final ProgressMonitor monitor,
 			final Collection<Ref> want, final Set<ObjectId> have,
 			OutputStream out) throws TransportException;
 
@@ -173,7 +173,7 @@
 	 * @return true if the last fetch call implicitly included tag objects;
 	 *         false if tags were not implicitly obtained.
 	 */
-	public boolean didFetchIncludeTags();
+	boolean didFetchIncludeTags();
 
 	/**
 	 * Did the last {@link #fetch(ProgressMonitor, Collection, Set)} validate
@@ -196,7 +196,7 @@
 	 *         client side in order to succeed; false if the last fetch assumed
 	 *         the remote peer supplied a complete graph.
 	 */
-	public boolean didFetchTestConnectivity();
+	boolean didFetchTestConnectivity();
 
 	/**
 	 * Set the lock message used when holding a pack out of garbage collection.
@@ -208,7 +208,7 @@
 	 *
 	 * @param message message to use when holding a pack in place.
 	 */
-	public void setPackLockMessage(String message);
+	void setPackLockMessage(String message);
 
 	/**
 	 * All locks created by the last
@@ -218,5 +218,5 @@
 	 *         fetch. The caller must release these after refs are updated in
 	 *         order to safely permit garbage collection.
 	 */
-	public Collection<PackLock> getPackLocks();
+	Collection<PackLock> getPackLocks();
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
index c43ab18..211707e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
@@ -44,6 +44,7 @@
 
 package org.eclipse.jgit.transport;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
 import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK;
 import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD;
@@ -337,7 +338,7 @@
 		try {
 			if (lock.lock()) {
 				try (Writer w = new OutputStreamWriter(
-						lock.getOutputStream())) {
+						lock.getOutputStream(), UTF_8)) {
 					for (FetchHeadRecord h : fetchHeadUpdates) {
 						h.write(w);
 						result.add(h);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchRequest.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchRequest.java
new file mode 100644
index 0000000..40ba3a3
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchRequest.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2018, Google LLC.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.transport;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.lib.ObjectId;
+
+/**
+ * Common fields between v0/v1/v2 fetch requests.
+ */
+abstract class FetchRequest {
+
+	final Set<ObjectId> wantIds;
+
+	final int depth;
+
+	final Set<ObjectId> clientShallowCommits;
+
+	final long filterBlobLimit;
+
+	final Set<String> clientCapabilities;
+
+	final int deepenSince;
+
+	final List<String> deepenNotRefs;
+
+	@Nullable
+	final String agent;
+
+	/**
+	 * Initialize the common fields of a fetch request.
+	 *
+	 * @param wantIds
+	 *            list of want ids
+	 * @param depth
+	 *            how deep to go in the tree
+	 * @param clientShallowCommits
+	 *            commits the client has without history
+	 * @param filterBlobLimit
+	 *            to exclude blobs on certain conditions
+	 * @param clientCapabilities
+	 *            capabilities sent in the request
+	 * @param deepenNotRefs
+	 *            Requests that the shallow clone/fetch should be cut at these
+	 *            specific revisions instead of a depth.
+	 * @param deepenSince
+	 *            Requests that the shallow clone/fetch should be cut at a
+	 *            specific time, instead of depth
+	 * @param agent
+	 *            agent as reported by the client in the request body
+	 */
+	FetchRequest(@NonNull Set<ObjectId> wantIds, int depth,
+			@NonNull Set<ObjectId> clientShallowCommits, long filterBlobLimit,
+			@NonNull Set<String> clientCapabilities, int deepenSince,
+			@NonNull List<String> deepenNotRefs, @Nullable String agent) {
+		this.wantIds = requireNonNull(wantIds);
+		this.depth = depth;
+		this.clientShallowCommits = requireNonNull(clientShallowCommits);
+		this.filterBlobLimit = filterBlobLimit;
+		this.clientCapabilities = requireNonNull(clientCapabilities);
+		this.deepenSince = deepenSince;
+		this.deepenNotRefs = requireNonNull(deepenNotRefs);
+		this.agent = agent;
+	}
+
+	/**
+	 * @return object ids in the "want" (and "want-ref") lines of the request
+	 */
+	@NonNull
+	Set<ObjectId> getWantIds() {
+		return wantIds;
+	}
+
+	/**
+	 * @return the depth set in a "deepen" line. 0 by default.
+	 */
+	int getDepth() {
+		return depth;
+	}
+
+	/**
+	 * Shallow commits the client already has.
+	 *
+	 * These are sent by the client in "shallow" request lines.
+	 *
+	 * @return set of commits the client has declared as shallow.
+	 */
+	@NonNull
+	Set<ObjectId> getClientShallowCommits() {
+		return clientShallowCommits;
+	}
+
+	/**
+	 * @return the blob limit set in a "filter" line (-1 if not set)
+	 */
+	long getFilterBlobLimit() {
+		return filterBlobLimit;
+	}
+
+	/**
+	 * Capabilities that the client wants enabled from the server.
+	 *
+	 * Capabilities are options that tune the expected response from the server,
+	 * like "thin-pack", "no-progress" or "ofs-delta". This list should be a
+	 * subset of the capabilities announced by the server in its first response.
+	 *
+	 * These options are listed and well-defined in the git protocol
+	 * specification.
+	 *
+	 * The agent capability is not included in this set. It can be retrieved via
+	 * {@link #getAgent()}.
+	 *
+	 * @return capabilities sent by the client (excluding the "agent"
+	 *         capability)
+	 */
+	@NonNull
+	Set<String> getClientCapabilities() {
+		return clientCapabilities;
+	}
+
+	/**
+	 * The value in a "deepen-since" line in the request, indicating the
+	 * timestamp where to stop fetching/cloning.
+	 *
+	 * @return timestamp in seconds since the epoch, where to stop the shallow
+	 *         fetch/clone. Defaults to 0 if not set in the request.
+	 */
+	int getDeepenSince() {
+		return deepenSince;
+	}
+
+	/**
+	 * @return refs received in "deepen-not" lines.
+	 */
+	@NonNull
+	List<String> getDeepenNotRefs() {
+		return deepenNotRefs;
+	}
+
+	/**
+	 * @return string identifying the agent (as sent in the request body by the
+	 *         client)
+	 */
+	@Nullable
+	String getAgent() {
+		return agent;
+	}
+}
\ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV0Request.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV0Request.java
new file mode 100644
index 0000000..05f4a81
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV0Request.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2018, Google LLC.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.transport;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.lib.ObjectId;
+
+/**
+ * Fetch request in the V0/V1 protocol.
+ */
+final class FetchV0Request extends FetchRequest {
+
+	FetchV0Request(@NonNull Set<ObjectId> wantIds, int depth,
+			@NonNull Set<ObjectId> clientShallowCommits, long filterBlobLimit,
+			@NonNull Set<String> clientCapabilities, @Nullable String agent) {
+		super(wantIds, depth, clientShallowCommits, filterBlobLimit,
+				clientCapabilities, 0, Collections.emptyList(), agent);
+	}
+
+	static final class Builder {
+
+		int depth;
+
+		final Set<ObjectId> wantIds = new HashSet<>();
+
+		final Set<ObjectId> clientShallowCommits = new HashSet<>();
+
+		long filterBlobLimit = -1;
+
+		final Set<String> clientCaps = new HashSet<>();
+
+		String agent;
+
+		/**
+		 * @param objectId
+		 *            object id received in a "want" line
+		 * @return this builder
+		 */
+		Builder addWantId(ObjectId objectId) {
+			wantIds.add(objectId);
+			return this;
+		}
+
+		/**
+		 * @param d
+		 *            depth set in a "deepen" line
+		 * @return this builder
+		 */
+		Builder setDepth(int d) {
+			depth = d;
+			return this;
+		}
+
+		/**
+		 * @param shallowOid
+		 *            object id received in a "shallow" line
+		 * @return this builder
+		 */
+		Builder addClientShallowCommit(ObjectId shallowOid) {
+			clientShallowCommits.add(shallowOid);
+			return this;
+		}
+
+		/**
+		 * @param clientCapabilities
+		 *            client capabilities sent by the client in the first want
+		 *            line of the request
+		 * @return this builder
+		 */
+		Builder addClientCapabilities(Collection<String> clientCapabilities) {
+			clientCaps.addAll(clientCapabilities);
+			return this;
+		}
+
+		/**
+		 * @param clientAgent
+		 *            agent line sent by the client in the request body
+		 * @return this builder
+		 */
+		Builder setAgent(String clientAgent) {
+			agent = clientAgent;
+			return this;
+		}
+
+		/**
+		 * @param filterBlobLim
+		 *            blob limit set in a "filter" line
+		 * @return this builder
+		 */
+		Builder setFilterBlobLimit(long filterBlobLim) {
+			filterBlobLimit = filterBlobLim;
+			return this;
+		}
+
+		FetchV0Request build() {
+			return new FetchV0Request(wantIds, depth, clientShallowCommits,
+					filterBlobLimit, clientCaps, agent);
+		}
+
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java
index 34f3484..ac6361c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java
@@ -42,70 +42,62 @@
  */
 package org.eclipse.jgit.transport;
 
+import static java.util.Objects.requireNonNull;
+
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
 import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.lib.ObjectId;
 
 /**
- * fetch protocol v2 request.
+ * Fetch request from git protocol v2.
  *
  * <p>
  * This is used as an input to {@link ProtocolV2Hook}.
  *
  * @since 5.1
  */
-public final class FetchV2Request {
+public final class FetchV2Request extends FetchRequest {
 	private final List<ObjectId> peerHas;
 
 	private final List<String> wantedRefs;
 
-	private final Set<ObjectId> wantsIds;
-
-	private final Set<ObjectId> clientShallowCommits;
-
-	private final int deepenSince;
-
-	private final List<String> deepenNotRefs;
-
-	private final int depth;
-
-	private final long filterBlobLimit;
-
-	private final Set<String> options;
-
 	private final boolean doneReceived;
 
-	private FetchV2Request(List<ObjectId> peerHas,
-			List<String> wantedRefs, Set<ObjectId> wantsIds,
-			Set<ObjectId> clientShallowCommits, int deepenSince,
-			List<String> deepenNotRefs, int depth, long filterBlobLimit,
-			boolean doneReceived, Set<String> options) {
-		this.peerHas = peerHas;
-		this.wantedRefs = wantedRefs;
-		this.wantsIds = wantsIds;
-		this.clientShallowCommits = clientShallowCommits;
-		this.deepenSince = deepenSince;
-		this.deepenNotRefs = deepenNotRefs;
-		this.depth = depth;
-		this.filterBlobLimit = filterBlobLimit;
+	@NonNull
+	private final List<String> serverOptions;
+
+	FetchV2Request(@NonNull List<ObjectId> peerHas,
+			@NonNull List<String> wantedRefs,
+			@NonNull Set<ObjectId> wantIds,
+			@NonNull Set<ObjectId> clientShallowCommits, int deepenSince,
+			@NonNull List<String> deepenNotRefs, int depth,
+			long filterBlobLimit,
+			boolean doneReceived, @NonNull Set<String> clientCapabilities,
+			@Nullable String agent, @NonNull List<String> serverOptions) {
+		super(wantIds, depth, clientShallowCommits, filterBlobLimit,
+				clientCapabilities, deepenSince, deepenNotRefs, agent);
+		this.peerHas = requireNonNull(peerHas);
+		this.wantedRefs = requireNonNull(wantedRefs);
 		this.doneReceived = doneReceived;
-		this.options = options;
+		this.serverOptions = requireNonNull(serverOptions);
 	}
 
 	/**
-	 * @return object ids in the "have" lines of the request
+	 * @return object ids received in the "have" lines
 	 */
 	@NonNull
 	List<ObjectId> getPeerHas() {
-		return this.peerHas;
+		return peerHas;
 	}
 
 	/**
-	 * @return list of references in the "want-ref" lines of the request
+	 * @return list of references received in "want-ref" lines
 	 */
 	@NonNull
 	List<String> getWantedRefs() {
@@ -113,59 +105,6 @@
 	}
 
 	/**
-	 * @return object ids in the "want" (but not "want-ref") lines of the request
-	 */
-	@NonNull
-	Set<ObjectId> getWantsIds() {
-		return wantsIds;
-	}
-
-	/**
-	 * Shallow commits the client already has.
-	 *
-	 * These are sent by the client in "shallow" request lines.
-	 *
-	 * @return set of commits the client has declared as shallow.
-	 */
-	@NonNull
-	Set<ObjectId> getClientShallowCommits() {
-		return clientShallowCommits;
-	}
-
-	/**
-	 * The value in a "deepen-since" line in the request, indicating the
-	 * timestamp where to stop fetching/cloning.
-	 *
-	 * @return timestamp in seconds since the epoch, where to stop the shallow
-	 *         fetch/clone. Defaults to 0 if not set in the request.
-	 */
-	int getDeepenSince() {
-		return deepenSince;
-	}
-
-	/**
-	 * @return the refs in "deepen-not" lines in the request.
-	 */
-	@NonNull
-	List<String> getDeepenNotRefs() {
-		return deepenNotRefs;
-	}
-
-	/**
-	 * @return the depth set in a "deepen" line. 0 by default.
-	 */
-	int getDepth() {
-		return depth;
-	}
-
-	/**
-	 * @return the blob limit set in a "filter" line (-1 if not set)
-	 */
-	long getFilterBlobLimit() {
-		return filterBlobLimit;
-	}
-
-	/**
 	 * @return true if the request had a "done" line
 	 */
 	boolean wasDoneReceived() {
@@ -173,17 +112,16 @@
 	}
 
 	/**
-	 * Options that tune the expected response from the server, like
-	 * "thin-pack", "no-progress" or "ofs-delta"
+	 * Options received in server-option lines. The caller can choose to act on
+	 * these in an application-specific way
 	 *
-	 * These are options listed and well-defined in the git protocol
-	 * specification
+	 * @return Immutable list of server options received in the request
 	 *
-	 * @return options found in the request lines
+	 * @since 5.2
 	 */
 	@NonNull
-	Set<String> getOptions() {
-		return options;
+	public List<String> getServerOptions() {
+		return serverOptions;
 	}
 
 	/** @return A builder of {@link FetchV2Request}. */
@@ -191,20 +129,19 @@
 		return new Builder();
 	}
 
-
 	/** A builder for {@link FetchV2Request}. */
 	static final class Builder {
-		List<ObjectId> peerHas = new ArrayList<>();
+		final List<ObjectId> peerHas = new ArrayList<>();
 
-		List<String> wantedRefs = new ArrayList<>();
+		final List<String> wantedRefs = new ArrayList<>();
 
-		Set<ObjectId> wantsIds = new HashSet<>();
+		final Set<ObjectId> wantIds = new HashSet<>();
 
-		Set<ObjectId> clientShallowCommits = new HashSet<>();
+		final Set<ObjectId> clientShallowCommits = new HashSet<>();
 
-		List<String> deepenNotRefs = new ArrayList<>();
+		final List<String> deepenNotRefs = new ArrayList<>();
 
-		Set<String> options = new HashSet<>();
+		final Set<String> clientCapabilities = new HashSet<>();
 
 		int depth;
 
@@ -214,13 +151,18 @@
 
 		boolean doneReceived;
 
+		@Nullable
+		String agent;
+
+		final List<String> serverOptions = new ArrayList<>();
+
 		private Builder() {
 		}
 
 		/**
 		 * @param objectId
-		 *            from a "have" line in a fetch request
-		 * @return the builder
+		 *            object id received in a "have" line
+		 * @return this builder
 		 */
 		Builder addPeerHas(ObjectId objectId) {
 			peerHas.add(objectId);
@@ -228,11 +170,11 @@
 		}
 
 		/**
-		 * From a "want-ref" line in a fetch request
+		 * Ref received in "want-ref" line and the object-id it refers to
 		 *
 		 * @param refName
 		 *            reference name
-		 * @return the builder
+		 * @return this builder
 		 */
 		Builder addWantedRef(String refName) {
 			wantedRefs.add(refName);
@@ -240,42 +182,42 @@
 		}
 
 		/**
-		 * @param option
-		 *            fetch request lines acting as options
-		 * @return the builder
+		 * @param clientCapability
+		 *            capability line sent by the client
+		 * @return this builder
 		 */
-		Builder addOption(String option) {
-			options.add(option);
+		Builder addClientCapability(String clientCapability) {
+			clientCapabilities.add(clientCapability);
 			return this;
 		}
 
 		/**
-		 * @param objectId
-		 *            from a "want" line in a fetch request
-		 * @return the builder
+		 * @param wantId
+		 *            object id received in a "want" line
+		 * @return this builder
 		 */
-		Builder addWantsId(ObjectId objectId) {
-			wantsIds.add(objectId);
+		Builder addWantId(ObjectId wantId) {
+			wantIds.add(wantId);
 			return this;
 		}
 
 		/**
 		 * @param shallowOid
-		 *            from a "shallow" line in the fetch request
-		 * @return the builder
+		 *            object id received in a "shallow" line
+		 * @return this builder
 		 */
 		Builder addClientShallowCommit(ObjectId shallowOid) {
-			this.clientShallowCommits.add(shallowOid);
+			clientShallowCommits.add(shallowOid);
 			return this;
 		}
 
 		/**
 		 * @param d
-		 *            from a "deepen" line in the fetch request
-		 * @return the builder
+		 *            Depth received in a "deepen" line
+		 * @return this builder
 		 */
 		Builder setDepth(int d) {
-			this.depth = d;
+			depth = d;
 			return this;
 		}
 
@@ -284,32 +226,34 @@
 		 *         0 if not set.
 		 */
 		int getDepth() {
-			return this.depth;
+			return depth;
 		}
 
 		/**
-		 * @return if there has been any "deepen not" line in the request
+		 * @return true if there has been at least one "deepen not" line in the
+		 *         request so far
 		 */
 		boolean hasDeepenNotRefs() {
 			return !deepenNotRefs.isEmpty();
 		}
 
 		/**
-		 * @param deepenNotRef reference in a "deepen not" line
-		 * @return the builder
+		 * @param deepenNotRef
+		 *            reference received in a "deepen not" line
+		 * @return this builder
 		 */
 		Builder addDeepenNotRef(String deepenNotRef) {
-			this.deepenNotRefs.add(deepenNotRef);
+			deepenNotRefs.add(deepenNotRef);
 			return this;
 		}
 
 		/**
 		 * @param value
 		 *            Unix timestamp received in a "deepen since" line
-		 * @return the builder
+		 * @return this builder
 		 */
 		Builder setDeepenSince(int value) {
-			this.deepenSince = value;
+			deepenSince = value;
 			return this;
 		}
 
@@ -318,35 +262,66 @@
 		 *         by default.
 		 */
 		int getDeepenSince() {
-			return this.deepenSince;
+			return deepenSince;
 		}
 
 		/**
-		 * @param filterBlobLimit
+		 * @param filterBlobLim
 		 *            set in a "filter" line
-		 * @return the builder
+		 * @return this builder
 		 */
-		Builder setFilterBlobLimit(long filterBlobLimit) {
-			this.filterBlobLimit = filterBlobLimit;
+		Builder setFilterBlobLimit(long filterBlobLim) {
+			filterBlobLimit = filterBlobLim;
 			return this;
 		}
 
 		/**
 		 * Mark that the "done" line has been received.
 		 *
-		 * @return the builder
+		 * @return this builder
 		 */
 		Builder setDoneReceived() {
-			this.doneReceived = true;
+			doneReceived = true;
 			return this;
 		}
+
+		/**
+		 * Value of an agent line received after the command and before the
+		 * arguments. E.g. "agent=a.b.c/1.0" should set "a.b.c/1.0".
+		 *
+		 * @param agentValue
+		 *            the client-supplied agent capability, without the leading
+		 *            "agent="
+		 * @return this builder
+		 */
+		Builder setAgent(@Nullable String agentValue) {
+			agent = agentValue;
+			return this;
+		}
+
+		/**
+		 * Records an application-specific option supplied in a server-option
+		 * line, for later retrieval with
+		 * {@link FetchV2Request#getServerOptions}.
+		 *
+		 * @param value
+		 *            the client-supplied server-option capability, without
+		 *            leading "server-option=".
+		 * @return this builder
+		 */
+		Builder addServerOption(@NonNull String value) {
+			serverOptions.add(value);
+			return this;
+		}
+
 		/**
 		 * @return Initialized fetch request
 		 */
 		FetchV2Request build() {
-			return new FetchV2Request(peerHas, wantedRefs, wantsIds,
+			return new FetchV2Request(peerHas, wantedRefs, wantIds,
 					clientShallowCommits, deepenSince, deepenNotRefs,
-					depth, filterBlobLimit, doneReceived, options);
+					depth, filterBlobLimit, doneReceived, clientCapabilities,
+					agent, Collections.unmodifiableList(serverOptions));
 		}
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FtpChannel.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FtpChannel.java
new file mode 100644
index 0000000..39e87b2
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FtpChannel.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.transport;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An interface providing FTP operations over a {@link RemoteSession}. All
+ * operations are supposed to throw {@link FtpException} for remote file system
+ * errors and other IOExceptions on connection errors.
+ *
+ * @since 5.2
+ */
+public interface FtpChannel {
+
+	/**
+	 * An {@link Exception} for reporting SFTP errors.
+	 */
+	static class FtpException extends IOException {
+
+		private static final long serialVersionUID = 7176525179280330876L;
+
+		public static final int OK = 0;
+
+		public static final int EOF = 1;
+
+		public static final int NO_SUCH_FILE = 2;
+
+		public static final int NO_PERMISSION = 3;
+
+		public static final int UNSPECIFIED_FAILURE = 4;
+
+		public static final int PROTOCOL_ERROR = 5;
+
+		public static final int UNSUPPORTED = 8;
+
+		private final int status;
+
+		public FtpException(String message, int status) {
+			super(message);
+			this.status = status;
+		}
+
+		public FtpException(String message, int status, Throwable cause) {
+			super(message, cause);
+			this.status = status;
+		}
+
+		public int getStatus() {
+			return status;
+		}
+	}
+
+	/**
+	 * Connects the {@link FtpChannel} to the remote end.
+	 *
+	 * @param timeout
+	 *            for establishing the FTP connection
+	 * @param unit
+	 *            of the {@code timeout}
+	 * @throws IOException
+	 */
+	void connect(int timeout, TimeUnit unit) throws IOException;
+
+	/**
+	 * Disconnects and {@link FtpChannel}.
+	 */
+	void disconnect();
+
+	/**
+	 * @return whether the {@link FtpChannel} is connected
+	 */
+	boolean isConnected();
+
+	/**
+	 * Changes the current remote directory.
+	 *
+	 * @param path
+	 *            target directory
+	 * @throws IOException
+	 *             if the operation could not be performed remotely
+	 */
+	void cd(String path) throws IOException;
+
+	/**
+	 * @return the current remote directory path
+	 * @throws IOException
+	 */
+	String pwd() throws IOException;
+
+	/**
+	 * Simplified remote directory entry.
+	 */
+	interface DirEntry {
+		String getFilename();
+
+		long getModifiedTime();
+
+		boolean isDirectory();
+	}
+
+	/**
+	 * Lists contents of a remote directory
+	 *
+	 * @param path
+	 *            of the directory to list
+	 * @return the directory entries
+	 * @throws IOException
+	 */
+	Collection<DirEntry> ls(String path) throws IOException;
+
+	/**
+	 * Deletes a directory on the remote file system. The directory must be
+	 * empty.
+	 *
+	 * @param path
+	 *            to delete
+	 * @throws IOException
+	 */
+	void rmdir(String path) throws IOException;
+
+	/**
+	 * Creates a directory on the remote file system.
+	 *
+	 * @param path
+	 *            to create
+	 * @throws IOException
+	 */
+	void mkdir(String path) throws IOException;
+
+	/**
+	 * Obtain an {@link InputStream} to read the contents of a remote file.
+	 *
+	 * @param path
+	 *            of the file to read
+	 *
+	 * @return the stream to read from
+	 * @throws IOException
+	 */
+	InputStream get(String path) throws IOException;
+
+	/**
+	 * Obtain an {@link OutputStream} to write to a remote file. If the file
+	 * exists already, it will be overwritten.
+	 *
+	 * @param path
+	 *            of the file to read
+	 *
+	 * @return the stream to read from
+	 * @throws IOException
+	 */
+	OutputStream put(String path) throws IOException;
+
+	/**
+	 * Deletes a file on the remote file system.
+	 *
+	 * @param path
+	 *            to delete
+	 * @throws IOException
+	 *             if the file does not exist or could otherwise not be deleted
+	 */
+	void rm(String path) throws IOException;
+
+	/**
+	 * Deletes a file on the remote file system. If the file does not exist, no
+	 * exception is thrown.
+	 *
+	 * @param path
+	 *            to delete
+	 * @throws IOException
+	 *             if the file exist but could not be deleted
+	 */
+	default void delete(String path) throws IOException {
+		try {
+			rm(path);
+		} catch (FileNotFoundException e) {
+			// Ignore; it's OK if the file doesn't exist
+		} catch (FtpException f) {
+			if (f.getStatus() == FtpException.NO_SUCH_FILE) {
+				return;
+			}
+			throw f;
+		}
+	}
+
+	/**
+	 * Renames a file on the remote file system. If {@code to} exists, it is
+	 * replaced by {@code from}. (POSIX rename() semantics)
+	 *
+	 * @param from
+	 *            original name of the file
+	 * @param to
+	 *            new name of the file
+	 * @throws IOException
+	 * @see <a href=
+	 *      "http://pubs.opengroup.org/onlinepubs/9699919799/functions/rename.html">stdio.h:
+	 *      rename()</a>
+	 */
+	void rename(String from, String to) throws IOException;
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
index 760ac6c..1561c93 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
@@ -245,6 +245,20 @@
 	public static final String CAPABILITY_REF_IN_WANT = "ref-in-want"; //$NON-NLS-1$
 
 	/**
+	 * The server supports arbitrary options
+	 *
+	 * @since 5.2
+	 */
+	public static final String CAPABILITY_SERVER_OPTION = "server-option"; //$NON-NLS-1$
+
+	/**
+	 * Option for passing application-specific options to the server.
+	 *
+	 * @since 5.2
+	 */
+	public static final String OPTION_SERVER_OPTION = "server-option"; //$NON-NLS-1$
+
+	/**
 	 * The server supports listing refs using protocol v2.
 	 *
 	 * @since 5.0
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalPushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalPushConnection.java
index 732be63..f05e0b8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalPushConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalPushConnection.java
@@ -46,6 +46,7 @@
 import java.io.IOException;
 import java.io.PipedInputStream;
 import java.io.PipedOutputStream;
+import java.io.UncheckedIOException;
 
 import org.eclipse.jgit.errors.TransportException;
 import org.eclipse.jgit.internal.JGitText;
@@ -103,10 +104,13 @@
 					// Ignored. Client cannot use this repository.
 				} catch (ServiceNotAuthorizedException e) {
 					// Ignored. Client cannot use this repository.
-				} catch (IOException err) {
-					// Client side of the pipes should report the problem.
-				} catch (RuntimeException err) {
-					// Clients side will notice we went away, and report.
+				} catch (IOException e) {
+					// Since the InternalPushConnection
+					// is used in tests, we want to avoid hiding exceptions
+					// because they can point to programming errors on the server
+					// side. By rethrowing, the default handler will dump it
+					// to stderr.
+					throw new UncheckedIOException(e);
 				} finally {
 					try {
 						out_r.close();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java
index 4e712a5..0bdd6ba 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java
@@ -52,7 +52,6 @@
 
 import static java.util.stream.Collectors.joining;
 import static java.util.stream.Collectors.toList;
-import static org.eclipse.jgit.transport.OpenSshConfig.SSH_PORT;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -275,10 +274,11 @@
 	}
 
 	private static String hostName(Session s) {
-		if (s.getPort() == SSH_PORT) {
+		if (s.getPort() == SshConstants.SSH_DEFAULT_PORT) {
 			return s.getHost();
 		}
-		return String.format("[%s]:%d", s.getHost(), s.getPort()); //$NON-NLS-1$
+		return String.format("[%s]:%d", s.getHost(), //$NON-NLS-1$
+				Integer.valueOf(s.getPort()));
 	}
 
 	private void copyConfigValueToSession(Session session, Config cfg,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java
index e3ef832..843b90c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java
@@ -52,6 +52,11 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jgit.errors.TransportException;
 import org.eclipse.jgit.internal.JGitText;
@@ -59,8 +64,10 @@
 
 import com.jcraft.jsch.Channel;
 import com.jcraft.jsch.ChannelExec;
+import com.jcraft.jsch.ChannelSftp;
 import com.jcraft.jsch.JSchException;
 import com.jcraft.jsch.Session;
+import com.jcraft.jsch.SftpException;
 
 /**
  * Run remote commands using Jsch.
@@ -109,12 +116,24 @@
 	 * @return a channel suitable for Sftp operations.
 	 * @throws com.jcraft.jsch.JSchException
 	 *             on problems getting the channel.
+	 * @deprecated since 5.2; use {@link #getFtpChannel()} instead
 	 */
+	@Deprecated
 	public Channel getSftpChannel() throws JSchException {
 		return sock.openChannel("sftp"); //$NON-NLS-1$
 	}
 
 	/**
+	 * {@inheritDoc}
+	 *
+	 * @since 5.2
+	 */
+	@Override
+	public FtpChannel getFtpChannel() {
+		return new JschFtpChannel();
+	}
+
+	/**
 	 * Implementation of Process for running a single command using Jsch.
 	 * <p>
 	 * Uses the Jsch session to do actual command execution and manage the
@@ -233,4 +252,154 @@
 			return exitValue();
 		}
 	}
+
+	private class JschFtpChannel implements FtpChannel {
+
+		private ChannelSftp ftp;
+
+		@Override
+		public void connect(int timeout, TimeUnit unit) throws IOException {
+			try {
+				ftp = (ChannelSftp) sock.openChannel("sftp"); //$NON-NLS-1$
+				ftp.connect((int) unit.toMillis(timeout));
+			} catch (JSchException e) {
+				ftp = null;
+				throw new IOException(e.getLocalizedMessage(), e);
+			}
+		}
+
+		@Override
+		public void disconnect() {
+			ftp.disconnect();
+			ftp = null;
+		}
+
+		private <T> T map(Callable<T> op) throws IOException {
+			try {
+				return op.call();
+			} catch (Exception e) {
+				if (e instanceof SftpException) {
+					throw new FtpChannel.FtpException(e.getLocalizedMessage(),
+							((SftpException) e).id, e);
+				}
+				throw new IOException(e.getLocalizedMessage(), e);
+			}
+		}
+
+		@Override
+		public boolean isConnected() {
+			return ftp != null && sock.isConnected();
+		}
+
+		@Override
+		public void cd(String path) throws IOException {
+			map(() -> {
+				ftp.cd(path);
+				return null;
+			});
+		}
+
+		@Override
+		public String pwd() throws IOException {
+			return map(() -> ftp.pwd());
+		}
+
+		@Override
+		public Collection<DirEntry> ls(String path) throws IOException {
+			return map(() -> {
+				List<DirEntry> result = new ArrayList<>();
+				for (Object e : ftp.ls(path)) {
+					ChannelSftp.LsEntry entry = (ChannelSftp.LsEntry) e;
+					result.add(new DirEntry() {
+
+						@Override
+						public String getFilename() {
+							return entry.getFilename();
+						}
+
+						@Override
+						public long getModifiedTime() {
+							return entry.getAttrs().getMTime();
+						}
+
+						@Override
+						public boolean isDirectory() {
+							return entry.getAttrs().isDir();
+						}
+					});
+				}
+				return result;
+			});
+		}
+
+		@Override
+		public void rmdir(String path) throws IOException {
+			map(() -> {
+				ftp.rm(path);
+				return null;
+			});
+		}
+
+		@Override
+		public void mkdir(String path) throws IOException {
+			map(() -> {
+				ftp.mkdir(path);
+				return null;
+			});
+		}
+
+		@Override
+		public InputStream get(String path) throws IOException {
+			return map(() -> ftp.get(path));
+		}
+
+		@Override
+		public OutputStream put(String path) throws IOException {
+			return map(() -> ftp.put(path));
+		}
+
+		@Override
+		public void rm(String path) throws IOException {
+			map(() -> {
+				ftp.rm(path);
+				return null;
+			});
+		}
+
+		@Override
+		public void rename(String from, String to) throws IOException {
+			map(() -> {
+				// Plain FTP rename will fail if "to" exists. Jsch knows about
+				// the FTP extension "posix-rename@openssh.com", which will
+				// remove "to" first if it exists.
+				if (hasPosixRename()) {
+					ftp.rename(from, to);
+				} else if (!to.equals(from)) {
+					// Try to remove "to" first. With git, we typically get this
+					// when a lock file is moved over the file locked. Note that
+					// the check for to being equal to from may still fail in
+					// the general case, but for use with JGit's TransportSftp
+					// it should be good enough.
+					delete(to);
+					ftp.rename(from, to);
+				}
+				return null;
+			});
+		}
+
+		/**
+		 * Determine whether the server has the posix-rename extension.
+		 *
+		 * @return {@code true} if it is supported, {@code false} otherwise
+		 * @see <a href=
+		 *      "https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL?annotate=HEAD">OpenSSH
+		 *      deviations and extensions to the published SSH protocol</a>
+		 * @see <a href=
+		 *      "http://pubs.opengroup.org/onlinepubs/9699919799/functions/rename.html">stdio.h:
+		 *      rename()</a>
+		 */
+		private boolean hasPosixRename() {
+			return "1".equals(ftp.getExtension("posix-rename@openssh.com")); //$NON-NLS-1$//$NON-NLS-2$
+		}
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/LsRefsV2Request.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/LsRefsV2Request.java
index 3aff584..add3731 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/LsRefsV2Request.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/LsRefsV2Request.java
@@ -42,9 +42,15 @@
  */
 package org.eclipse.jgit.transport;
 
+import static java.util.Objects.requireNonNull;
+
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
+
 /**
  * ls-refs protocol v2 request.
  *
@@ -60,11 +66,20 @@
 
 	private final boolean peel;
 
+	@Nullable
+	private final String agent;
+
+	@NonNull
+	private final List<String> serverOptions;
+
 	private LsRefsV2Request(List<String> refPrefixes, boolean symrefs,
-			boolean peel) {
+			boolean peel, @Nullable String agent,
+			@NonNull List<String> serverOptions) {
 		this.refPrefixes = refPrefixes;
 		this.symrefs = symrefs;
 		this.peel = peel;
+		this.agent = agent;
+		this.serverOptions = requireNonNull(serverOptions);
 	}
 
 	/** @return ref prefixes that the client requested. */
@@ -82,6 +97,34 @@
 		return peel;
 	}
 
+	/**
+	 * @return agent as reported by the client
+	 *
+	 * @since 5.2
+	 */
+	@Nullable
+	public String getAgent() {
+		return agent;
+	}
+
+	/**
+	 * Get application-specific options provided by the client using
+	 * --server-option.
+	 * <p>
+	 * It returns just the content, without the "server-option=" prefix. E.g. a
+	 * request with server-option=A and server-option=B lines returns the list
+	 * [A, B].
+	 *
+	 * @return application-specific options from the client as an unmodifiable
+	 *         list
+	 *
+	 * @since 5.2
+	 */
+	@NonNull
+	public List<String> getServerOptions() {
+		return serverOptions;
+	}
+
 	/** @return A builder of {@link LsRefsV2Request}. */
 	public static Builder builder() {
 		return new Builder();
@@ -95,6 +138,10 @@
 
 		private boolean peel;
 
+		private final List<String> serverOptions = new ArrayList<>();
+
+		private String agent;
+
 		private Builder() {
 		}
 
@@ -125,10 +172,43 @@
 			return this;
 		}
 
+		/**
+		 * Records an application-specific option supplied in a server-option
+		 * line, for later retrieval with
+		 * {@link LsRefsV2Request#getServerOptions}.
+		 *
+		 * @param value
+		 *            the client-supplied server-option capability, without
+		 *            leading "server-option=".
+		 * @return this builder
+		 * @since 5.2
+		 */
+		public Builder addServerOption(@NonNull String value) {
+			serverOptions.add(value);
+			return this;
+		}
+
+		/**
+		 * Value of an agent line received after the command and before the
+		 * arguments. E.g. "agent=a.b.c/1.0" should set "a.b.c/1.0".
+		 *
+		 * @param value
+		 *            the client-supplied agent capability, without leading
+		 *            "agent="
+		 * @return this builder
+		 *
+		 * @since 5.2
+		 */
+		public Builder setAgent(@Nullable String value) {
+			agent = value;
+			return this;
+		}
+
 		/** @return LsRefsV2Request */
 		public LsRefsV2Request build() {
 			return new LsRefsV2Request(
-					Collections.unmodifiableList(refPrefixes), symrefs, peel);
+					Collections.unmodifiableList(refPrefixes), symrefs, peel,
+					agent, Collections.unmodifiableList(serverOptions));
 		}
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java
index e688f63..8562376 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java
@@ -42,10 +42,13 @@
 
 package org.eclipse.jgit.transport;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.BufferedReader;
 import java.io.File;
-import java.io.FileReader;
+import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStreamReader;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Locale;
@@ -211,7 +214,8 @@
 		this.hosts.clear();
 		this.lastModified = this.netrc.lastModified();
 
-		try (BufferedReader r = new BufferedReader(new FileReader(netrc))) {
+		try (BufferedReader r = new BufferedReader(
+				new InputStreamReader(new FileInputStream(netrc), UTF_8))) {
 			String line = null;
 
 			NetRCEntry entry = new NetRCEntry();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NonceGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NonceGenerator.java
index 51fe907..fc22034 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NonceGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NonceGenerator.java
@@ -66,7 +66,7 @@
 	 * @return The nonce to be signed by the pusher
 	 * @throws java.lang.IllegalStateException
 	 */
-	public String createNonce(Repository db, long timestamp)
+	String createNonce(Repository db, long timestamp)
 			throws IllegalStateException;
 
 	/**
@@ -91,6 +91,6 @@
 	 * @return a NonceStatus indicating the trustworthiness of the received
 	 *         nonce.
 	 */
-	public NonceStatus verify(String received, String sent,
+	NonceStatus verify(String received, String sent,
 			Repository db, boolean allowSlop, int slop);
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java
index 480055c..32e1dff 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008, 2017, Google Inc.
+ * Copyright (C) 2008, 2018, Google Inc.
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -43,29 +43,16 @@
 
 package org.eclipse.jgit.transport;
 
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
+import static org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile.positive;
 
-import org.eclipse.jgit.errors.InvalidPatternException;
-import org.eclipse.jgit.fnmatch.FileNameMatcher;
-import org.eclipse.jgit.lib.Constants;
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile;
+import org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile.HostEntry;
 import org.eclipse.jgit.util.FS;
-import org.eclipse.jgit.util.StringUtils;
-import org.eclipse.jgit.util.SystemReader;
 
 import com.jcraft.jsch.ConfigRepository;
 
@@ -83,8 +70,7 @@
  * <li>JSch's OpenSSHConfig doesn't monitor for config file changes.
  * </ul>
  * <p>
- * Therefore implement our own parser to read an OpenSSH configuration file. It
- * makes the critical options available to
+ * This parser makes the critical options available to
  * {@link org.eclipse.jgit.transport.SshSessionFactory} via
  * {@link org.eclipse.jgit.transport.OpenSshConfig.Host} objects returned by
  * {@link #lookup(String)}, and implements a fully conforming
@@ -92,49 +78,11 @@
  * {@link com.jcraft.jsch.ConfigRepository.Config}s via
  * {@link #getConfig(String)}.
  * </p>
- * <p>
- * Limitations compared to the full OpenSSH 7.5 parser:
- * </p>
- * <ul>
- * <li>This parser does not handle Match or Include keywords.
- * <li>This parser does not do host name canonicalization (Jsch ignores it
- * anyway).
- * </ul>
- * <p>
- * Note that OpenSSH's readconf.c is a validating parser; Jsch's
- * ConfigRepository OTOH treats all option values as plain strings, so any
- * validation must happen in Jsch outside of the parser. Thus this parser does
- * not validate option values, except for a few options when constructing a
- * {@link org.eclipse.jgit.transport.OpenSshConfig.Host} object.
- * </p>
- * <p>
- * This config does %-substitutions for the following tokens:
- * </p>
- * <ul>
- * <li>%% - single %
- * <li>%C - short-hand for %l%h%p%r. See %p and %r below; the replacement may be
- * done partially only and may leave %p or %r or both unreplaced.
- * <li>%d - home directory path
- * <li>%h - remote host name
- * <li>%L - local host name without domain
- * <li>%l - FQDN of the local host
- * <li>%n - host name as specified in {@link #lookup(String)}
- * <li>%p - port number; replaced only if set in the config
- * <li>%r - remote user name; replaced only if set in the config
- * <li>%u - local user name
- * </ul>
- * <p>
- * If the config doesn't set the port or the remote user name, %p and %r remain
- * un-substituted. It's the caller's responsibility to replace them with values
- * obtained from the connection URI. %i is not handled; Java has no concept of a
- * "user ID".
- * </p>
+ *
+ * @see OpenSshConfigFile
  */
 public class OpenSshConfig implements ConfigRepository {
 
-	/** IANA assigned port number for SSH. */
-	static final int SSH_PORT = 22;
-
 	/**
 	 * Obtain the user's configuration data.
 	 * <p>
@@ -153,43 +101,17 @@
 		if (home == null)
 			home = new File(".").getAbsoluteFile(); //$NON-NLS-1$
 
-		final File config = new File(new File(home, ".ssh"), Constants.CONFIG); //$NON-NLS-1$
-		final OpenSshConfig osc = new OpenSshConfig(home, config);
-		osc.refresh();
-		return osc;
+		final File config = new File(new File(home, SshConstants.SSH_DIR),
+				SshConstants.CONFIG);
+		return new OpenSshConfig(home, config);
 	}
 
-	/** The user's home directory, as key files may be relative to here. */
-	private final File home;
-
-	/** The .ssh/config file we read and monitor for updates. */
-	private final File configFile;
-
-	/** Modification time of {@link #configFile} when it was last loaded. */
-	private long lastModified;
-
-	/**
-	 * Encapsulates entries read out of the configuration file, and
-	 * {@link Host}s created from that.
-	 */
-	private static class State {
-		Map<String, HostEntry> entries = new LinkedHashMap<>();
-		Map<String, Host> hosts = new HashMap<>();
-
-		@Override
-		@SuppressWarnings("nls")
-		public String toString() {
-			return "State [entries=" + entries + ", hosts=" + hosts + "]";
-		}
-	}
-
-	/** State read from the config file, plus {@link Host}s created from it. */
-	private State state;
+	/** The base file. */
+	private OpenSshConfigFile configFile;
 
 	OpenSshConfig(File h, File cfg) {
-		home = h;
-		configFile = cfg;
-		state = new State();
+		configFile = new OpenSshConfigFile(h, cfg,
+				SshSessionFactory.getLocalUserName());
 	}
 
 	/**
@@ -202,603 +124,8 @@
 	 * @return r configuration for the requested name. Never null.
 	 */
 	public Host lookup(String hostName) {
-		final State cache = refresh();
-		Host h = cache.hosts.get(hostName);
-		if (h != null) {
-			return h;
-		}
-		HostEntry fullConfig = new HostEntry();
-		// Initialize with default entries at the top of the file, before the
-		// first Host block.
-		fullConfig.merge(cache.entries.get(HostEntry.DEFAULT_NAME));
-		for (Map.Entry<String, HostEntry> e : cache.entries.entrySet()) {
-			String key = e.getKey();
-			if (isHostMatch(key, hostName)) {
-				fullConfig.merge(e.getValue());
-			}
-		}
-		fullConfig.substitute(hostName, home);
-		h = new Host(fullConfig, hostName, home);
-		cache.hosts.put(hostName, h);
-		return h;
-	}
-
-	private synchronized State refresh() {
-		final long mtime = configFile.lastModified();
-		if (mtime != lastModified) {
-			State newState = new State();
-			try (FileInputStream in = new FileInputStream(configFile)) {
-				newState.entries = parse(in);
-			} catch (IOException none) {
-				// Ignore -- we'll set and return an empty state
-			}
-			lastModified = mtime;
-			state = newState;
-		}
-		return state;
-	}
-
-	private Map<String, HostEntry> parse(InputStream in)
-			throws IOException {
-		final Map<String, HostEntry> m = new LinkedHashMap<>();
-		final BufferedReader br = new BufferedReader(new InputStreamReader(in));
-		final List<HostEntry> current = new ArrayList<>(4);
-		String line;
-
-		// The man page doesn't say so, but the OpenSSH parser (readconf.c)
-		// starts out in active mode and thus always applies any lines that
-		// occur before the first host block. We gather those options in a
-		// HostEntry for DEFAULT_NAME.
-		HostEntry defaults = new HostEntry();
-		current.add(defaults);
-		m.put(HostEntry.DEFAULT_NAME, defaults);
-
-		while ((line = br.readLine()) != null) {
-			line = line.trim();
-			if (line.isEmpty() || line.startsWith("#")) { //$NON-NLS-1$
-				continue;
-			}
-			String[] parts = line.split("[ \t]*[= \t]", 2); //$NON-NLS-1$
-			// Although the ssh-config man page doesn't say so, the OpenSSH
-			// parser does allow quoted keywords.
-			String keyword = dequote(parts[0].trim());
-			// man 5 ssh-config says lines had the format "keyword arguments",
-			// with no indication that arguments were optional. However, let's
-			// not crap out on missing arguments. See bug 444319.
-			String argValue = parts.length > 1 ? parts[1].trim() : ""; //$NON-NLS-1$
-
-			if (StringUtils.equalsIgnoreCase("Host", keyword)) { //$NON-NLS-1$
-				current.clear();
-				for (String name : HostEntry.parseList(argValue)) {
-					if (name == null || name.isEmpty()) {
-						// null should not occur, but better be safe than sorry.
-						continue;
-					}
-					HostEntry c = m.get(name);
-					if (c == null) {
-						c = new HostEntry();
-						m.put(name, c);
-					}
-					current.add(c);
-				}
-				continue;
-			}
-
-			if (current.isEmpty()) {
-				// We received an option outside of a Host block. We
-				// don't know who this should match against, so skip.
-				continue;
-			}
-
-			if (HostEntry.isListKey(keyword)) {
-				List<String> args = HostEntry.parseList(argValue);
-				for (HostEntry entry : current) {
-					entry.setValue(keyword, args);
-				}
-			} else if (!argValue.isEmpty()) {
-				argValue = dequote(argValue);
-				for (HostEntry entry : current) {
-					entry.setValue(keyword, argValue);
-				}
-			}
-		}
-
-		return m;
-	}
-
-	private static boolean isHostMatch(final String pattern,
-			final String name) {
-		if (pattern.startsWith("!")) { //$NON-NLS-1$
-			return !patternMatchesHost(pattern.substring(1), name);
-		} else {
-			return patternMatchesHost(pattern, name);
-		}
-	}
-
-	private static boolean patternMatchesHost(final String pattern,
-			final String name) {
-		if (pattern.indexOf('*') >= 0 || pattern.indexOf('?') >= 0) {
-			final FileNameMatcher fn;
-			try {
-				fn = new FileNameMatcher(pattern, null);
-			} catch (InvalidPatternException e) {
-				return false;
-			}
-			fn.append(name);
-			return fn.isMatch();
-		} else {
-			// Not a pattern but a full host name
-			return pattern.equals(name);
-		}
-	}
-
-	private static String dequote(String value) {
-		if (value.startsWith("\"") && value.endsWith("\"") //$NON-NLS-1$ //$NON-NLS-2$
-				&& value.length() > 1)
-			return value.substring(1, value.length() - 1);
-		return value;
-	}
-
-	private static String nows(String value) {
-		final StringBuilder b = new StringBuilder();
-		for (int i = 0; i < value.length(); i++) {
-			if (!Character.isSpaceChar(value.charAt(i)))
-				b.append(value.charAt(i));
-		}
-		return b.toString();
-	}
-
-	private static Boolean yesno(String value) {
-		if (StringUtils.equalsIgnoreCase("yes", value)) //$NON-NLS-1$
-			return Boolean.TRUE;
-		return Boolean.FALSE;
-	}
-
-	private static File toFile(String path, File home) {
-		if (path.startsWith("~/")) { //$NON-NLS-1$
-			return new File(home, path.substring(2));
-		}
-		File ret = new File(path);
-		if (ret.isAbsolute()) {
-			return ret;
-		}
-		return new File(home, path);
-	}
-
-	private static int positive(String value) {
-		if (value != null) {
-			try {
-				return Integer.parseUnsignedInt(value);
-			} catch (NumberFormatException e) {
-				// Ignore
-			}
-		}
-		return -1;
-	}
-
-	static String userName() {
-		return AccessController.doPrivileged(new PrivilegedAction<String>() {
-			@Override
-			public String run() {
-				return SystemReader.getInstance()
-						.getProperty(Constants.OS_USER_NAME_KEY);
-			}
-		});
-	}
-
-	private static class HostEntry implements ConfigRepository.Config {
-
-		/**
-		 * "Host name" of the HostEntry for the default options before the first
-		 * host block in a config file.
-		 */
-		public static final String DEFAULT_NAME = ""; //$NON-NLS-1$
-
-		// See com.jcraft.jsch.OpenSSHConfig. Translates some command-line keys
-		// to ssh-config keys.
-		private static final Map<String, String> KEY_MAP = new HashMap<>();
-
-		static {
-			KEY_MAP.put("kex", "KexAlgorithms"); //$NON-NLS-1$//$NON-NLS-2$
-			KEY_MAP.put("server_host_key", "HostKeyAlgorithms"); //$NON-NLS-1$ //$NON-NLS-2$
-			KEY_MAP.put("cipher.c2s", "Ciphers"); //$NON-NLS-1$ //$NON-NLS-2$
-			KEY_MAP.put("cipher.s2c", "Ciphers"); //$NON-NLS-1$ //$NON-NLS-2$
-			KEY_MAP.put("mac.c2s", "Macs"); //$NON-NLS-1$ //$NON-NLS-2$
-			KEY_MAP.put("mac.s2c", "Macs"); //$NON-NLS-1$ //$NON-NLS-2$
-			KEY_MAP.put("compression.s2c", "Compression"); //$NON-NLS-1$ //$NON-NLS-2$
-			KEY_MAP.put("compression.c2s", "Compression"); //$NON-NLS-1$ //$NON-NLS-2$
-			KEY_MAP.put("compression_level", "CompressionLevel"); //$NON-NLS-1$ //$NON-NLS-2$
-			KEY_MAP.put("MaxAuthTries", "NumberOfPasswordPrompts"); //$NON-NLS-1$ //$NON-NLS-2$
-		}
-
-		/**
-		 * Keys that can be specified multiple times, building up a list. (I.e.,
-		 * those are the keys that do not follow the general rule of "first
-		 * occurrence wins".)
-		 */
-		private static final Set<String> MULTI_KEYS = new HashSet<>();
-
-		static {
-			MULTI_KEYS.add("CERTIFICATEFILE"); //$NON-NLS-1$
-			MULTI_KEYS.add("IDENTITYFILE"); //$NON-NLS-1$
-			MULTI_KEYS.add("LOCALFORWARD"); //$NON-NLS-1$
-			MULTI_KEYS.add("REMOTEFORWARD"); //$NON-NLS-1$
-			MULTI_KEYS.add("SENDENV"); //$NON-NLS-1$
-		}
-
-		/**
-		 * Keys that take a whitespace-separated list of elements as argument.
-		 * Because the dequote-handling is different, we must handle those in
-		 * the parser. There are a few other keys that take comma-separated
-		 * lists as arguments, but for the parser those are single arguments
-		 * that must be quoted if they contain whitespace, and taking them apart
-		 * is the responsibility of the user of those keys.
-		 */
-		private static final Set<String> LIST_KEYS = new HashSet<>();
-
-		static {
-			LIST_KEYS.add("CANONICALDOMAINS"); //$NON-NLS-1$
-			LIST_KEYS.add("GLOBALKNOWNHOSTSFILE"); //$NON-NLS-1$
-			LIST_KEYS.add("SENDENV"); //$NON-NLS-1$
-			LIST_KEYS.add("USERKNOWNHOSTSFILE"); //$NON-NLS-1$
-		}
-
-		private Map<String, String> options;
-
-		private Map<String, List<String>> multiOptions;
-
-		private Map<String, List<String>> listOptions;
-
-		@Override
-		public String getHostname() {
-			return getValue("HOSTNAME"); //$NON-NLS-1$
-		}
-
-		@Override
-		public String getUser() {
-			return getValue("USER"); //$NON-NLS-1$
-		}
-
-		@Override
-		public int getPort() {
-			return positive(getValue("PORT")); //$NON-NLS-1$
-		}
-
-		private static String mapKey(String key) {
-			String k = KEY_MAP.get(key);
-			if (k == null) {
-				k = key;
-			}
-			return k.toUpperCase(Locale.ROOT);
-		}
-
-		private String findValue(String key) {
-			String k = mapKey(key);
-			String result = options != null ? options.get(k) : null;
-			if (result == null) {
-				// Also check the list and multi options. Modern OpenSSH treats
-				// UserKnownHostsFile and GlobalKnownHostsFile as list-valued,
-				// and so does this parser. Jsch 0.1.54 in general doesn't know
-				// about list-valued options (it _does_ know multi-valued
-				// options, though), and will ask for a single value for such
-				// options.
-				//
-				// Let's be lenient and return at least the first value from
-				// a list-valued or multi-valued key for which Jsch asks for a
-				// single value.
-				List<String> values = listOptions != null ? listOptions.get(k)
-						: null;
-				if (values == null) {
-					values = multiOptions != null ? multiOptions.get(k) : null;
-				}
-				if (values != null && !values.isEmpty()) {
-					result = values.get(0);
-				}
-			}
-			return result;
-		}
-
-		@Override
-		public String getValue(String key) {
-			// See com.jcraft.jsch.OpenSSHConfig.MyConfig.getValue() for this
-			// special case.
-			if (key.equals("compression.s2c") //$NON-NLS-1$
-					|| key.equals("compression.c2s")) { //$NON-NLS-1$
-				String foo = findValue(key);
-				if (foo == null || foo.equals("no")) { //$NON-NLS-1$
-					return "none,zlib@openssh.com,zlib"; //$NON-NLS-1$
-				}
-				return "zlib@openssh.com,zlib,none"; //$NON-NLS-1$
-			}
-			return findValue(key);
-		}
-
-		@Override
-		public String[] getValues(String key) {
-			String k = mapKey(key);
-			List<String> values = listOptions != null ? listOptions.get(k)
-					: null;
-			if (values == null) {
-				values = multiOptions != null ? multiOptions.get(k) : null;
-			}
-			if (values == null || values.isEmpty()) {
-				return new String[0];
-			}
-			return values.toArray(new String[0]);
-		}
-
-		public void setValue(String key, String value) {
-			String k = key.toUpperCase(Locale.ROOT);
-			if (MULTI_KEYS.contains(k)) {
-				if (multiOptions == null) {
-					multiOptions = new HashMap<>();
-				}
-				List<String> values = multiOptions.get(k);
-				if (values == null) {
-					values = new ArrayList<>(4);
-					multiOptions.put(k, values);
-				}
-				values.add(value);
-			} else {
-				if (options == null) {
-					options = new HashMap<>();
-				}
-				if (!options.containsKey(k)) {
-					options.put(k, value);
-				}
-			}
-		}
-
-		public void setValue(String key, List<String> values) {
-			if (values.isEmpty()) {
-				// Can occur only on a missing argument: ignore.
-				return;
-			}
-			String k = key.toUpperCase(Locale.ROOT);
-			// Check multi-valued keys first; because of the replacement
-			// strategy, they must take precedence over list-valued keys
-			// which always follow the "first occurrence wins" strategy.
-			//
-			// Note that SendEnv is a multi-valued list-valued key. (It's
-			// rather immaterial for JGit, though.)
-			if (MULTI_KEYS.contains(k)) {
-				if (multiOptions == null) {
-					multiOptions = new HashMap<>(2 * MULTI_KEYS.size());
-				}
-				List<String> items = multiOptions.get(k);
-				if (items == null) {
-					items = new ArrayList<>(values);
-					multiOptions.put(k, items);
-				} else {
-					items.addAll(values);
-				}
-			} else {
-				if (listOptions == null) {
-					listOptions = new HashMap<>(2 * LIST_KEYS.size());
-				}
-				if (!listOptions.containsKey(k)) {
-					listOptions.put(k, values);
-				}
-			}
-		}
-
-		public static boolean isListKey(String key) {
-			return LIST_KEYS.contains(key.toUpperCase(Locale.ROOT));
-		}
-
-		/**
-		 * Splits the argument into a list of whitespace-separated elements.
-		 * Elements containing whitespace must be quoted and will be de-quoted.
-		 *
-		 * @param argument
-		 *            argument part of the configuration line as read from the
-		 *            config file
-		 * @return a {@link List} of elements, possibly empty and possibly
-		 *         containing empty elements
-		 */
-		public static List<String> parseList(String argument) {
-			List<String> result = new ArrayList<>(4);
-			int start = 0;
-			int length = argument.length();
-			while (start < length) {
-				// Skip whitespace
-				if (Character.isSpaceChar(argument.charAt(start))) {
-					start++;
-					continue;
-				}
-				if (argument.charAt(start) == '"') {
-					int stop = argument.indexOf('"', ++start);
-					if (stop < start) {
-						// No closing double quote: skip
-						break;
-					}
-					result.add(argument.substring(start, stop));
-					start = stop + 1;
-				} else {
-					int stop = start + 1;
-					while (stop < length
-							&& !Character.isSpaceChar(argument.charAt(stop))) {
-						stop++;
-					}
-					result.add(argument.substring(start, stop));
-					start = stop + 1;
-				}
-			}
-			return result;
-		}
-
-		protected void merge(HostEntry entry) {
-			if (entry == null) {
-				// Can occur if we could not read the config file
-				return;
-			}
-			if (entry.options != null) {
-				if (options == null) {
-					options = new HashMap<>();
-				}
-				for (Map.Entry<String, String> item : entry.options
-						.entrySet()) {
-					if (!options.containsKey(item.getKey())) {
-						options.put(item.getKey(), item.getValue());
-					}
-				}
-			}
-			if (entry.listOptions != null) {
-				if (listOptions == null) {
-					listOptions = new HashMap<>(2 * LIST_KEYS.size());
-				}
-				for (Map.Entry<String, List<String>> item : entry.listOptions
-						.entrySet()) {
-					if (!listOptions.containsKey(item.getKey())) {
-						listOptions.put(item.getKey(), item.getValue());
-					}
-				}
-
-			}
-			if (entry.multiOptions != null) {
-				if (multiOptions == null) {
-					multiOptions = new HashMap<>(2 * MULTI_KEYS.size());
-				}
-				for (Map.Entry<String, List<String>> item : entry.multiOptions
-						.entrySet()) {
-					List<String> values = multiOptions.get(item.getKey());
-					if (values == null) {
-						values = new ArrayList<>(item.getValue());
-						multiOptions.put(item.getKey(), values);
-					} else {
-						values.addAll(item.getValue());
-					}
-				}
-			}
-		}
-
-		private class Replacer {
-			private final Map<Character, String> replacements = new HashMap<>();
-
-			public Replacer(String originalHostName, File home) {
-				replacements.put(Character.valueOf('%'), "%"); //$NON-NLS-1$
-				replacements.put(Character.valueOf('d'), home.getPath());
-				// Needs special treatment...
-				String host = getValue("HOSTNAME"); //$NON-NLS-1$
-				replacements.put(Character.valueOf('h'), originalHostName);
-				if (host != null && host.indexOf('%') >= 0) {
-					host = substitute(host, "h"); //$NON-NLS-1$
-					options.put("HOSTNAME", host); //$NON-NLS-1$
-				}
-				if (host != null) {
-					replacements.put(Character.valueOf('h'), host);
-				}
-				String localhost = SystemReader.getInstance().getHostname();
-				replacements.put(Character.valueOf('l'), localhost);
-				int period = localhost.indexOf('.');
-				if (period > 0) {
-					localhost = localhost.substring(0, period);
-				}
-				replacements.put(Character.valueOf('L'), localhost);
-				replacements.put(Character.valueOf('n'), originalHostName);
-				replacements.put(Character.valueOf('p'), getValue("PORT")); //$NON-NLS-1$
-				replacements.put(Character.valueOf('r'), getValue("USER")); //$NON-NLS-1$
-				replacements.put(Character.valueOf('u'), userName());
-				replacements.put(Character.valueOf('C'),
-						substitute("%l%h%p%r", "hlpr")); //$NON-NLS-1$ //$NON-NLS-2$
-			}
-
-			public String substitute(String input, String allowed) {
-				if (input == null || input.length() <= 1
-						|| input.indexOf('%') < 0) {
-					return input;
-				}
-				StringBuilder builder = new StringBuilder();
-				int start = 0;
-				int length = input.length();
-				while (start < length) {
-					int percent = input.indexOf('%', start);
-					if (percent < 0 || percent + 1 >= length) {
-						builder.append(input.substring(start));
-						break;
-					}
-					String replacement = null;
-					char ch = input.charAt(percent + 1);
-					if (ch == '%' || allowed.indexOf(ch) >= 0) {
-						replacement = replacements.get(Character.valueOf(ch));
-					}
-					if (replacement == null) {
-						builder.append(input.substring(start, percent + 2));
-					} else {
-						builder.append(input.substring(start, percent))
-								.append(replacement);
-					}
-					start = percent + 2;
-				}
-				return builder.toString();
-			}
-		}
-
-		private List<String> substitute(List<String> values, String allowed,
-				Replacer r) {
-			List<String> result = new ArrayList<>(values.size());
-			for (String value : values) {
-				result.add(r.substitute(value, allowed));
-			}
-			return result;
-		}
-
-		private List<String> replaceTilde(List<String> values, File home) {
-			List<String> result = new ArrayList<>(values.size());
-			for (String value : values) {
-				result.add(toFile(value, home).getPath());
-			}
-			return result;
-		}
-
-		protected void substitute(String originalHostName, File home) {
-			Replacer r = new Replacer(originalHostName, home);
-			if (multiOptions != null) {
-				List<String> values = multiOptions.get("IDENTITYFILE"); //$NON-NLS-1$
-				if (values != null) {
-					values = substitute(values, "dhlru", r); //$NON-NLS-1$
-					values = replaceTilde(values, home);
-					multiOptions.put("IDENTITYFILE", values); //$NON-NLS-1$
-				}
-				values = multiOptions.get("CERTIFICATEFILE"); //$NON-NLS-1$
-				if (values != null) {
-					values = substitute(values, "dhlru", r); //$NON-NLS-1$
-					values = replaceTilde(values, home);
-					multiOptions.put("CERTIFICATEFILE", values); //$NON-NLS-1$
-				}
-			}
-			if (listOptions != null) {
-				List<String> values = listOptions.get("GLOBALKNOWNHOSTSFILE"); //$NON-NLS-1$
-				if (values != null) {
-					values = replaceTilde(values, home);
-					listOptions.put("GLOBALKNOWNHOSTSFILE", values); //$NON-NLS-1$
-				}
-				values = listOptions.get("USERKNOWNHOSTSFILE"); //$NON-NLS-1$
-				if (values != null) {
-					values = replaceTilde(values, home);
-					listOptions.put("USERKNOWNHOSTSFILE", values); //$NON-NLS-1$
-				}
-			}
-			if (options != null) {
-				// HOSTNAME already done in Replacer constructor
-				String value = options.get("IDENTITYAGENT"); //$NON-NLS-1$
-				if (value != null) {
-					value = r.substitute(value, "dhlru"); //$NON-NLS-1$
-					value = toFile(value, home).getPath();
-					options.put("IDENTITYAGENT", value); //$NON-NLS-1$
-				}
-			}
-			// Match is not implemented and would need to be done elsewhere
-			// anyway. ControlPath, LocalCommand, ProxyCommand, and
-			// RemoteCommand are not used by Jsch.
-		}
-
-		@Override
-		@SuppressWarnings("nls")
-		public String toString() {
-			return "HostEntry [options=" + options + ", multiOptions="
-					+ multiOptions + ", listOptions=" + listOptions + "]";
-		}
+		HostEntry entry = configFile.lookup(hostName, -1, null);
+		return new Host(entry, hostName, configFile.getLocalUserName());
 	}
 
 	/**
@@ -829,8 +156,34 @@
 
 		int connectionAttempts;
 
+		private HostEntry entry;
+
 		private Config config;
 
+		// See com.jcraft.jsch.OpenSSHConfig. Translates some command-line keys
+		// to ssh-config keys.
+		private static final Map<String, String> KEY_MAP = new TreeMap<>(
+				String.CASE_INSENSITIVE_ORDER);
+
+		static {
+			KEY_MAP.put("kex", SshConstants.KEX_ALGORITHMS); //$NON-NLS-1$
+			KEY_MAP.put("server_host_key", SshConstants.HOST_KEY_ALGORITHMS); //$NON-NLS-1$
+			KEY_MAP.put("cipher.c2s", SshConstants.CIPHERS); //$NON-NLS-1$
+			KEY_MAP.put("cipher.s2c", SshConstants.CIPHERS); //$NON-NLS-1$
+			KEY_MAP.put("mac.c2s", SshConstants.MACS); //$NON-NLS-1$
+			KEY_MAP.put("mac.s2c", SshConstants.MACS); //$NON-NLS-1$
+			KEY_MAP.put("compression.s2c", SshConstants.COMPRESSION); //$NON-NLS-1$
+			KEY_MAP.put("compression.c2s", SshConstants.COMPRESSION); //$NON-NLS-1$
+			KEY_MAP.put("compression_level", "CompressionLevel"); //$NON-NLS-1$ //$NON-NLS-2$
+			KEY_MAP.put("MaxAuthTries", //$NON-NLS-1$
+					SshConstants.NUMBER_OF_PASSWORD_PROMPTS);
+		}
+
+		private static String mapKey(String key) {
+			String k = KEY_MAP.get(key);
+			return k != null ? k : key;
+		}
+
 		/**
 		 * Creates a new uninitialized {@link Host}.
 		 */
@@ -838,9 +191,9 @@
 			// For API backwards compatibility with pre-4.9 JGit
 		}
 
-		Host(Config config, String hostName, File homeDir) {
-			this.config = config;
-			complete(hostName, homeDir);
+		Host(HostEntry entry, String hostName, String localUserName) {
+			this.entry = entry;
+			complete(hostName, localUserName);
 		}
 
 		/**
@@ -910,42 +263,84 @@
 		}
 
 
-		private void complete(String initialHostName, File homeDir) {
+		private void complete(String initialHostName, String localUserName) {
 			// Try to set values from the options.
-			hostName = config.getHostname();
-			user = config.getUser();
-			port = config.getPort();
+			hostName = entry.getValue(SshConstants.HOST_NAME);
+			user = entry.getValue(SshConstants.USER);
+			port = positive(entry.getValue(SshConstants.PORT));
 			connectionAttempts = positive(
-					config.getValue("ConnectionAttempts")); //$NON-NLS-1$
-			strictHostKeyChecking = config.getValue("StrictHostKeyChecking"); //$NON-NLS-1$
-			String value = config.getValue("BatchMode"); //$NON-NLS-1$
-			if (value != null) {
-				batchMode = yesno(value);
-			}
-			value = config.getValue("PreferredAuthentications"); //$NON-NLS-1$
-			if (value != null) {
-				preferredAuthentications = nows(value);
-			}
+					entry.getValue(SshConstants.CONNECTION_ATTEMPTS));
+			strictHostKeyChecking = entry
+					.getValue(SshConstants.STRICT_HOST_KEY_CHECKING);
+			batchMode = Boolean.valueOf(OpenSshConfigFile
+					.flag(entry.getValue(SshConstants.BATCH_MODE)));
+			preferredAuthentications = entry
+					.getValue(SshConstants.PREFERRED_AUTHENTICATIONS);
 			// Fill in defaults if still not set
-			if (hostName == null) {
+			if (hostName == null || hostName.isEmpty()) {
 				hostName = initialHostName;
 			}
-			if (user == null) {
-				user = OpenSshConfig.userName();
+			if (user == null || user.isEmpty()) {
+				user = localUserName;
 			}
 			if (port <= 0) {
-				port = OpenSshConfig.SSH_PORT;
+				port = SshConstants.SSH_DEFAULT_PORT;
 			}
 			if (connectionAttempts <= 0) {
 				connectionAttempts = 1;
 			}
-			String[] identityFiles = config.getValues("IdentityFile"); //$NON-NLS-1$
-			if (identityFiles != null && identityFiles.length > 0) {
-				identityFile = toFile(identityFiles[0], homeDir);
+			List<String> identityFiles = entry
+					.getValues(SshConstants.IDENTITY_FILE);
+			if (identityFiles != null && !identityFiles.isEmpty()) {
+				identityFile = new File(identityFiles.get(0));
 			}
 		}
 
 		Config getConfig() {
+			if (config == null) {
+				config = new Config() {
+
+					@Override
+					public String getHostname() {
+						return Host.this.getHostName();
+					}
+
+					@Override
+					public String getUser() {
+						return Host.this.getUser();
+					}
+
+					@Override
+					public int getPort() {
+						return Host.this.getPort();
+					}
+
+					@Override
+					public String getValue(String key) {
+						// See com.jcraft.jsch.OpenSSHConfig.MyConfig.getValue()
+						// for this special case.
+						if (key.equals("compression.s2c") //$NON-NLS-1$
+								|| key.equals("compression.c2s")) { //$NON-NLS-1$
+							if (!OpenSshConfigFile.flag(
+									Host.this.entry.getValue(mapKey(key)))) {
+								return "none,zlib@openssh.com,zlib"; //$NON-NLS-1$
+							}
+							return "zlib@openssh.com,zlib,none"; //$NON-NLS-1$
+						}
+						return Host.this.entry.getValue(mapKey(key));
+					}
+
+					@Override
+					public String[] getValues(String key) {
+						List<String> values = Host.this.entry
+								.getValues(mapKey(key));
+						if (values == null) {
+							return new String[0];
+						}
+						return values.toArray(new String[0]);
+					}
+				};
+			}
 			return config;
 		}
 
@@ -957,7 +352,7 @@
 					+ ", preferredAuthentications=" + preferredAuthentications
 					+ ", batchMode=" + batchMode + ", strictHostKeyChecking="
 					+ strictHostKeyChecking + ", connectionAttempts="
-					+ connectionAttempts + ", config=" + config + "]";
+					+ connectionAttempts + ", entry=" + entry + "]";
 		}
 	}
 
@@ -977,9 +372,7 @@
 
 	/** {@inheritDoc} */
 	@Override
-	@SuppressWarnings("nls")
 	public String toString() {
-		return "OpenSshConfig [home=" + home + ", configFile=" + configFile
-				+ ", lastModified=" + lastModified + ", state=" + state + "]";
+		return "OpenSshConfig [configFile=" + configFile + ']'; //$NON-NLS-1$
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHook.java
index 5cbb6f5..ba5d2f3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHook.java
@@ -63,7 +63,7 @@
  */
 public interface PostReceiveHook {
 	/** A simple no-op hook. */
-	public static final PostReceiveHook NULL = new PostReceiveHook() {
+	PostReceiveHook NULL = new PostReceiveHook() {
 		@Override
 		public void onPostReceive(final ReceivePack rp,
 				final Collection<ReceiveCommand> commands) {
@@ -81,6 +81,6 @@
 	 *            unmodifiable set of successfully completed commands. May be
 	 *            the empty set.
 	 */
-	public void onPostReceive(ReceivePack rp,
+	void onPostReceive(ReceivePack rp,
 			Collection<ReceiveCommand> commands);
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHook.java
index 09667eb..3aa3b12 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHook.java
@@ -57,7 +57,7 @@
  */
 public interface PostUploadHook {
 	/** A simple no-op hook. */
-	public static final PostUploadHook NULL = new PostUploadHook() {
+	PostUploadHook NULL = new PostUploadHook() {
 		@Override
 		public void onPostUpload(PackStatistics stats) {
 			// Do nothing.
@@ -72,5 +72,5 @@
 	 *            {@link org.eclipse.jgit.internal.storage.pack.PackWriter} for
 	 *            the uploaded pack
 	 */
-	public void onPostUpload(PackStatistics stats);
+	void onPostUpload(PackStatistics stats);
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHook.java
index 77c1a8a..30845d3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHook.java
@@ -79,7 +79,7 @@
  */
 public interface PreReceiveHook {
 	/** A simple no-op hook. */
-	public static final PreReceiveHook NULL = new PreReceiveHook() {
+	PreReceiveHook NULL = new PreReceiveHook() {
 		@Override
 		public void onPreReceive(final ReceivePack rp,
 				final Collection<ReceiveCommand> commands) {
@@ -99,5 +99,5 @@
 	 *            unmodifiable set of valid commands still pending execution.
 	 *            May be the empty set.
 	 */
-	public void onPreReceive(ReceivePack rp, Collection<ReceiveCommand> commands);
+	void onPreReceive(ReceivePack rp, Collection<ReceiveCommand> commands);
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHook.java
index 2e1cd58..65dc241 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHook.java
@@ -59,7 +59,7 @@
  */
 public interface PreUploadHook {
 	/** A simple no-op hook. */
-	public static final PreUploadHook NULL = new PreUploadHook() {
+	PreUploadHook NULL = new PreUploadHook() {
 		@Override
 		public void onBeginNegotiateRound(UploadPack up,
 				Collection<? extends ObjectId> wants, int cntOffered)
@@ -96,7 +96,7 @@
 	 * @throws org.eclipse.jgit.transport.ServiceMayNotContinueException
 	 *             abort; the message will be sent to the user.
 	 */
-	public void onBeginNegotiateRound(UploadPack up,
+	void onBeginNegotiateRound(UploadPack up,
 			Collection<? extends ObjectId> wants, int cntOffered)
 			throws ServiceMayNotContinueException;
 
@@ -120,7 +120,7 @@
 	 * @throws org.eclipse.jgit.transport.ServiceMayNotContinueException
 	 *             abort; the message will be sent to the user.
 	 */
-	public void onEndNegotiateRound(UploadPack up,
+	void onEndNegotiateRound(UploadPack up,
 			Collection<? extends ObjectId> wants, int cntCommon,
 			int cntNotFound, boolean ready)
 			throws ServiceMayNotContinueException;
@@ -141,7 +141,7 @@
 	 * @throws org.eclipse.jgit.transport.ServiceMayNotContinueException
 	 *             abort; the message will be sent to the user.
 	 */
-	public void onSendPack(UploadPack up, Collection<? extends ObjectId> wants,
+	void onSendPack(UploadPack up, Collection<? extends ObjectId> wants,
 			Collection<? extends ObjectId> haves)
 			throws ServiceMayNotContinueException;
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV0Parser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV0Parser.java
new file mode 100644
index 0000000..21498d6
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV0Parser.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2018, Google LLC.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+package org.eclipse.jgit.transport;
+
+import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_FILTER;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.text.MessageFormat;
+
+import org.eclipse.jgit.errors.PackProtocolException;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.transport.parser.FirstWant;
+import org.eclipse.jgit.lib.ObjectId;
+
+/**
+ * Parser for git protocol versions 0 and 1.
+ *
+ * It reads the lines coming through the {@link PacketLineIn} and builds a
+ * {@link FetchV0Request} object.
+ *
+ * It requires a transferConfig object to know if the server supports filters.
+ */
+final class ProtocolV0Parser {
+
+	private final TransferConfig transferConfig;
+
+	ProtocolV0Parser(TransferConfig transferConfig) {
+		this.transferConfig = transferConfig;
+	}
+
+	/**
+	 * Parse an incoming protocol v1 upload request arguments from the wire.
+	 *
+	 * The incoming PacketLineIn is consumed until an END line, but the caller
+	 * is responsible for closing it (if needed).
+	 *
+	 * @param pckIn
+	 *            incoming lines. This method will read until an END line.
+	 * @return a FetchV0Request with the data received in the wire.
+	 * @throws PackProtocolException
+	 * @throws IOException
+	 */
+	FetchV0Request recvWants(PacketLineIn pckIn)
+			throws PackProtocolException, IOException {
+		FetchV0Request.Builder reqBuilder = new FetchV0Request.Builder();
+
+		boolean isFirst = true;
+		boolean filterReceived = false;
+
+		for (;;) {
+			String line;
+			try {
+				line = pckIn.readString();
+			} catch (EOFException eof) {
+				if (isFirst) {
+					break;
+				}
+				throw eof;
+			}
+
+			if (line == PacketLineIn.END) {
+				break;
+			}
+
+			if (line.startsWith("deepen ")) { //$NON-NLS-1$
+				int depth = Integer.parseInt(line.substring(7));
+				if (depth <= 0) {
+					throw new PackProtocolException(
+							MessageFormat.format(JGitText.get().invalidDepth,
+									Integer.valueOf(depth)));
+				}
+				reqBuilder.setDepth(depth);
+				continue;
+			}
+
+			if (line.startsWith("shallow ")) { //$NON-NLS-1$
+				reqBuilder.addClientShallowCommit(
+						ObjectId.fromString(line.substring(8)));
+				continue;
+			}
+
+			if (transferConfig.isAllowFilter()
+					&& line.startsWith(OPTION_FILTER + " ")) { //$NON-NLS-1$
+				String arg = line.substring(OPTION_FILTER.length() + 1);
+
+				if (filterReceived) {
+					throw new PackProtocolException(
+							JGitText.get().tooManyFilters);
+				}
+				filterReceived = true;
+
+				reqBuilder.setFilterBlobLimit(ProtocolV2Parser.filterLine(arg));
+				continue;
+			}
+
+			if (!line.startsWith("want ") || line.length() < 45) { //$NON-NLS-1$
+				throw new PackProtocolException(MessageFormat
+						.format(JGitText.get().expectedGot, "want", line)); //$NON-NLS-1$
+			}
+
+			if (isFirst) {
+				if (line.length() > 45) {
+					FirstWant firstLine = FirstWant.fromLine(line);
+					reqBuilder.addClientCapabilities(firstLine.getCapabilities());
+					reqBuilder.setAgent(firstLine.getAgent());
+					line = firstLine.getLine();
+				}
+			}
+
+			reqBuilder.addWantId(ObjectId.fromString(line.substring(5)));
+			isFirst = false;
+		}
+
+		return reqBuilder.build();
+	}
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java
index 2cc50a7..8f4b86e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java
@@ -44,15 +44,20 @@
 
 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_DEEPEN_RELATIVE;
 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_FILTER;
+import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_INCLUDE_TAG;
 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_NO_PROGRESS;
 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_OFS_DELTA;
+import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SERVER_OPTION;
 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND_64K;
 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_THIN_PACK;
 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_WANT_REF;
 
 import java.io.IOException;
 import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
 
 import org.eclipse.jgit.errors.PackProtocolException;
 import org.eclipse.jgit.internal.JGitText;
@@ -73,6 +78,35 @@
 		this.transferConfig = transferConfig;
 	}
 
+	/*
+	 * Read lines until DELIM or END, calling the appropiate consumer.
+	 *
+	 * Returns the last read line (so caller can check if there is more to read
+	 * in the line).
+	 */
+	private static String consumeCapabilities(PacketLineIn pckIn,
+			Consumer<String> serverOptionConsumer,
+			Consumer<String> agentConsumer) throws IOException {
+
+		String serverOptionPrefix = OPTION_SERVER_OPTION + '=';
+		String agentPrefix = OPTION_AGENT + '=';
+
+		String line = pckIn.readString();
+		while (line != PacketLineIn.DELIM && line != PacketLineIn.END) {
+			if (line.startsWith(serverOptionPrefix)) {
+				serverOptionConsumer
+						.accept(line.substring(serverOptionPrefix.length()));
+			} else if (line.startsWith(agentPrefix)) {
+				agentConsumer.accept(line.substring(agentPrefix.length()));
+			} else {
+				// Unrecognized capability. Ignore it.
+			}
+			line = pckIn.readString();
+		}
+
+		return line;
+	}
+
 	/**
 	 * Parse the incoming fetch request arguments from the wire. The caller must
 	 * be sure that what is comings is a fetch request before coming here.
@@ -93,21 +127,26 @@
 
 		// Packs are always sent multiplexed and using full 64K
 		// lengths.
-		reqBuilder.addOption(OPTION_SIDE_BAND_64K);
+		reqBuilder.addClientCapability(OPTION_SIDE_BAND_64K);
 
-		String line;
+		String line = consumeCapabilities(pckIn,
+				serverOption -> reqBuilder.addServerOption(serverOption),
+				agent -> reqBuilder.setAgent(agent));
 
-		// Currently, we do not support any capabilities, so the next
-		// line is DELIM.
-		if ((line = pckIn.readString()) != PacketLineIn.DELIM) {
-			throw new PackProtocolException(MessageFormat
-					.format(JGitText.get().unexpectedPacketLine, line));
+		if (line == PacketLineIn.END) {
+			return reqBuilder.build();
+		}
+
+		if (line != PacketLineIn.DELIM) {
+			throw new PackProtocolException(
+					MessageFormat.format(JGitText.get().unexpectedPacketLine,
+							line));
 		}
 
 		boolean filterReceived = false;
 		while ((line = pckIn.readString()) != PacketLineIn.END) {
 			if (line.startsWith("want ")) { //$NON-NLS-1$
-				reqBuilder.addWantsId(ObjectId.fromString(line.substring(5)));
+				reqBuilder.addWantId(ObjectId.fromString(line.substring(5)));
 			} else if (transferConfig.isAllowRefInWant()
 					&& line.startsWith(OPTION_WANT_REF + " ")) { //$NON-NLS-1$
 				reqBuilder.addWantedRef(line.substring(OPTION_WANT_REF.length() + 1));
@@ -116,13 +155,13 @@
 			} else if (line.equals("done")) { //$NON-NLS-1$
 				reqBuilder.setDoneReceived();
 			} else if (line.equals(OPTION_THIN_PACK)) {
-				reqBuilder.addOption(OPTION_THIN_PACK);
+				reqBuilder.addClientCapability(OPTION_THIN_PACK);
 			} else if (line.equals(OPTION_NO_PROGRESS)) {
-				reqBuilder.addOption(OPTION_NO_PROGRESS);
+				reqBuilder.addClientCapability(OPTION_NO_PROGRESS);
 			} else if (line.equals(OPTION_INCLUDE_TAG)) {
-				reqBuilder.addOption(OPTION_INCLUDE_TAG);
+				reqBuilder.addClientCapability(OPTION_INCLUDE_TAG);
 			} else if (line.equals(OPTION_OFS_DELTA)) {
-				reqBuilder.addOption(OPTION_OFS_DELTA);
+				reqBuilder.addClientCapability(OPTION_OFS_DELTA);
 			} else if (line.startsWith("shallow ")) { //$NON-NLS-1$
 				reqBuilder.addClientShallowCommit(
 						ObjectId.fromString(line.substring(8)));
@@ -149,7 +188,7 @@
 							JGitText.get().deepenNotWithDeepen);
 				}
 			} else if (line.equals(OPTION_DEEPEN_RELATIVE)) {
-				reqBuilder.addOption(OPTION_DEEPEN_RELATIVE);
+				reqBuilder.addClientCapability(OPTION_DEEPEN_RELATIVE);
 			} else if (line.startsWith("deepen-since ")) { //$NON-NLS-1$
 				int ts = Integer.parseInt(line.substring(13));
 				if (ts <= 0) {
@@ -180,6 +219,57 @@
 	}
 
 	/**
+	 * Parse the incoming ls-refs request arguments from the wire. This is meant
+	 * for calling immediately after the caller has consumed a "command=ls-refs"
+	 * line indicating the beginning of a ls-refs request.
+	 *
+	 * The incoming PacketLineIn is consumed until an END line, but the caller
+	 * is responsible for closing it (if needed)
+	 *
+	 * @param pckIn
+	 *            incoming lines. This method will read until an END line.
+	 * @return a LsRefsV2Request object with the data received in the wire.
+	 * @throws PackProtocolException
+	 *             for inconsistencies in the protocol (e.g. unexpected lines)
+	 * @throws IOException
+	 *             reporting problems reading the incoming messages from the
+	 *             wire
+	 */
+	LsRefsV2Request parseLsRefsRequest(PacketLineIn pckIn)
+			throws PackProtocolException, IOException {
+		LsRefsV2Request.Builder builder = LsRefsV2Request.builder();
+		List<String> prefixes = new ArrayList<>();
+
+		String line = consumeCapabilities(pckIn,
+				serverOption -> builder.addServerOption(serverOption),
+				agent -> builder.setAgent(agent));
+
+		if (line == PacketLineIn.END) {
+			return builder.build();
+		}
+
+		if (line != PacketLineIn.DELIM) {
+			throw new PackProtocolException(MessageFormat
+					.format(JGitText.get().unexpectedPacketLine, line));
+		}
+
+		while ((line = pckIn.readString()) != PacketLineIn.END) {
+			if (line.equals("peel")) { //$NON-NLS-1$
+				builder.setPeel(true);
+			} else if (line.equals("symrefs")) { //$NON-NLS-1$
+				builder.setSymrefs(true);
+			} else if (line.startsWith("ref-prefix ")) { //$NON-NLS-1$
+				prefixes.add(line.substring("ref-prefix ".length())); //$NON-NLS-1$
+			} else {
+				throw new PackProtocolException(MessageFormat
+						.format(JGitText.get().unexpectedPacketLine, line));
+			}
+		}
+
+		return builder.setRefPrefixes(prefixes).build();
+	}
+
+	/*
 	 * Process the content of "filter" line from the protocol. It has a shape
 	 * like "blob:none" or "blob:limit=N", with limit a positive number.
 	 *
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConnection.java
index ff2939a..7f98d4d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConnection.java
@@ -113,7 +113,7 @@
 	 *             created. Non-critical errors concerning only isolated refs
 	 *             should be placed in refUpdates.
 	 */
-	public void push(final ProgressMonitor monitor,
+	void push(final ProgressMonitor monitor,
 			final Map<String, RemoteRefUpdate> refUpdates)
 			throws TransportException;
 
@@ -163,7 +163,7 @@
 	 *             should be placed in refUpdates.
 	 * @since 3.0
 	 */
-	public void push(final ProgressMonitor monitor,
+	void push(final ProgressMonitor monitor,
 			final Map<String, RemoteRefUpdate> refUpdates, OutputStream out)
 			throws TransportException;
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
index 35fb0b17..577aaf4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
@@ -76,8 +76,6 @@
 	/** If {@link BasePackPushConnection#CAPABILITY_REPORT_STATUS} is enabled. */
 	private boolean reportStatus;
 
-	private boolean echoCommandFailures;
-
 	/** Whether the client intends to use push options. */
 	private boolean usePushOptions;
 	private List<String> pushOptions;
@@ -191,9 +189,11 @@
 	 *            messages before sending the command results. This is usually
 	 *            not necessary, but may help buggy Git clients that discard the
 	 *            errors when all branches fail.
+	 * @deprecated no widely used Git versions need this any more
 	 */
+	@Deprecated
 	public void setEchoCommandFailures(boolean echo) {
-		echoCommandFailures = echo;
+		// No-op.
 	}
 
 	/**
@@ -269,36 +269,28 @@
 				}
 			}
 
-			if (unpackError == null) {
-				boolean atomic = isCapabilityEnabled(CAPABILITY_ATOMIC);
-				setAtomic(atomic);
+			try {
+				if (unpackError == null) {
+					boolean atomic = isCapabilityEnabled(CAPABILITY_ATOMIC);
+					setAtomic(atomic);
 
-				validateCommands();
-				if (atomic && anyRejects())
-					failPendingCommands();
+					validateCommands();
+					if (atomic && anyRejects()) {
+						failPendingCommands();
+					}
 
-				preReceive.onPreReceive(this, filterCommands(Result.NOT_ATTEMPTED));
-				if (atomic && anyRejects())
-					failPendingCommands();
-				executeCommands();
+					preReceive.onPreReceive(
+							this, filterCommands(Result.NOT_ATTEMPTED));
+					if (atomic && anyRejects()) {
+						failPendingCommands();
+					}
+					executeCommands();
+				}
+			} finally {
+				unlockPack();
 			}
-			unlockPack();
 
 			if (reportStatus) {
-				if (echoCommandFailures && msgOut != null) {
-					sendStatusReport(false, unpackError, new Reporter() {
-						@Override
-						void sendString(String s) throws IOException {
-							msgOut.write(Constants.encode(s + "\n")); //$NON-NLS-1$
-						}
-					});
-					msgOut.flush();
-					try {
-						Thread.sleep(500);
-					} catch (InterruptedException wakeUp) {
-						// Ignore an early wake up.
-					}
-				}
 				sendStatusReport(true, unpackError, new Reporter() {
 					@Override
 					void sendString(String s) throws IOException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
index 4662435..6595cab 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
@@ -207,7 +207,8 @@
 	 * <p>
 	 * This method must be invoked prior to any of the following:
 	 * <ul>
-	 * <li>{@link #send(Map)}
+	 * <li>{@link #send(Map)}</li>
+	 * <li>{@link #send(Collection)}</li>
 	 * </ul>
 	 *
 	 * @param deref
@@ -223,8 +224,9 @@
 	 * <p>
 	 * This method must be invoked prior to any of the following:
 	 * <ul>
-	 * <li>{@link #send(Map)}
-	 * <li>{@link #advertiseHave(AnyObjectId)}
+	 * <li>{@link #send(Map)}</li>
+	 * <li>{@link #send(Collection)}</li>
+	 * <li>{@link #advertiseHave(AnyObjectId)}</li>
 	 * </ul>
 	 *
 	 * @param name
@@ -257,8 +259,9 @@
 	 * <p>
 	 * This method must be invoked prior to any of the following:
 	 * <ul>
-	 * <li>{@link #send(Map)}
-	 * <li>{@link #advertiseHave(AnyObjectId)}
+	 * <li>{@link #send(Map)}</li>
+	 * <li>{@link #send(Collection)}</li>
+	 * <li>{@link #advertiseHave(AnyObjectId)}</li>
 	 * </ul>
 	 *
 	 * @param from
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefFilter.java
index 992ddc6..d6d6198 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefFilter.java
@@ -61,7 +61,7 @@
 	/**
 	 * The default filter, allows all refs to be shown.
 	 */
-	public static final RefFilter DEFAULT = new RefFilter() {
+	RefFilter DEFAULT = new RefFilter() {
 		@Override
 		public Map<String, Ref> filter (Map<String, Ref> refs) {
 			return refs;
@@ -76,5 +76,5 @@
 	 * @return
 	 *            the filtered map of refs.
 	 */
-	public Map<String, Ref> filter(Map<String, Ref> refs);
+	Map<String, Ref> filter(Map<String, Ref> refs);
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java
index 931653f..9a67f0f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java
@@ -521,7 +521,7 @@
 						: "(null)") + "..."
 				+ (newObjectId != null ? newObjectId.name() : "(null)")
 				+ (fastForward ? ", fastForward" : "")
- + ", srcRef=" + srcRef
+				+ ", srcRef=" + srcRef
 				+ (forceUpdate ? ", forceUpdate" : "") + ", message="
 				+ (message != null ? "\"" + message + "\"" : "null") + "]";
 	}
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 525c895..e2109c2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteSession.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteSession.java
@@ -78,10 +78,21 @@
 	 *             a TransportException may be thrown (a subclass of
 	 *             java.io.IOException).
 	 */
-	public Process exec(String commandName, int timeout) throws IOException;
+	Process exec(String commandName, int timeout) throws IOException;
+
+	/**
+	 * Obtain an {@link FtpChannel} for performing FTP operations over this
+	 * {@link RemoteSession}. The default implementation returns {@code null}.
+	 *
+	 * @return the {@link FtpChannel}
+	 * @since 5.2
+	 */
+	default FtpChannel getFtpChannel() {
+		return null;
+	}
 
 	/**
 	 * Disconnect the remote session
 	 */
-	public void disconnect();
+	void disconnect();
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java
index 90600cb..fde4401 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java
@@ -236,7 +236,7 @@
 
 		messages.write(msg);
 		if (out != null)
-			out.write(msg.getBytes());
+			out.write(msg.getBytes(UTF_8));
 	}
 
 	private void beginTask(int totalWorkUnits) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java
new file mode 100644
index 0000000..2b79d71
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2018 Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://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.
+ */
+
+package org.eclipse.jgit.transport;
+
+import org.eclipse.jgit.lib.Constants;
+
+/**
+ * Constants relating to ssh.
+ *
+ * @since 5.2
+ */
+@SuppressWarnings("nls")
+public final class SshConstants {
+
+	private SshConstants() {
+		// No instances, please.
+	}
+
+	/** IANA assigned port number for ssh. */
+	public static final int SSH_DEFAULT_PORT = 22;
+
+	/** URI scheme for ssh. */
+	public static final String SSH_SCHEME = "ssh";
+
+	/** URI scheme for sftp. */
+	public static final String SFTP_SCHEME = "sftp";
+
+	/** Default name for a ssh directory. */
+	public static final String SSH_DIR = ".ssh";
+
+	/** Name of the ssh config file. */
+	public static final String CONFIG = Constants.CONFIG;
+
+	/** Default name of the user "known hosts" file. */
+	public static final String KNOWN_HOSTS = "known_hosts";
+
+	// Config file keys
+
+	/** Key in an ssh config file. */
+	public static final String BATCH_MODE = "BatchMode";
+
+	/** Key in an ssh config file. */
+	public static final String CANONICAL_DOMAINS = "CanonicalDomains";
+
+	/** Key in an ssh config file. */
+	public static final String CERTIFICATE_FILE = "CertificateFile";
+
+	/** Key in an ssh config file. */
+	public static final String CIPHERS = "Ciphers";
+
+	/** Key in an ssh config file. */
+	public static final String COMPRESSION = "Compression";
+
+	/** Key in an ssh config file. */
+	public static final String CONNECTION_ATTEMPTS = "ConnectionAttempts";
+
+	/** Key in an ssh config file. */
+	public static final String CONTROL_PATH = "ControlPath";
+
+	/** Key in an ssh config file. */
+	public static final String GLOBAL_KNOWN_HOSTS_FILE = "GlobalKnownHostsFile";
+
+	/** Key in an ssh config file. */
+	public static final String HOST = "Host";
+
+	/** Key in an ssh config file. */
+	public static final String HOST_KEY_ALGORITHMS = "HostKeyAlgorithms";
+
+	/** Key in an ssh config file. */
+	public static final String HOST_NAME = "HostName";
+
+	/** Key in an ssh config file. */
+	public static final String IDENTITIES_ONLY = "IdentitiesOnly";
+
+	/** Key in an ssh config file. */
+	public static final String IDENTITY_AGENT = "IdentityAgent";
+
+	/** Key in an ssh config file. */
+	public static final String IDENTITY_FILE = "IdentityFile";
+
+	/** Key in an ssh config file. */
+	public static final String KEX_ALGORITHMS = "KexAlgorithms";
+
+	/** Key in an ssh config file. */
+	public static final String LOCAL_COMMAND = "LocalCommand";
+
+	/** Key in an ssh config file. */
+	public static final String LOCAL_FORWARD = "LocalForward";
+
+	/** Key in an ssh config file. */
+	public static final String MACS = "MACs";
+
+	/** Key in an ssh config file. */
+	public static final String NUMBER_OF_PASSWORD_PROMPTS = "NumberOfPasswordPrompts";
+
+	/** Key in an ssh config file. */
+	public static final String PORT = "Port";
+
+	/** Key in an ssh config file. */
+	public static final String PREFERRED_AUTHENTICATIONS = "PreferredAuthentications";
+
+	/** Key in an ssh config file. */
+	public static final String PROXY_COMMAND = "ProxyCommand";
+
+	/** Key in an ssh config file. */
+	public static final String REMOTE_COMMAND = "RemoteCommand";
+
+	/** Key in an ssh config file. */
+	public static final String REMOTE_FORWARD = "RemoteForward";
+
+	/** Key in an ssh config file. */
+	public static final String SEND_ENV = "SendEnv";
+
+	/** Key in an ssh config file. */
+	public static final String STRICT_HOST_KEY_CHECKING = "StrictHostKeyChecking";
+
+	/** Key in an ssh config file. */
+	public static final String USER = "User";
+
+	/** Key in an ssh config file. */
+	public static final String USER_KNOWN_HOSTS_FILE = "UserKnownHostsFile";
+
+	// Values
+
+	/** Flag value. */
+	public static final String YES = "yes";
+
+	/** Flag value. */
+	public static final String ON = "on";
+
+	/** Flag value. */
+	public static final String TRUE = "true";
+
+	/** Flag value. */
+	public static final String NO = "no";
+
+	/** Flag value. */
+	public static final String OFF = "off";
+
+	/** Flag value. */
+	public static final String FALSE = "false";
+
+	// Default identity file names
+
+	/** Name of the default RSA private identity file. */
+	public static final String ID_RSA = "id_rsa";
+
+	/** Name of the default DSA private identity file. */
+	public static final String ID_DSA = "id_dsa";
+
+	/** Name of the default ECDSA private identity file. */
+	public static final String ID_ECDSA = "id_ecdsa";
+
+	/** Name of the default ECDSA private identity file. */
+	public static final String ID_ED25519 = "id_ed25519";
+
+	/** All known default identity file names. */
+	public static final String[] DEFAULT_IDENTITIES = { //
+			ID_RSA, ID_DSA, ID_ECDSA, ID_ED25519
+	};
+}
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 ae357df..005a0c2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java
@@ -44,8 +44,13 @@
 
 package org.eclipse.jgit.transport;
 
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
 import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.SystemReader;
 
 /**
  * Creates and destroys SSH connections to a remote system.
@@ -88,21 +93,38 @@
 	}
 
 	/**
+	 * Retrieves the local user name as defined by the system property
+	 * "user.name".
+	 *
+	 * @return the user name
+	 * @since 5.2
+	 */
+	public static String getLocalUserName() {
+		return AccessController.doPrivileged(new PrivilegedAction<String>() {
+			@Override
+			public String run() {
+				return SystemReader.getInstance()
+						.getProperty(Constants.OS_USER_NAME_KEY);
+			}
+		});
+	}
+
+	/**
 	 * 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.
+	 * The caller must connect the session by invoking <code>connect()</code> if
+	 * it has not already been connected.
 	 *
 	 * @param uri
 	 *            URI information about the remote host
 	 * @param credentialsProvider
 	 *            provider to support authentication, may be null.
 	 * @param fs
-	 *            the file system abstraction which will be necessary to
-	 *            perform certain file system operations.
+	 *            the file system abstraction which will be necessary to perform
+	 *            certain file system operations.
 	 * @param tms
 	 *            Timeout value, in milliseconds.
 	 * @return a session that can contact the remote host.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
index db95396..a3e655c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
@@ -106,7 +106,8 @@
 			this.name = name;
 		}
 
-		static @Nullable ProtocolVersion parse(@Nullable String name) {
+		@Nullable
+		static ProtocolVersion parse(@Nullable String name) {
 			if (name == null) {
 				return null;
 			}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundle.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundle.java
index 6a285e5..ee851cc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundle.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundle.java
@@ -58,5 +58,5 @@
 	/**
 	 * Bundle signature
 	 */
-	public static final String V2_BUNDLE_SIGNATURE = "# v2 git bundle"; //$NON-NLS-1$
+	String V2_BUNDLE_SIGNATURE = "# v2 git bundle"; //$NON-NLS-1$
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java
index f129ba3..5c68308 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java
@@ -53,13 +53,14 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeMap;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 import org.eclipse.jgit.errors.NotSupportedException;
 import org.eclipse.jgit.errors.TransportException;
@@ -73,12 +74,6 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.SymbolicRef;
 
-import com.jcraft.jsch.Channel;
-import com.jcraft.jsch.ChannelSftp;
-import com.jcraft.jsch.JSchException;
-import com.jcraft.jsch.SftpATTRS;
-import com.jcraft.jsch.SftpException;
-
 /**
  * Transport over the non-Git aware SFTP (SSH based FTP) protocol.
  * <p>
@@ -158,24 +153,16 @@
 		return r;
 	}
 
-	ChannelSftp newSftp() throws TransportException {
-		final int tms = getTimeout() > 0 ? getTimeout() * 1000 : 0;
-		try {
-			// @TODO: Fix so that this operation is generic and casting to
-			// JschSession is no longer necessary.
-			final Channel channel = ((JschSession) getSession())
-					.getSftpChannel();
-			channel.connect(tms);
-			return (ChannelSftp) channel;
-		} catch (JSchException je) {
-			throw new TransportException(uri, je.getMessage(), je);
-		}
+	FtpChannel newSftp() throws IOException {
+		FtpChannel channel = getSession().getFtpChannel();
+		channel.connect(getTimeout(), TimeUnit.SECONDS);
+		return channel;
 	}
 
 	class SftpObjectDB extends WalkRemoteObjectDatabase {
 		private final String objectsPath;
 
-		private ChannelSftp ftp;
+		private FtpChannel ftp;
 
 		SftpObjectDB(String path) throws TransportException {
 			if (path.startsWith("/~")) //$NON-NLS-1$
@@ -187,13 +174,13 @@
 				ftp.cd(path);
 				ftp.cd("objects"); //$NON-NLS-1$
 				objectsPath = ftp.pwd();
-			} catch (TransportException err) {
-				close();
-				throw err;
-			} catch (SftpException je) {
+			} catch (FtpChannel.FtpException f) {
 				throw new TransportException(MessageFormat.format(
 						JGitText.get().cannotEnterObjectsPath, path,
-						je.getMessage()), je);
+						f.getMessage()), f);
+			} catch (IOException ioe) {
+				close();
+				throw new TransportException(uri, ioe.getMessage(), ioe);
 			}
 		}
 
@@ -204,13 +191,13 @@
 				ftp.cd(parent.objectsPath);
 				ftp.cd(p);
 				objectsPath = ftp.pwd();
-			} catch (TransportException err) {
-				close();
-				throw err;
-			} catch (SftpException je) {
+			} catch (FtpChannel.FtpException f) {
 				throw new TransportException(MessageFormat.format(
 						JGitText.get().cannotEnterPathFromParent, p,
-						parent.objectsPath, je.getMessage()), je);
+						parent.objectsPath, f.getMessage()), f);
+			} catch (IOException ioe) {
+				close();
+				throw new TransportException(uri, ioe.getMessage(), ioe);
 			}
 		}
 
@@ -238,41 +225,32 @@
 		Collection<String> getPackNames() throws IOException {
 			final List<String> packs = new ArrayList<>();
 			try {
-				@SuppressWarnings("unchecked")
-				final Collection<ChannelSftp.LsEntry> list = ftp.ls("pack"); //$NON-NLS-1$
-				final HashMap<String, ChannelSftp.LsEntry> files;
-				final HashMap<String, Integer> mtimes;
+				Collection<FtpChannel.DirEntry> list = ftp.ls("pack"); //$NON-NLS-1$
+				Set<String> files = list.stream()
+						.map(FtpChannel.DirEntry::getFilename)
+						.collect(Collectors.toSet());
+				HashMap<String, Long> mtimes = new HashMap<>();
 
-				files = new HashMap<>();
-				mtimes = new HashMap<>();
-
-				for (ChannelSftp.LsEntry ent : list)
-					files.put(ent.getFilename(), ent);
-				for (ChannelSftp.LsEntry ent : list) {
-					final String n = ent.getFilename();
-					if (!n.startsWith("pack-") || !n.endsWith(".pack")) //$NON-NLS-1$ //$NON-NLS-2$
+				for (FtpChannel.DirEntry ent : list) {
+					String n = ent.getFilename();
+					if (!n.startsWith("pack-") || !n.endsWith(".pack")) { //$NON-NLS-1$ //$NON-NLS-2$
 						continue;
-
-					final String in = n.substring(0, n.length() - 5) + ".idx"; //$NON-NLS-1$
-					if (!files.containsKey(in))
+					}
+					String in = n.substring(0, n.length() - 5) + ".idx"; //$NON-NLS-1$
+					if (!files.contains(in)) {
 						continue;
-
-					mtimes.put(n, Integer.valueOf(ent.getAttrs().getMTime()));
+					}
+					mtimes.put(n, Long.valueOf(ent.getModifiedTime()));
 					packs.add(n);
 				}
 
-				Collections.sort(packs, new Comparator<String>() {
-					@Override
-					public int compare(String o1, String o2) {
-						return mtimes.get(o2).intValue()
-								- mtimes.get(o1).intValue();
-					}
-				});
-			} catch (SftpException je) {
+				Collections.sort(packs,
+						(o1, o2) -> mtimes.get(o2).compareTo(mtimes.get(o1)));
+			} catch (FtpChannel.FtpException f) {
 				throw new TransportException(
 						MessageFormat.format(JGitText.get().cannotListPackPath,
-								objectsPath, je.getMessage()),
-						je);
+								objectsPath, f.getMessage()),
+						f);
 			}
 			return packs;
 		}
@@ -280,27 +258,25 @@
 		@Override
 		FileStream open(String path) throws IOException {
 			try {
-				final SftpATTRS a = ftp.lstat(path);
-				return new FileStream(ftp.get(path), a.getSize());
-			} catch (SftpException je) {
-				if (je.id == ChannelSftp.SSH_FX_NO_SUCH_FILE)
+				return new FileStream(ftp.get(path));
+			} catch (FtpChannel.FtpException f) {
+				if (f.getStatus() == FtpChannel.FtpException.NO_SUCH_FILE) {
 					throw new FileNotFoundException(path);
+				}
 				throw new TransportException(MessageFormat.format(
 						JGitText.get().cannotGetObjectsPath, objectsPath, path,
-						je.getMessage()), je);
+						f.getMessage()), f);
 			}
 		}
 
 		@Override
 		void deleteFile(String path) throws IOException {
 			try {
-				ftp.rm(path);
-			} catch (SftpException je) {
-				if (je.id == ChannelSftp.SSH_FX_NO_SUCH_FILE)
-					return;
+				ftp.delete(path);
+			} catch (FtpChannel.FtpException f) {
 				throw new TransportException(MessageFormat.format(
 						JGitText.get().cannotDeleteObjectsPath, objectsPath,
-						path, je.getMessage()), je);
+						path, f.getMessage()), f);
 			}
 
 			// Prune any now empty directories.
@@ -312,7 +288,7 @@
 					dir = dir.substring(0, s);
 					ftp.rmdir(dir);
 					s = dir.lastIndexOf('/');
-				} catch (SftpException je) {
+				} catch (IOException je) {
 					// If we cannot delete it, leave it alone. It may have
 					// entries still in it, or maybe we lack write access on
 					// the parent. Either way it isn't a fatal error.
@@ -323,25 +299,31 @@
 		}
 
 		@Override
-		OutputStream writeFile(final String path,
-				final ProgressMonitor monitor, final String monitorTask)
-				throws IOException {
+		OutputStream writeFile(String path, ProgressMonitor monitor,
+				String monitorTask) throws IOException {
+			Throwable err = null;
 			try {
 				return ftp.put(path);
-			} catch (SftpException je) {
-				if (je.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
+			} catch (FileNotFoundException e) {
+				mkdir_p(path);
+			} catch (FtpChannel.FtpException je) {
+				if (je.getStatus() == FtpChannel.FtpException.NO_SUCH_FILE) {
 					mkdir_p(path);
-					try {
-						return ftp.put(path);
-					} catch (SftpException je2) {
-						je = je2;
-					}
+				} else {
+					err = je;
 				}
-
-				throw new TransportException(MessageFormat.format(
-						JGitText.get().cannotWriteObjectsPath, objectsPath,
-						path, je.getMessage()), je);
 			}
+			if (err == null) {
+				try {
+					return ftp.put(path);
+				} catch (IOException e) {
+					err = e;
+				}
+			}
+			throw new TransportException(
+					MessageFormat.format(JGitText.get().cannotWriteObjectsPath,
+							objectsPath, path, err.getMessage()),
+					err);
 		}
 
 		@Override
@@ -351,15 +333,15 @@
 				super.writeFile(lock, data);
 				try {
 					ftp.rename(lock, path);
-				} catch (SftpException je) {
+				} catch (IOException e) {
 					throw new TransportException(MessageFormat.format(
 							JGitText.get().cannotWriteObjectsPath, objectsPath,
-							path, je.getMessage()), je);
+							path, e.getMessage()), e);
 				}
 			} catch (IOException err) {
 				try {
 					ftp.rm(lock);
-				} catch (SftpException e) {
+				} catch (IOException e) {
 					// Ignore deletion failure, we are already
 					// failing anyway.
 				}
@@ -373,23 +355,30 @@
 				return;
 
 			path = path.substring(0, s);
+			Throwable err = null;
 			try {
 				ftp.mkdir(path);
-			} catch (SftpException je) {
-				if (je.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
+				return;
+			} catch (FileNotFoundException f) {
+				mkdir_p(path);
+			} catch (FtpChannel.FtpException je) {
+				if (je.getStatus() == FtpChannel.FtpException.NO_SUCH_FILE) {
 					mkdir_p(path);
-					try {
-						ftp.mkdir(path);
-						return;
-					} catch (SftpException je2) {
-						je = je2;
-					}
+				} else {
+					err = je;
 				}
-
-				throw new TransportException(MessageFormat.format(
-						JGitText.get().cannotMkdirObjectPath, objectsPath, path,
-						je.getMessage()), je);
 			}
+			if (err == null) {
+				try {
+					ftp.mkdir(path);
+					return;
+				} catch (IOException e) {
+					err = e;
+				}
+			}
+			throw new TransportException(MessageFormat.format(
+						JGitText.get().cannotMkdirObjectPath, objectsPath, path,
+					err.getMessage()), err);
 		}
 
 		Map<String, Ref> readAdvertisedRefs() throws TransportException {
@@ -400,34 +389,33 @@
 			return avail;
 		}
 
-		@SuppressWarnings("unchecked")
-		private void readLooseRefs(final TreeMap<String, Ref> avail,
-				final String dir, final String prefix)
-				throws TransportException {
-			final Collection<ChannelSftp.LsEntry> list;
+		private void readLooseRefs(TreeMap<String, Ref> avail, String dir,
+				String prefix) throws TransportException {
+			final Collection<FtpChannel.DirEntry> list;
 			try {
 				list = ftp.ls(dir);
-			} catch (SftpException je) {
+			} catch (IOException e) {
 				throw new TransportException(MessageFormat.format(
 						JGitText.get().cannotListObjectsPath, objectsPath, dir,
-						je.getMessage()), je);
+						e.getMessage()), e);
 			}
 
-			for (ChannelSftp.LsEntry ent : list) {
-				final String n = ent.getFilename();
+			for (FtpChannel.DirEntry ent : list) {
+				String n = ent.getFilename();
 				if (".".equals(n) || "..".equals(n)) //$NON-NLS-1$ //$NON-NLS-2$
 					continue;
 
-				final String nPath = dir + "/" + n; //$NON-NLS-1$
-				if (ent.getAttrs().isDir())
+				String nPath = dir + "/" + n; //$NON-NLS-1$
+				if (ent.isDirectory()) {
 					readLooseRefs(avail, nPath, prefix + n + "/"); //$NON-NLS-1$
-				else
+				} else {
 					readRef(avail, nPath, prefix + n);
+				}
 			}
 		}
 
-		private Ref readRef(final TreeMap<String, Ref> avail,
-				final String path, final String name) throws TransportException {
+		private Ref readRef(TreeMap<String, Ref> avail, String path,
+				String name) throws TransportException {
 			final String line;
 			try (BufferedReader br = openReader(path)) {
 				line = br.readLine();
@@ -439,10 +427,10 @@
 						err.getMessage()), err);
 			}
 
-			if (line == null)
+			if (line == null) {
 				throw new TransportException(
 						MessageFormat.format(JGitText.get().emptyRef, name));
-
+			}
 			if (line.startsWith("ref: ")) { //$NON-NLS-1$
 				final String target = line.substring("ref: ".length()); //$NON-NLS-1$
 				Ref r = avail.get(target);
@@ -467,8 +455,9 @@
 		}
 
 		private Storage loose(Ref r) {
-			if (r != null && r.getStorage() == Storage.PACKED)
+			if (r != null && r.getStorage() == Storage.PACKED) {
 				return Storage.LOOSE_PACKED;
+			}
 			return Storage.LOOSE;
 		}
 
@@ -476,8 +465,9 @@
 		void close() {
 			if (ftp != null) {
 				try {
-					if (ftp.isConnected())
+					if (ftp.isConnected()) {
 						ftp.disconnect();
+					}
 				} finally {
 					ftp = null;
 				}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
index 026fd81..70fb1f0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
@@ -48,10 +48,11 @@
 
 package org.eclipse.jgit.transport;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.Serializable;
-import java.io.UnsupportedEncodingException;
 import java.net.URISyntaxException;
 import java.net.URL;
 import java.util.BitSet;
@@ -282,12 +283,7 @@
 		if (s.indexOf('%') < 0)
 			return s;
 
-		byte[] bytes;
-		try {
-			bytes = s.getBytes(Constants.CHARACTER_ENCODING);
-		} catch (UnsupportedEncodingException e) {
-			throw new RuntimeException(e); // can't happen
-		}
+		byte[] bytes = s.getBytes(UTF_8);
 
 		byte[] os = new byte[bytes.length];
 		int j = 0;
@@ -335,12 +331,7 @@
 		if (s == null)
 			return null;
 		ByteArrayOutputStream os = new ByteArrayOutputStream(s.length());
-		byte[] bytes;
-		try {
-			bytes = s.getBytes(Constants.CHARACTER_ENCODING);
-		} catch (UnsupportedEncodingException e) {
-			throw new RuntimeException(e); // cannot happen
-		}
+		byte[] bytes = s.getBytes(UTF_8);
 		for (int i = 0; i < bytes.length; ++i) {
 			int b = bytes[i] & 0xFF;
 			if (b <= 32 || (encodeNonAscii && b > 127) || b == '%'
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 48a3e0b..2fbcaa2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
@@ -49,6 +49,7 @@
 import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_REF_IN_WANT;
 import static org.eclipse.jgit.transport.GitProtocolConstants.COMMAND_FETCH;
 import static org.eclipse.jgit.transport.GitProtocolConstants.COMMAND_LS_REFS;
+import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_SERVER_OPTION;
 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_ALLOW_REACHABLE_SHA1_IN_WANT;
 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_ALLOW_TIP_SHA1_IN_WANT;
@@ -70,11 +71,11 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.io.UncheckedIOException;
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -88,6 +89,7 @@
 import org.eclipse.jgit.errors.PackProtocolException;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.internal.storage.pack.PackWriter;
+import org.eclipse.jgit.internal.transport.parser.FirstWant;
 import org.eclipse.jgit.lib.BitmapIndex;
 import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder;
 import org.eclipse.jgit.lib.Constants;
@@ -96,6 +98,7 @@
 import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.AsyncRevObjectQueue;
 import org.eclipse.jgit.revwalk.BitmapWalker;
@@ -179,44 +182,53 @@
 				throws PackProtocolException, IOException;
 	}
 
-	/** Data in the first line of a request, the line itself plus options. */
+	/**
+	 * Data in the first line of a want-list, the line itself plus options.
+	 *
+	 * @deprecated Use {@link FirstWant} instead
+	 */
+	@Deprecated
 	public static class FirstLine {
-		private final String line;
-		private final Set<String> options;
+
+		private final FirstWant firstWant;
 
 		/**
-		 * Parse the first line of a receive-pack request.
-		 *
 		 * @param line
 		 *            line from the client.
 		 */
 		public FirstLine(String line) {
-			if (line.length() > 45) {
-				final HashSet<String> opts = new HashSet<>();
-				String opt = line.substring(45);
-				if (opt.startsWith(" ")) //$NON-NLS-1$
-					opt = opt.substring(1);
-				for (String c : opt.split(" ")) //$NON-NLS-1$
-					opts.add(c);
-				this.line = line.substring(0, 45);
-				this.options = Collections.unmodifiableSet(opts);
-			} else {
-				this.line = line;
-				this.options = Collections.emptySet();
+			try {
+				firstWant = FirstWant.fromLine(line);
+			} catch (PackProtocolException e) {
+				throw new UncheckedIOException(e);
 			}
 		}
 
 		/** @return non-capabilities part of the line. */
 		public String getLine() {
-			return line;
+			return firstWant.getLine();
 		}
 
-		/** @return options parsed from the line. */
+		/** @return capabilities parsed from the line. */
 		public Set<String> getOptions() {
-			return options;
+			if (firstWant.getAgent() != null) {
+				Set<String> caps = new HashSet<>(firstWant.getCapabilities());
+				caps.add(OPTION_AGENT + '=' + firstWant.getAgent());
+				return caps;
+			}
+			return firstWant.getCapabilities();
 		}
 	}
 
+	/*
+	 * {@link java.util.function.Consumer} doesn't allow throwing checked
+	 * exceptions. Define our own to propagate IOExceptions.
+	 */
+	@FunctionalInterface
+	private static interface IOConsumer<R> {
+		void accept(R t) throws IOException;
+	}
+
 	/** Database we read the objects from. */
 	private final Repository db;
 
@@ -288,12 +300,11 @@
 	/** Hook for taking post upload actions. */
 	private PostUploadHook postUploadHook = PostUploadHook.NULL;
 
-	/** Capabilities requested by the client. */
-	private Set<String> options;
+	/** Caller user agent */
 	String userAgent;
 
 	/** Raw ObjectIds the client has asked for, before validating them. */
-	private final Set<ObjectId> wantIds = new HashSet<>();
+	private Set<ObjectId> wantIds = new HashSet<>();
 
 	/** Objects the client wants to obtain. */
 	private final Set<RevObject> wantAll = new HashSet<>();
@@ -301,25 +312,6 @@
 	/** Objects on both sides, these don't have to be sent. */
 	private final Set<RevObject> commonBase = new HashSet<>();
 
-	/** Shallow commits the client already has. */
-	private Set<ObjectId> clientShallowCommits = new HashSet<>();
-
-	/** Desired depth from the client on a shallow request. */
-	private int depth;
-
-	/**
-	 * Commit time of the newest objects the client has asked us using
-	 * --shallow-since not to send. Cannot be nonzero if depth is nonzero.
-	 */
-	private int shallowSince;
-
-	/**
-	 * (Possibly short) ref names, ancestors of which the client has asked us
-	 * not to send using --shallow-exclude. Cannot be non-empty if depth is
-	 * nonzero.
-	 */
-	private List<String> deepenNotRefs = new ArrayList<>();
-
 	/** Commit time of the oldest common commit, in seconds. */
 	private int oldestTime;
 
@@ -353,7 +345,14 @@
 
 	private PackStatistics statistics;
 
-	private long filterBlobLimit = -1;
+	/**
+	 * Request this instance is handling.
+	 *
+	 * We need to keep a reference to it for {@link PreUploadHook pre upload
+	 * hooks}. They receive a reference this instance and invoke methods like
+	 * getDepth() to get information about the request.
+	 */
+	private FetchRequest currentRequest;
 
 	/**
 	 * Create a new pack upload for an open repository.
@@ -695,10 +694,12 @@
 	 *             read.
 	 */
 	public boolean isSideBand() throws RequestNotYetReadException {
-		if (options == null)
+		if (currentRequest == null) {
 			throw new RequestNotYetReadException();
-		return (options.contains(OPTION_SIDE_BAND)
-				|| options.contains(OPTION_SIDE_BAND_64K));
+		}
+		Set<String> caps = currentRequest.getClientCapabilities();
+		return caps.contains(OPTION_SIDE_BAND)
+				|| caps.contains(OPTION_SIDE_BAND_64K);
 	}
 
 	/**
@@ -829,12 +830,10 @@
 		}
 		if (refs == null) {
 			// Fast path: the advertised refs hook did not set advertised refs.
-			Map<String, Ref> rs = new HashMap<>();
-			for (String p : refPrefixes) {
-				for (Ref r : db.getRefDatabase().getRefsByPrefix(p)) {
-					rs.put(r.getName(), r);
-				}
-			}
+			String[] prefixes = refPrefixes.toArray(new String[0]);
+			Map<String, Ref> rs =
+					db.getRefDatabase().getRefsByPrefix(prefixes).stream()
+						.collect(toMap(Ref::getName, identity(), (a, b) -> b));
 			if (refFilter != RefFilter.DEFAULT) {
 				return refFilter.filter(rs);
 			}
@@ -880,12 +879,45 @@
 		return getAdvertisedOrDefaultRefs().get(name);
 	}
 
+	/**
+	 * Find a ref in the usual search path on behalf of the client.
+	 * <p>
+	 * This checks that the ref is present in the ref advertisement since
+	 * otherwise the client might not be supposed to be able to read it.
+	 *
+	 * @param name
+	 *            short name of the ref to find, e.g. "master" to find
+	 *            "refs/heads/master".
+	 * @return the requested Ref, or {@code null} if it is not visible or
+	 *         does not exist.
+	 * @throws java.io.IOException
+	 *            on failure to read the ref or check it for visibility.
+	 */
+	@Nullable
+	private Ref findRef(String name) throws IOException {
+		if (refs != null) {
+			return RefDatabase.findRef(refs, name);
+		}
+		if (!advertiseRefsHookCalled) {
+			advertiseRefsHook.advertiseRefs(this);
+			advertiseRefsHookCalled = true;
+		}
+		if (refs == null &&
+				refFilter == RefFilter.DEFAULT &&
+				transferConfig.hasDefaultRefFilter()) {
+			// Fast path: no ref filtering is needed.
+			return db.getRefDatabase().getRef(name);
+		}
+		return RefDatabase.findRef(getAdvertisedOrDefaultRefs(), name);
+	}
+
 	private void service() throws IOException {
 		boolean sendPack = false;
 		// If it's a non-bidi request, we need to read the entire request before
 		// writing a response. Buffer the response until then.
 		PackStatistics.Accumulator accumulator = new PackStatistics.Accumulator();
 		List<ObjectId> unshallowCommits = new ArrayList<>();
+		FetchRequest req;
 		try {
 			if (biDirectionalPipe)
 				sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut));
@@ -896,29 +928,46 @@
 
 			long negotiateStart = System.currentTimeMillis();
 			accumulator.advertised = advertised.size();
-			recvWants();
-			if (wantIds.isEmpty()) {
-				preUploadHook.onBeginNegotiateRound(this, wantIds, 0);
-				preUploadHook.onEndNegotiateRound(this, wantIds, 0, 0, false);
+
+			ProtocolV0Parser parser = new ProtocolV0Parser(transferConfig);
+			req = parser.recvWants(pckIn);
+			currentRequest = req;
+
+			wantIds = req.getWantIds();
+
+			if (req.getWantIds().isEmpty()) {
+				preUploadHook.onBeginNegotiateRound(this, req.getWantIds(), 0);
+				preUploadHook.onEndNegotiateRound(this, req.getWantIds(), 0, 0,
+						false);
 				return;
 			}
-			accumulator.wants = wantIds.size();
+			accumulator.wants = req.getWantIds().size();
 
-			if (options.contains(OPTION_MULTI_ACK_DETAILED)) {
+			if (req.getClientCapabilities().contains(OPTION_MULTI_ACK_DETAILED)) {
 				multiAck = MultiAck.DETAILED;
-				noDone = options.contains(OPTION_NO_DONE);
-			} else if (options.contains(OPTION_MULTI_ACK))
+				noDone = req.getClientCapabilities().contains(OPTION_NO_DONE);
+			} else if (req.getClientCapabilities().contains(OPTION_MULTI_ACK))
 				multiAck = MultiAck.CONTINUE;
 			else
 				multiAck = MultiAck.OFF;
 
-			if (!clientShallowCommits.isEmpty())
-				verifyClientShallow(clientShallowCommits);
-			if (depth != 0)
-				processShallow(null, unshallowCommits, true);
-			if (!clientShallowCommits.isEmpty())
-				walk.assumeShallow(clientShallowCommits);
-			sendPack = negotiate(accumulator);
+			if (!req.getClientShallowCommits().isEmpty()) {
+				verifyClientShallow(req.getClientShallowCommits());
+			}
+
+			if (req.getDepth() != 0 || req.getDeepenSince() != 0) {
+				computeShallowsAndUnshallows(req, shallow -> {
+					pckOut.writeString("shallow " + shallow.name() + '\n'); //$NON-NLS-1$
+				}, unshallow -> {
+					pckOut.writeString("unshallow " + unshallow.name() + '\n'); //$NON-NLS-1$
+					unshallowCommits.add(unshallow);
+				}, Collections.emptyList());
+				pckOut.end();
+			}
+
+			if (!req.getClientShallowCommits().isEmpty())
+				walk.assumeShallow(req.getClientShallowCommits());
+			sendPack = negotiate(req, accumulator);
 			accumulator.timeNegotiating += System.currentTimeMillis()
 					- negotiateStart;
 
@@ -968,35 +1017,14 @@
 		}
 
 		if (sendPack) {
-			sendPack(accumulator, refs == null ? null : refs.values(), unshallowCommits);
+			sendPack(accumulator, req, refs == null ? null : refs.values(),
+					unshallowCommits, Collections.emptyList());
 		}
 	}
 
 	private void lsRefsV2() throws IOException {
-		LsRefsV2Request.Builder builder = LsRefsV2Request.builder();
-		List<String> prefixes = new ArrayList<>();
-		String line = pckIn.readString();
-		// Currently, we do not support any capabilities, so the next
-		// line is DELIM if there are arguments or END if not.
-		if (line == PacketLineIn.DELIM) {
-			while ((line = pckIn.readString()) != PacketLineIn.END) {
-				if (line.equals("peel")) { //$NON-NLS-1$
-					builder.setPeel(true);
-				} else if (line.equals("symrefs")) { //$NON-NLS-1$
-					builder.setSymrefs(true);
-				} else if (line.startsWith("ref-prefix ")) { //$NON-NLS-1$
-					prefixes.add(line.substring("ref-prefix ".length())); //$NON-NLS-1$
-				} else {
-					throw new PackProtocolException(MessageFormat
-							.format(JGitText.get().unexpectedPacketLine, line));
-				}
-			}
-		} else if (line != PacketLineIn.END) {
-			throw new PackProtocolException(MessageFormat
-					.format(JGitText.get().unexpectedPacketLine, line));
-		}
-		LsRefsV2Request req = builder.setRefPrefixes(prefixes).build();
-
+		ProtocolV2Parser parser = new ProtocolV2Parser(transferConfig);
+		LsRefsV2Request req = parser.parseLsRefsRequest(pckIn);
 		protocolV2Hook.onLsRefs(req);
 
 		rawOut.stopBuffering();
@@ -1029,20 +1057,23 @@
 
 		ProtocolV2Parser parser = new ProtocolV2Parser(transferConfig);
 		FetchV2Request req = parser.parseFetchRequest(pckIn);
+		currentRequest = req;
 		rawOut.stopBuffering();
 
 		protocolV2Hook.onFetch(req);
 
 		// TODO(ifrade): Refactor to pass around the Request object, instead of
 		// copying data back to class fields
-		options = req.getOptions();
-		clientShallowCommits = req.getClientShallowCommits();
-		depth = req.getDepth();
-		shallowSince = req.getDeepenSince();
-		filterBlobLimit = req.getFilterBlobLimit();
-		deepenNotRefs = req.getDeepenNotRefs();
+		List<ObjectId> deepenNots = new ArrayList<>();
+		for (String s : req.getDeepenNotRefs()) {
+			Ref ref = findRef(s);
+			if (ref == null) {
+				throw new PackProtocolException(MessageFormat
+						.format(JGitText.get().invalidRefName, s));
+			}
+			deepenNots.add(ref.getObjectId());
+		}
 
-		wantIds.addAll(req.getWantsIds());
 		Map<String, ObjectId> wantedRefs = new TreeMap<>();
 		for (String refName : req.getWantedRefs()) {
 			Ref ref = getRef(refName);
@@ -1055,21 +1086,27 @@
 				throw new PackProtocolException(MessageFormat
 						.format(JGitText.get().invalidRefName, refName));
 			}
-			wantIds.add(oid);
+			// TODO(ifrade): Avoid mutating the parsed request.
+			req.getWantIds().add(oid);
 			wantedRefs.put(refName, oid);
 		}
+		wantIds = req.getWantIds();
 
 		boolean sectionSent = false;
-		@Nullable List<ObjectId> shallowCommits = null;
+		boolean mayHaveShallow = req.getDepth() != 0
+				|| req.getDeepenSince() != 0
+				|| !req.getDeepenNotRefs().isEmpty();
+		List<ObjectId> shallowCommits = new ArrayList<>();
 		List<ObjectId> unshallowCommits = new ArrayList<>();
 
 		if (!req.getClientShallowCommits().isEmpty()) {
 			verifyClientShallow(req.getClientShallowCommits());
 		}
-		if (req.getDepth() != 0 || req.getDeepenSince() != 0
-				|| !req.getDeepenNotRefs().isEmpty()) {
-			shallowCommits = new ArrayList<>();
-			processShallow(shallowCommits, unshallowCommits, false);
+		if (mayHaveShallow) {
+			computeShallowsAndUnshallows(req,
+					shallowCommit -> shallowCommits.add(shallowCommit),
+					unshallowCommit -> unshallowCommits.add(unshallowCommit),
+					deepenNots);
 		}
 		if (!req.getClientShallowCommits().isEmpty())
 			walk.assumeShallow(req.getClientShallowCommits());
@@ -1095,7 +1132,7 @@
 		}
 
 		if (req.wasDoneReceived() || okToGiveUp()) {
-			if (shallowCommits != null) {
+			if (mayHaveShallow) {
 				if (sectionSent)
 					pckOut.writeDelim();
 				pckOut.writeString("shallow-info\n"); //$NON-NLS-1$
@@ -1125,10 +1162,11 @@
 				pckOut.writeDelim();
 			pckOut.writeString("packfile\n"); //$NON-NLS-1$
 			sendPack(new PackStatistics.Accumulator(),
-					req.getOptions().contains(OPTION_INCLUDE_TAG)
+					req,
+					req.getClientCapabilities().contains(OPTION_INCLUDE_TAG)
 						? db.getRefDatabase().getRefsByPrefix(R_TAGS)
 						: null,
-					unshallowCommits);
+					unshallowCommits, deepenNots);
 			// sendPack invokes pckOut.end() for us, so we do not
 			// need to invoke it here.
 		} else {
@@ -1179,6 +1217,7 @@
 				(transferConfig.isAllowFilter() ? OPTION_FILTER + ' ' : "") + //$NON-NLS-1$
 				(advertiseRefInWant ? CAPABILITY_REF_IN_WANT + ' ' : "") + //$NON-NLS-1$
 				OPTION_SHALLOW);
+		caps.add(CAPABILITY_SERVER_OPTION);
 		return caps;
 	}
 
@@ -1227,28 +1266,28 @@
 	}
 
 	/*
-	 * Determines what "shallow" and "unshallow" lines to send to the user.
-	 * The information is written to shallowCommits (if not null) and
-	 * unshallowCommits, and also written to #pckOut (if writeToPckOut is
-	 * true).
+	 * Determines what object ids must be marked as shallow or unshallow for the
+	 * client.
 	 */
-	private void processShallow(@Nullable List<ObjectId> shallowCommits,
-			List<ObjectId> unshallowCommits,
-			boolean writeToPckOut) throws IOException {
-		if (options.contains(OPTION_DEEPEN_RELATIVE) ||
-				shallowSince != 0 ||
-				!deepenNotRefs.isEmpty()) {
-			// TODO(jonathantanmy): Implement deepen-relative, deepen-since,
-			// and deepen-not.
+	private void computeShallowsAndUnshallows(FetchRequest req,
+			IOConsumer<ObjectId> shallowFunc,
+			IOConsumer<ObjectId> unshallowFunc,
+			List<ObjectId> deepenNots)
+			throws IOException {
+		if (req.getClientCapabilities().contains(OPTION_DEEPEN_RELATIVE)) {
+			// TODO(jonathantanmy): Implement deepen-relative
 			throw new UnsupportedOperationException();
 		}
 
-		int walkDepth = depth - 1;
+		int walkDepth = req.getDepth() == 0 ? Integer.MAX_VALUE
+				: req.getDepth() - 1;
 		try (DepthWalk.RevWalk depthWalk = new DepthWalk.RevWalk(
 				walk.getObjectReader(), walkDepth)) {
 
+			depthWalk.setDeepenSince(req.getDeepenSince());
+
 			// Find all the commits which will be shallow
-			for (ObjectId o : wantIds) {
+			for (ObjectId o : req.getWantIds()) {
 				try {
 					depthWalk.markRoot(depthWalk.parseCommit(o));
 				} catch (IncorrectObjectTypeException notCommit) {
@@ -1256,35 +1295,32 @@
 				}
 			}
 
+			depthWalk.setDeepenNots(deepenNots);
+
 			RevCommit o;
+			boolean atLeastOne = false;
 			while ((o = depthWalk.next()) != null) {
 				DepthWalk.Commit c = (DepthWalk.Commit) o;
+				atLeastOne = true;
+
+				boolean isBoundary = (c.getDepth() == walkDepth) || c.isBoundary();
 
 				// Commits at the boundary which aren't already shallow in
 				// the client need to be marked as such
-				if (c.getDepth() == walkDepth
-						&& !clientShallowCommits.contains(c)) {
-					if (shallowCommits != null) {
-						shallowCommits.add(c.copy());
-					}
-					if (writeToPckOut) {
-						pckOut.writeString("shallow " + o.name()); //$NON-NLS-1$
-					}
+				if (isBoundary && !req.getClientShallowCommits().contains(c)) {
+					shallowFunc.accept(c.copy());
 				}
 
 				// Commits not on the boundary which are shallow in the client
 				// need to become unshallowed
-				if (c.getDepth() < walkDepth
-						&& clientShallowCommits.remove(c)) {
-					unshallowCommits.add(c.copy());
-					if (writeToPckOut) {
-						pckOut.writeString("unshallow " + c.name()); //$NON-NLS-1$
-					}
+				if (!isBoundary && req.getClientShallowCommits().remove(c)) {
+					unshallowFunc.accept(c.copy());
 				}
 			}
-		}
-		if (writeToPckOut) {
-			pckOut.end();
+			if (!atLeastOne) {
+				throw new PackProtocolException(
+					JGitText.get().noCommitsSelectedForShallow);
+			}
 		}
 	}
 
@@ -1436,67 +1472,6 @@
 		return msgOut;
 	}
 
-	private void recvWants() throws IOException {
-		boolean isFirst = true;
-		boolean filterReceived = false;
-		for (;;) {
-			String line;
-			try {
-				line = pckIn.readString();
-			} catch (EOFException eof) {
-				if (isFirst)
-					break;
-				throw eof;
-			}
-
-			if (line == PacketLineIn.END)
-				break;
-
-			if (line.startsWith("deepen ")) { //$NON-NLS-1$
-				depth = Integer.parseInt(line.substring(7));
-				if (depth <= 0) {
-					throw new PackProtocolException(
-							MessageFormat.format(JGitText.get().invalidDepth,
-									Integer.valueOf(depth)));
-				}
-				continue;
-			}
-
-			if (line.startsWith("shallow ")) { //$NON-NLS-1$
-				clientShallowCommits.add(ObjectId.fromString(line.substring(8)));
-				continue;
-			}
-
-			if (transferConfig.isAllowFilter()
-					&& line.startsWith(OPTION_FILTER + " ")) { //$NON-NLS-1$
-				String arg = line.substring(OPTION_FILTER.length() + 1);
-
-				if (filterReceived) {
-					throw new PackProtocolException(JGitText.get().tooManyFilters);
-				}
-				filterReceived = true;
-
-				filterBlobLimit = ProtocolV2Parser.filterLine(arg);
-				continue;
-			}
-
-			if (!line.startsWith("want ") || line.length() < 45) //$NON-NLS-1$
-				throw new PackProtocolException(MessageFormat.format(JGitText.get().expectedGot, "want", line)); //$NON-NLS-1$
-
-			if (isFirst) {
-				if (line.length() > 45) {
-					FirstLine firstLine = new FirstLine(line);
-					options = firstLine.getOptions();
-					line = firstLine.getLine();
-				} else
-					options = Collections.emptySet();
-			}
-
-			wantIds.add(ObjectId.fromString(line.substring(5)));
-			isFirst = false;
-		}
-	}
-
 	/**
 	 * Returns the clone/fetch depth. Valid only after calling recvWants(). A
 	 * depth of 1 means return only the wants.
@@ -1505,9 +1480,9 @@
 	 * @since 4.0
 	 */
 	public int getDepth() {
-		if (options == null)
+		if (currentRequest == null)
 			throw new RequestNotYetReadException();
-		return depth;
+		return currentRequest.getDepth();
 	}
 
 	/**
@@ -1526,10 +1501,15 @@
 	 * @since 4.0
 	 */
 	public String getPeerUserAgent() {
-		return UserAgent.getAgent(options, userAgent);
+		if (currentRequest != null && currentRequest.getAgent() != null) {
+			return currentRequest.getAgent();
+		}
+
+		return userAgent;
 	}
 
-	private boolean negotiate(PackStatistics.Accumulator accumulator)
+	private boolean negotiate(FetchRequest req,
+			PackStatistics.Accumulator accumulator)
 			throws IOException {
 		okToGiveUp = Boolean.FALSE;
 
@@ -1545,7 +1525,7 @@
 				// disconnected, and will try another request with actual want/have.
 				// Don't report the EOF here, its a bug in the protocol that the client
 				// just disconnects without sending an END.
-				if (!biDirectionalPipe && depth > 0)
+				if (!biDirectionalPipe && req.getDepth() > 0)
 					return false;
 				throw eof;
 			}
@@ -1926,25 +1906,31 @@
 	 * Send the requested objects to the client.
 	 *
 	 * @param accumulator
-	 *                where to write statistics about the content of the pack.
+	 *            where to write statistics about the content of the pack.
+	 * @param req
+	 *            request in process
 	 * @param allTags
-	 *                refs to search for annotated tags to include in the pack
-	 *                if the {@link #OPTION_INCLUDE_TAG} capability was
-	 *                requested.
+	 *            refs to search for annotated tags to include in the pack if
+	 *            the {@link #OPTION_INCLUDE_TAG} capability was requested.
 	 * @param unshallowCommits
-	 *                shallow commits on the client that are now becoming
-	 *                unshallow
+	 *            shallow commits on the client that are now becoming unshallow
+	 * @param deepenNots
+	 *            objects that the client specified using --shallow-exclude
 	 * @throws IOException
-	 *                if an error occured while generating or writing the pack.
+	 *             if an error occurred while generating or writing the pack.
 	 */
 	private void sendPack(PackStatistics.Accumulator accumulator,
+			FetchRequest req,
 			@Nullable Collection<Ref> allTags,
-			List<ObjectId> unshallowCommits) throws IOException {
-		final boolean sideband = options.contains(OPTION_SIDE_BAND)
-				|| options.contains(OPTION_SIDE_BAND_64K);
+			List<ObjectId> unshallowCommits,
+			List<ObjectId> deepenNots) throws IOException {
+		Set<String> caps = req.getClientCapabilities();
+		boolean sideband = caps.contains(OPTION_SIDE_BAND)
+				|| caps.contains(OPTION_SIDE_BAND_64K);
 		if (sideband) {
 			try {
-				sendPack(true, accumulator, allTags, unshallowCommits);
+				sendPack(true, req, accumulator, allTags, unshallowCommits,
+						deepenNots);
 			} catch (ServiceMayNotContinueException noPack) {
 				// This was already reported on (below).
 				throw noPack;
@@ -1965,7 +1951,7 @@
 					throw err;
 			}
 		} else {
-			sendPack(false, accumulator, allTags, unshallowCommits);
+			sendPack(false, req, accumulator, allTags, unshallowCommits, deepenNots);
 		}
 	}
 
@@ -1989,35 +1975,39 @@
 	 * Send the requested objects to the client.
 	 *
 	 * @param sideband
-	 *                whether to wrap the pack in side-band pkt-lines,
-	 *                interleaved with progress messages and errors.
+	 *            whether to wrap the pack in side-band pkt-lines, interleaved
+	 *            with progress messages and errors.
+	 * @param req
+	 *            request being processed
 	 * @param accumulator
-	 *                where to write statistics about the content of the pack.
+	 *            where to write statistics about the content of the pack.
 	 * @param allTags
-	 *                refs to search for annotated tags to include in the pack
-	 *                if the {@link #OPTION_INCLUDE_TAG} capability was
-	 *                requested.
+	 *            refs to search for annotated tags to include in the pack if
+	 *            the {@link #OPTION_INCLUDE_TAG} capability was requested.
 	 * @param unshallowCommits
-	 *                shallow commits on the client that are now becoming
-	 *                unshallow
+	 *            shallow commits on the client that are now becoming unshallow
+	 * @param deepenNots
+	 *            objects that the client specified using --shallow-exclude
 	 * @throws IOException
-	 *                if an error occured while generating or writing the pack.
+	 *             if an error occurred while generating or writing the pack.
 	 */
 	private void sendPack(final boolean sideband,
+			FetchRequest req,
 			PackStatistics.Accumulator accumulator,
 			@Nullable Collection<Ref> allTags,
-			List<ObjectId> unshallowCommits) throws IOException {
+			List<ObjectId> unshallowCommits,
+			List<ObjectId> deepenNots) throws IOException {
 		ProgressMonitor pm = NullProgressMonitor.INSTANCE;
 		OutputStream packOut = rawOut;
 
 		if (sideband) {
 			int bufsz = SideBandOutputStream.SMALL_BUF;
-			if (options.contains(OPTION_SIDE_BAND_64K))
+			if (req.getClientCapabilities().contains(OPTION_SIDE_BAND_64K))
 				bufsz = SideBandOutputStream.MAX_BUF;
 
 			packOut = new SideBandOutputStream(SideBandOutputStream.CH_DATA,
 					bufsz, rawOut);
-			if (!options.contains(OPTION_NO_PROGRESS)) {
+			if (!req.getClientCapabilities().contains(OPTION_NO_PROGRESS)) {
 				msgOut = new SideBandOutputStream(
 						SideBandOutputStream.CH_PROGRESS, bufsz, rawOut);
 				pm = new SideBandProgressMonitor(msgOut);
@@ -2053,17 +2043,20 @@
 				accumulator);
 		try {
 			pw.setIndexDisabled(true);
-			if (filterBlobLimit >= 0) {
-				pw.setFilterBlobLimit(filterBlobLimit);
+			if (req.getFilterBlobLimit() >= 0) {
+				pw.setFilterBlobLimit(req.getFilterBlobLimit());
 				pw.setUseCachedPacks(false);
 			} else {
 				pw.setUseCachedPacks(true);
 			}
-			pw.setUseBitmaps(depth == 0 && clientShallowCommits.isEmpty());
-			pw.setClientShallowCommits(clientShallowCommits);
+			pw.setUseBitmaps(
+					req.getDepth() == 0
+							&& req.getClientShallowCommits().isEmpty());
+			pw.setClientShallowCommits(req.getClientShallowCommits());
 			pw.setReuseDeltaCommits(true);
-			pw.setDeltaBaseAsOffset(options.contains(OPTION_OFS_DELTA));
-			pw.setThin(options.contains(OPTION_THIN_PACK));
+			pw.setDeltaBaseAsOffset(
+					req.getClientCapabilities().contains(OPTION_OFS_DELTA));
+			pw.setThin(req.getClientCapabilities().contains(OPTION_THIN_PACK));
 			pw.setReuseValidatingObjects(false);
 
 			// Objects named directly by references go at the beginning
@@ -2082,14 +2075,22 @@
 			}
 
 			RevWalk rw = walk;
-			if (depth > 0) {
-				pw.setShallowPack(depth, unshallowCommits);
-				rw = new DepthWalk.RevWalk(walk.getObjectReader(), depth - 1);
-				rw.assumeShallow(clientShallowCommits);
+			if (req.getDepth() > 0 || req.getDeepenSince() != 0 || !deepenNots.isEmpty()) {
+				int walkDepth = req.getDepth() == 0 ? Integer.MAX_VALUE
+						: req.getDepth() - 1;
+				pw.setShallowPack(req.getDepth(), unshallowCommits);
+
+				DepthWalk.RevWalk dw = new DepthWalk.RevWalk(
+						walk.getObjectReader(), walkDepth);
+				dw.setDeepenSince(req.getDeepenSince());
+				dw.setDeepenNots(deepenNots);
+				dw.assumeShallow(req.getClientShallowCommits());
+				rw = dw;
 			}
 
 			if (wantAll.isEmpty()) {
-				pw.preparePack(pm, wantIds, commonBase, clientShallowCommits);
+				pw.preparePack(pm, wantIds, commonBase,
+						req.getClientShallowCommits());
 			} else {
 				walk.reset();
 
@@ -2098,7 +2099,8 @@
 				rw = ow;
 			}
 
-			if (options.contains(OPTION_INCLUDE_TAG) && allTags != null) {
+			if (req.getClientCapabilities().contains(OPTION_INCLUDE_TAG)
+					&& allTags != null) {
 				for (Ref ref : allTags) {
 					ObjectId objectId = ref.getObjectId();
 					if (objectId == null) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java
index d815bc3..c38b002 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java
@@ -58,6 +58,8 @@
 import javax.net.ssl.KeyManager;
 import javax.net.ssl.TrustManager;
 
+import org.eclipse.jgit.annotations.NonNull;
+
 /**
  * The interface of connections used during HTTP communication. This interface
  * is that subset of the interface exposed by {@link java.net.HttpURLConnection}
@@ -69,25 +71,25 @@
 	/**
 	 * @see HttpURLConnection#HTTP_OK
 	 */
-	public static final int HTTP_OK = java.net.HttpURLConnection.HTTP_OK;
+	int HTTP_OK = java.net.HttpURLConnection.HTTP_OK;
 
 	/**
 	 * @see HttpURLConnection#HTTP_MOVED_PERM
 	 * @since 4.7
 	 */
-	public static final int HTTP_MOVED_PERM = java.net.HttpURLConnection.HTTP_MOVED_PERM;
+	int HTTP_MOVED_PERM = java.net.HttpURLConnection.HTTP_MOVED_PERM;
 
 	/**
 	 * @see HttpURLConnection#HTTP_MOVED_TEMP
 	 * @since 4.9
 	 */
-	public static final int HTTP_MOVED_TEMP = java.net.HttpURLConnection.HTTP_MOVED_TEMP;
+	int HTTP_MOVED_TEMP = java.net.HttpURLConnection.HTTP_MOVED_TEMP;
 
 	/**
 	 * @see HttpURLConnection#HTTP_SEE_OTHER
 	 * @since 4.9
 	 */
-	public static final int HTTP_SEE_OTHER = java.net.HttpURLConnection.HTTP_SEE_OTHER;
+	int HTTP_SEE_OTHER = java.net.HttpURLConnection.HTTP_SEE_OTHER;
 
 	/**
 	 * HTTP 1.1 additional MOVED_TEMP status code; value = 307.
@@ -95,22 +97,22 @@
 	 * @see #HTTP_MOVED_TEMP
 	 * @since 4.9
 	 */
-	public static final int HTTP_11_MOVED_TEMP = 307;
+	int HTTP_11_MOVED_TEMP = 307;
 
 	/**
 	 * @see HttpURLConnection#HTTP_NOT_FOUND
 	 */
-	public static final int HTTP_NOT_FOUND = java.net.HttpURLConnection.HTTP_NOT_FOUND;
+	int HTTP_NOT_FOUND = java.net.HttpURLConnection.HTTP_NOT_FOUND;
 
 	/**
 	 * @see HttpURLConnection#HTTP_UNAUTHORIZED
 	 */
-	public static final int HTTP_UNAUTHORIZED = java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
+	int HTTP_UNAUTHORIZED = java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
 
 	/**
 	 * @see HttpURLConnection#HTTP_FORBIDDEN
 	 */
-	public static final int HTTP_FORBIDDEN = java.net.HttpURLConnection.HTTP_FORBIDDEN;
+	int HTTP_FORBIDDEN = java.net.HttpURLConnection.HTTP_FORBIDDEN;
 
 	/**
 	 * Get response code
@@ -119,7 +121,7 @@
 	 * @return the HTTP Status-Code, or -1
 	 * @throws java.io.IOException
 	 */
-	public int getResponseCode() throws IOException;
+	int getResponseCode() throws IOException;
 
 	/**
 	 * Get URL
@@ -127,7 +129,7 @@
 	 * @see HttpURLConnection#getURL()
 	 * @return the URL.
 	 */
-	public URL getURL();
+	URL getURL();
 
 	/**
 	 * Get response message
@@ -136,15 +138,15 @@
 	 * @return the HTTP response message, or <code>null</code>
 	 * @throws java.io.IOException
 	 */
-	public String getResponseMessage() throws IOException;
+	String getResponseMessage() throws IOException;
 
 	/**
-	 * Get list of header fields
+	 * Get map of header fields
 	 *
 	 * @see HttpURLConnection#getHeaderFields()
 	 * @return a Map of header fields
 	 */
-	public Map<String, List<String>> getHeaderFields();
+	Map<String, List<String>> getHeaderFields();
 
 	/**
 	 * Set request property
@@ -156,7 +158,7 @@
 	 * @param value
 	 *            the value associated with it.
 	 */
-	public void setRequestProperty(String key, String value);
+	void setRequestProperty(String key, String value);
 
 	/**
 	 * Set request method
@@ -170,7 +172,7 @@
 	 * @throws java.net.ProtocolException
 	 *             if any.
 	 */
-	public void setRequestMethod(String method)
+	void setRequestMethod(String method)
 			throws ProtocolException;
 
 	/**
@@ -181,7 +183,7 @@
 	 *            a <code>boolean</code> indicating whether or not to allow
 	 *            caching
 	 */
-	public void setUseCaches(boolean usecaches);
+	void setUseCaches(boolean usecaches);
 
 	/**
 	 * Set connect timeout
@@ -191,7 +193,7 @@
 	 *            an <code>int</code> that specifies the connect timeout value
 	 *            in milliseconds
 	 */
-	public void setConnectTimeout(int timeout);
+	void setConnectTimeout(int timeout);
 
 	/**
 	 * Set read timeout
@@ -201,7 +203,7 @@
 	 *            an <code>int</code> that specifies the timeout value to be
 	 *            used in milliseconds
 	 */
-	public void setReadTimeout(int timeout);
+	void setReadTimeout(int timeout);
 
 	/**
 	 * Get content type
@@ -210,7 +212,7 @@
 	 * @return the content type of the resource that the URL references, or
 	 *         <code>null</code> if not known.
 	 */
-	public String getContentType();
+	String getContentType();
 
 	/**
 	 * Get input stream
@@ -222,10 +224,16 @@
 	 * @throws java.io.IOException
 	 *             if any.
 	 */
-	public InputStream getInputStream() throws IOException;
+	InputStream getInputStream() throws IOException;
 
 	/**
-	 * Get header field
+	 * Get header field. According to
+	 * {@link <a href="https://tools.ietf.org/html/rfc2616#section-4.2">RFC
+	 * 2616</a>} header field names are case insensitive. Header fields defined
+	 * as a comma separated list can have multiple header fields with the same
+	 * field name. This method only returns one of these header fields. If you
+	 * want the union of all values of all multiple header fields with the same
+	 * field name then use {@link #getHeaderFields(String)}
 	 *
 	 * @see HttpURLConnection#getHeaderField(String)
 	 * @param name
@@ -233,7 +241,22 @@
 	 * @return the value of the named header field, or <code>null</code> if
 	 *         there is no such field in the header.
 	 */
-	public String getHeaderField(String name);
+	String getHeaderField(@NonNull String name);
+
+	/**
+	 * Get all values of given header field. According to
+	 * {@link <a href="https://tools.ietf.org/html/rfc2616#section-4.2">RFC
+	 * 2616</a>} header field names are case insensitive. Header fields defined
+	 * as a comma separated list can have multiple header fields with the same
+	 * field name. This method does not validate if the given header field is
+	 * defined as a comma separated list.
+	 *
+	 * @param name
+	 *            the name of a header field.
+	 * @return the list of values of the named header field
+	 * @since 5.2
+	 */
+	List<String> getHeaderFields(@NonNull String name);
 
 	/**
 	 * Get content length
@@ -243,7 +266,7 @@
 	 *         references, {@code -1} if the content length is not known, or if
 	 *         the content length is greater than Integer.MAX_VALUE.
 	 */
-	public int getContentLength();
+	int getContentLength();
 
 	/**
 	 * Set whether or not to follow HTTP redirects.
@@ -253,7 +276,7 @@
 	 *            a <code>boolean</code> indicating whether or not to follow
 	 *            HTTP redirects.
 	 */
-	public void setInstanceFollowRedirects(boolean followRedirects);
+	void setInstanceFollowRedirects(boolean followRedirects);
 
 	/**
 	 * Set if to do output
@@ -262,7 +285,7 @@
 	 * @param dooutput
 	 *            the new value.
 	 */
-	public void setDoOutput(boolean dooutput);
+	void setDoOutput(boolean dooutput);
 
 	/**
 	 * Set fixed length streaming mode
@@ -271,7 +294,7 @@
 	 * @param contentLength
 	 *            The number of bytes which will be written to the OutputStream.
 	 */
-	public void setFixedLengthStreamingMode(int contentLength);
+	void setFixedLengthStreamingMode(int contentLength);
 
 	/**
 	 * Get output stream
@@ -280,7 +303,7 @@
 	 * @return an output stream that writes to this connection.
 	 * @throws java.io.IOException
 	 */
-	public OutputStream getOutputStream() throws IOException;
+	OutputStream getOutputStream() throws IOException;
 
 	/**
 	 * Set chunked streaming mode
@@ -290,7 +313,7 @@
 	 *            The number of bytes to write in each chunk. If chunklen is
 	 *            less than or equal to zero, a default value will be used.
 	 */
-	public void setChunkedStreamingMode(int chunklen);
+	void setChunkedStreamingMode(int chunklen);
 
 	/**
 	 * Get request method
@@ -298,7 +321,7 @@
 	 * @see HttpURLConnection#getRequestMethod()
 	 * @return the HTTP request method
 	 */
-	public String getRequestMethod();
+	String getRequestMethod();
 
 	/**
 	 * Whether we use a proxy
@@ -306,7 +329,7 @@
 	 * @see HttpURLConnection#usingProxy()
 	 * @return a boolean indicating if the connection is using a proxy.
 	 */
-	public boolean usingProxy();
+	boolean usingProxy();
 
 	/**
 	 * Connect
@@ -314,7 +337,7 @@
 	 * @see HttpURLConnection#connect()
 	 * @throws java.io.IOException
 	 */
-	public void connect() throws IOException;
+	void connect() throws IOException;
 
 	/**
 	 * Configure the connection so that it can be used for https communication.
@@ -332,7 +355,7 @@
 	 * @throws java.security.NoSuchAlgorithmException
 	 * @throws java.security.KeyManagementException
 	 */
-	public void configure(KeyManager[] km, TrustManager[] tm,
+	void configure(KeyManager[] km, TrustManager[] tm,
 			SecureRandom random) throws NoSuchAlgorithmException,
 			KeyManagementException;
 
@@ -345,6 +368,6 @@
 	 * @throws java.security.NoSuchAlgorithmException
 	 * @throws java.security.KeyManagementException
 	 */
-	public void setHostnameVerifier(HostnameVerifier hostnameverifier)
+	void setHostnameVerifier(HostnameVerifier hostnameverifier)
 			throws NoSuchAlgorithmException, KeyManagementException;
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnectionFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnectionFactory.java
index bd9d61f..1169145 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnectionFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnectionFactory.java
@@ -62,7 +62,7 @@
 	 * @return a {@link org.eclipse.jgit.transport.http.HttpConnection}
 	 * @throws java.io.IOException
 	 */
-	public HttpConnection create(URL url) throws IOException;
+	HttpConnection create(URL url) throws IOException;
 
 	/**
 	 * Creates a new connection to a destination defined by a
@@ -75,6 +75,6 @@
 	 * @return a {@link org.eclipse.jgit.transport.http.HttpConnection}
 	 * @throws java.io.IOException
 	 */
-	public HttpConnection create(URL url, Proxy proxy)
+	HttpConnection create(URL url, Proxy proxy)
 			throws IOException;
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnection.java
index 8241c59..734b549 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnection.java
@@ -53,6 +53,7 @@
 import java.security.KeyManagementException;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
@@ -62,6 +63,7 @@
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.TrustManager;
 
+import org.eclipse.jgit.annotations.NonNull;
 /**
  * A {@link org.eclipse.jgit.transport.http.HttpConnection} which simply
  * delegates every call to a {@link java.net.HttpURLConnection}. This is the
@@ -72,6 +74,11 @@
 public class JDKHttpConnection implements HttpConnection {
 	HttpURLConnection wrappedUrlConnection;
 
+	// used for mock testing
+	JDKHttpConnection(HttpURLConnection urlConnection) {
+		this.wrappedUrlConnection = urlConnection;
+	}
+
 	/**
 	 * Constructor for JDKHttpConnection.
 	 *
@@ -170,10 +177,26 @@
 
 	/** {@inheritDoc} */
 	@Override
-	public String getHeaderField(String name) {
+	public String getHeaderField(@NonNull String name) {
 		return wrappedUrlConnection.getHeaderField(name);
 	}
 
+	@Override
+	public List<String> getHeaderFields(@NonNull String name) {
+		Map<String, List<String>> m = wrappedUrlConnection.getHeaderFields();
+		List<String> fields = mapValuesToListIgnoreCase(name, m);
+		return fields;
+	}
+
+	private static List<String> mapValuesToListIgnoreCase(String keyName,
+			Map<String, List<String>> m) {
+		List<String> fields = new LinkedList<>();
+		m.entrySet().stream().filter(e -> keyName.equalsIgnoreCase(e.getKey()))
+				.filter(e -> e.getValue() != null)
+				.forEach(e -> fields.addAll(e.getValue()));
+		return fields;
+	}
+
 	/** {@inheritDoc} */
 	@Override
 	public int getContentLength() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ReceivePackFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ReceivePackFactory.java
index 4967169..b850d1e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ReceivePackFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ReceivePackFactory.java
@@ -57,7 +57,7 @@
 	/**
 	 * A factory disabling the ReceivePack service for all repositories
 	 */
-	public static final ReceivePackFactory<?> DISABLED = new ReceivePackFactory<Object>() {
+	ReceivePackFactory<?> DISABLED = new ReceivePackFactory<Object>() {
 		@Override
 		public ReceivePack create(Object req, Repository db)
 				throws ServiceNotEnabledException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/RepositoryResolver.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/RepositoryResolver.java
index a305e4c..4816f21 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/RepositoryResolver.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/RepositoryResolver.java
@@ -57,7 +57,7 @@
 	/**
 	 * Resolver configured to open nothing.
 	 */
-	public static final RepositoryResolver<?> NONE = new RepositoryResolver<Object>() {
+	RepositoryResolver<?> NONE = new RepositoryResolver<Object>() {
 		@Override
 		public Repository open(Object req, String name)
 				throws RepositoryNotFoundException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/UploadPackFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/UploadPackFactory.java
index 40d1ffd..bb43b13 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/UploadPackFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/UploadPackFactory.java
@@ -57,7 +57,7 @@
 	/**
 	 * A factory disabling the UploadPack service for all repositories.
 	 */
-	public static final UploadPackFactory<?> DISABLED = new UploadPackFactory<Object>() {
+	UploadPackFactory<?> DISABLED = new UploadPackFactory<Object>() {
 		@Override
 		public UploadPack create(Object req, Repository db)
 				throws ServiceNotEnabledException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
index 24b9ac0..3d25c23 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
@@ -46,6 +46,8 @@
 
 package org.eclipse.jgit.treewalk;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileInputStream;
@@ -412,7 +414,7 @@
 		public InputStream openInputStream() throws IOException {
 			if (attributes.isSymbolicLink()) {
 				return new ByteArrayInputStream(fs.readSymLink(getFile())
-						.getBytes(Constants.CHARACTER_ENCODING));
+						.getBytes(UTF_8));
 			} else {
 				return new FileInputStream(getFile());
 			}
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 7c6bfb9..1fa1db5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
@@ -1045,7 +1045,7 @@
 			}
 		}
 		if (FileMode.GITLINK == iMode
-				&& FileMode.TREE == wtMode) {
+				&& FileMode.TREE == wtMode && !getOptions().isDirNoGitLinks()) {
 			return iMode;
 		}
 		if (FileMode.TREE == iMode
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 180123e..fb95887 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
@@ -43,6 +43,8 @@
 
 package org.eclipse.jgit.util;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
 import java.io.Closeable;
@@ -1222,6 +1224,13 @@
 				hookPath);
 		ProcessBuilder hookProcess = runInShell(cmd, args);
 		hookProcess.directory(runDirectory);
+		Map<String, String> environment = hookProcess.environment();
+		environment.put(Constants.GIT_DIR_KEY,
+				repository.getDirectory().getAbsolutePath());
+		if (!repository.isBare()) {
+			environment.put(Constants.GIT_WORK_TREE_KEY,
+					repository.getWorkTree().getAbsolutePath());
+		}
 		try {
 			return new ProcessResult(runProcess(hookProcess, outRedirect,
 					errRedirect, stdinArgs), Status.OK);
@@ -1286,7 +1295,7 @@
 			OutputStream outRedirect, OutputStream errRedirect, String stdinArgs)
 			throws IOException, InterruptedException {
 		InputStream in = (stdinArgs == null) ? null : new ByteArrayInputStream(
-				stdinArgs.getBytes(Constants.CHARACTER_ENCODING));
+						stdinArgs.getBytes(UTF_8));
 		return runProcess(processBuilder, outRedirect, errRedirect, in);
 	}
 
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 6f92b37..9190a59 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java
@@ -44,7 +44,7 @@
 
 package org.eclipse.jgit.util;
 
-import static org.eclipse.jgit.lib.Constants.CHARACTER_ENCODING;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
@@ -181,7 +181,7 @@
 		if (key == null || key.length() == 0)
 			return;
 		try {
-			urlstr.append(URLEncoder.encode(key, CHARACTER_ENCODING));
+			urlstr.append(URLEncoder.encode(key, UTF_8.name()));
 		} catch (UnsupportedEncodingException e) {
 			throw new RuntimeException(JGitText.get().couldNotURLEncodeToUTF8, e);
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/LfsFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/LfsFactory.java
index 6d60ef3..96636b7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/LfsFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/LfsFactory.java
@@ -152,7 +152,8 @@
 	 * @param outputStream
 	 * @return a {@link PrePushHook} implementation or <code>null</code>
 	 */
-	public @Nullable PrePushHook getPrePushHook(Repository repo,
+	@Nullable
+	public PrePushHook getPrePushHook(Repository repo,
 			PrintStream outputStream) {
 		return null;
 	}
@@ -163,7 +164,8 @@
 	 *
 	 * @return a command to install LFS support.
 	 */
-	public @Nullable LfsInstallCommand getInstallCommand() {
+	@Nullable
+	public LfsInstallCommand getInstallCommand() {
 		return null;
 	}
 
@@ -294,6 +296,11 @@
 			return stream.read();
 		}
 
+		@Override
+		public int read(byte b[], int off, int len) throws IOException {
+			return stream.read(b, off, len);
+		}
+
 		/**
 		 * @return the length of the stream
 		 */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java
index 28f406a..a440cb2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java
@@ -677,10 +677,6 @@
 	 * <p>
 	 * The last element (index <code>map.size()-1</code>) always contains
 	 * <code>end</code>.
-	 * <p>
-	 * If the data contains a '\0' anywhere, the whole region is considered
-	 * binary and a LineMap corresponding to a single line is returned.
-	 * </p>
 	 *
 	 * @param buf
 	 *            buffer to scan.
@@ -689,18 +685,15 @@
 	 *            line 1.
 	 * @param end
 	 *            1 past the end of the content within <code>buf</code>.
-	 * @return a line map indicating the starting position of each line, or a
-	 *         map representing the entire buffer as a single line if
-	 *         <code>buf</code> contains a NUL byte.
+	 * @return a line map indicating the starting position of each line.
 	 */
 	public static final IntList lineMap(byte[] buf, int ptr, int end) {
-		IntList map = lineMapOrNull(buf, ptr, end);
-		if (map == null) {
-			map = new IntList(3);
-			map.add(Integer.MIN_VALUE);
+		IntList map = new IntList((end - ptr) / 36);
+		map.fillTo(1, Integer.MIN_VALUE);
+		for (; ptr < end; ptr = nextLF(buf, ptr)) {
 			map.add(ptr);
-			map.add(end);
 		}
+		map.add(end);
 		return map;
 	}
 
@@ -729,7 +722,8 @@
 		return map;
 	}
 
-	private static @Nullable IntList lineMapOrNull(byte[] buf, int ptr, int end) {
+	@Nullable
+	private static IntList lineMapOrNull(byte[] buf, int ptr, int end) {
 		// Experimentally derived from multiple source repositories
 		// the average number of bytes/line is 36. Its a rough guess
 		// to initially size our map close to the target.
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 822961f..d7c6bec 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,6 @@
 /*
  * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com>
+ * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
  * under the terms of the Eclipse Distribution License v1.0 which
@@ -49,6 +50,7 @@
 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;
 
 /**
  * Utility used to create input and output stream wrappers for
@@ -57,7 +59,6 @@
  * @since 4.3
  */
 public final class EolStreamTypeUtil {
-	private static final boolean FORCE_EOL_LF_ON_CHECKOUT = false;
 
 	private EolStreamTypeUtil() {
 	}
@@ -164,11 +165,11 @@
 
 		// old git system
 		if (attrs.isSet("crlf")) {//$NON-NLS-1$
-			return EolStreamType.TEXT_LF;
+			return EolStreamType.TEXT_LF; // Same as isSet("text")
 		} else if (attrs.isUnset("crlf")) {//$NON-NLS-1$
-			return EolStreamType.DIRECT;
+			return EolStreamType.DIRECT; // Same as isUnset("text")
 		} else if ("input".equals(attrs.getValue("crlf"))) {//$NON-NLS-1$ //$NON-NLS-2$
-			return EolStreamType.TEXT_LF;
+			return EolStreamType.TEXT_LF; // Same as eol=lf
 		}
 
 		// new git system
@@ -196,6 +197,28 @@
 		return EolStreamType.DIRECT;
 	}
 
+	private static EolStreamType getOutputFormat(WorkingTreeOptions options) {
+		switch (options.getAutoCRLF()) {
+		case TRUE:
+			return EolStreamType.TEXT_CRLF;
+		default:
+			// no decision
+		}
+		switch (options.getEOL()) {
+		case CRLF:
+			return EolStreamType.TEXT_CRLF;
+		case NATIVE:
+			if (SystemReader.getInstance().isWindows()) {
+				return EolStreamType.TEXT_CRLF;
+			}
+			return EolStreamType.TEXT_LF;
+		case LF:
+		default:
+			break;
+		}
+		return EolStreamType.DIRECT;
+	}
+
 	private static EolStreamType checkOutStreamType(WorkingTreeOptions options,
 			Attributes attrs) {
 		if (attrs.isUnset("text")) {//$NON-NLS-1$
@@ -205,57 +228,35 @@
 
 		// old git system
 		if (attrs.isSet("crlf")) {//$NON-NLS-1$
-			return FORCE_EOL_LF_ON_CHECKOUT ? EolStreamType.TEXT_LF
-					: EolStreamType.DIRECT;
+			return getOutputFormat(options); // Same as isSet("text")
 		} else if (attrs.isUnset("crlf")) {//$NON-NLS-1$
-			return EolStreamType.DIRECT;
+			return EolStreamType.DIRECT; // Same as isUnset("text")
 		} else if ("input".equals(attrs.getValue("crlf"))) {//$NON-NLS-1$ //$NON-NLS-2$
-			return EolStreamType.DIRECT;
+			return EolStreamType.DIRECT; // Same as eol=lf
 		}
 
 		// new git system
 		String eol = attrs.getValue("eol"); //$NON-NLS-1$
-		if (eol != null && "crlf".equals(eol)) //$NON-NLS-1$
-			return EolStreamType.TEXT_CRLF;
-		if (eol != null && "lf".equals(eol)) //$NON-NLS-1$
-			return FORCE_EOL_LF_ON_CHECKOUT ? EolStreamType.TEXT_LF
-					: EolStreamType.DIRECT;
-
-		if (attrs.isSet("text")) { //$NON-NLS-1$
-			switch (options.getAutoCRLF()) {
-			case TRUE:
+		if (eol != null) {
+			if ("crlf".equals(eol)) {//$NON-NLS-1$
 				return EolStreamType.TEXT_CRLF;
-			default:
-				// no decision
-			}
-			switch (options.getEOL()) {
-			case CRLF:
-				return EolStreamType.TEXT_CRLF;
-			case LF:
-				return FORCE_EOL_LF_ON_CHECKOUT ? EolStreamType.TEXT_LF
-						: EolStreamType.DIRECT;
-			case NATIVE:
-			default:
+			} else if ("lf".equals(eol)) { //$NON-NLS-1$
 				return EolStreamType.DIRECT;
 			}
 		}
+		if (attrs.isSet("text")) { //$NON-NLS-1$
+			return getOutputFormat(options);
+		}
 
 		if ("auto".equals(attrs.getValue("text"))) { //$NON-NLS-1$ //$NON-NLS-2$
-			switch (options.getAutoCRLF()) {
-			case TRUE:
+			EolStreamType basic = getOutputFormat(options);
+			switch (basic) {
+			case TEXT_CRLF:
 				return EolStreamType.AUTO_CRLF;
+			case TEXT_LF:
+				return EolStreamType.AUTO_LF;
 			default:
-				// no decision
-			}
-			switch (options.getEOL()) {
-			case CRLF:
-				return EolStreamType.AUTO_CRLF;
-			case LF:
-				return FORCE_EOL_LF_ON_CHECKOUT ? EolStreamType.TEXT_LF
-						: EolStreamType.DIRECT;
-			case NATIVE:
-			default:
-				return EolStreamType.DIRECT;
+				return basic;
 			}
 		}
 
diff --git a/pom.xml b/pom.xml
index 934f1b8..25fa24d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -51,7 +51,7 @@
   <groupId>org.eclipse.jgit</groupId>
   <artifactId>org.eclipse.jgit-parent</artifactId>
   <packaging>pom</packaging>
-  <version>5.1.9-SNAPSHOT</version>
+  <version>5.2.3-SNAPSHOT</version>
 
   <name>JGit - Parent</name>
   <url>${jgit-url}</url>
@@ -77,15 +77,12 @@
 
   <developers>
     <developer>
-      <name>Chris Aniszczyk</name>
+      <name>Andrey Loskutov</name>
     </developer>
     <developer>
       <name>Christian Halstrick</name>
     </developer>
     <developer>
-      <name>Colby Ranger</name>
-    </developer>
-    <developer>
       <name>Dave Borowitz</name>
     </developer>
     <developer>
@@ -98,28 +95,16 @@
       <name>Jonathan Nieder</name>
     </developer>
     <developer>
-      <name>Kevin Sawicki</name>
-    </developer>
-    <developer>
-      <name>Mathias Kinzler</name>
+      <name>Jonathan Tan</name>
     </developer>
     <developer>
       <name>Matthias Sohn</name>
     </developer>
     <developer>
-      <name>Robin Rosenberg</name>
-    </developer>
-    <developer>
-      <name>Robin Stocker</name>
-    </developer>
-    <developer>
       <name>Sasa Zivkov</name>
     </developer>
     <developer>
-      <name>Shawn Pearce</name>
-    </developer>
-    <developer>
-      <name>Stefan Lay</name>
+      <name>Terry Parker</name>
     </developer>
     <developer>
       <name>Thomas Wolf</name>
@@ -197,14 +182,15 @@
     <maven.build.timestamp.format>yyyyMMddHHmm</maven.build.timestamp.format>
     <bundle-manifest>${project.build.directory}/META-INF/MANIFEST.MF</bundle-manifest>
 
-    <jgit-last-release-version>4.11.0.201803080745-r</jgit-last-release-version>
+    <jgit-last-release-version>5.1.0.201809111528-r</jgit-last-release-version>
+    <apache-sshd-version>2.0.0</apache-sshd-version>
     <jsch-version>0.1.54</jsch-version>
     <jzlib-version>1.1.1</jzlib-version>
     <javaewah-version>1.1.6</javaewah-version>
     <junit-version>4.12</junit-version>
     <test-fork-count>1C</test-fork-count>
     <args4j-version>2.33</args4j-version>
-    <commons-compress-version>1.15</commons-compress-version>
+    <commons-compress-version>1.18</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.11.v20180605</jetty-version>
@@ -216,9 +202,11 @@
     <maven-javadoc-plugin-version>3.0.1</maven-javadoc-plugin-version>
     <tycho-extras-version>1.2.0</tycho-extras-version>
     <gson-version>2.8.2</gson-version>
-    <spotbugs-maven-plugin-version>3.1.6</spotbugs-maven-plugin-version>
-    <maven-surefire-report-plugin-version>2.21.0</maven-surefire-report-plugin-version>
+    <spotbugs-maven-plugin-version>3.1.8</spotbugs-maven-plugin-version>
+    <maven-surefire-version>2.22.1</maven-surefire-version>
     <maven-compiler-plugin-version>3.8.0</maven-compiler-plugin-version>
+    <maven-project-info-reports-plugin-version>3.0.0</maven-project-info-reports-plugin-version>
+    <maven-jxr-plugin-version>3.0.0</maven-jxr-plugin-version>
 
     <!-- Properties to enable jacoco code coverage analysis -->
     <sonar.core.codeCoveragePlugin>jacoco</sonar.core.codeCoveragePlugin>
@@ -275,7 +263,7 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-shade-plugin</artifactId>
-          <version>3.1.1</version>
+          <version>3.2.1</version>
         </plugin>
 
         <plugin>
@@ -305,7 +293,7 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-surefire-plugin</artifactId>
-          <version>2.21.0</version>
+          <version>${maven-surefire-version}</version>
           <configuration>
             <forkCount>${test-fork-count}</forkCount>
             <reuseForks>true</reuseForks>
@@ -338,7 +326,7 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-pmd-plugin</artifactId>
-          <version>3.10.0</version>
+          <version>3.11.0</version>
           <configuration>
             <sourceEncoding>utf-8</sourceEncoding>
             <minimumTokens>100</minimumTokens>
@@ -376,7 +364,7 @@
         <plugin>
           <groupId>org.jacoco</groupId>
           <artifactId>jacoco-maven-plugin</artifactId>
-          <version>0.8.1</version>
+          <version>0.8.2</version>
         </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
@@ -386,24 +374,24 @@
             <dependency><!-- add support for ssh/scp -->
               <groupId>org.apache.maven.wagon</groupId>
               <artifactId>wagon-ssh</artifactId>
-              <version>3.1.0</version>
+              <version>3.2.0</version>
             </dependency>
           </dependencies>
         </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-surefire-report-plugin</artifactId>
-          <version>${maven-surefire-report-plugin-version}</version>
+          <version>${maven-surefire-version}</version>
         </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-jxr-plugin</artifactId>
-          <version>2.5</version>
+          <version>${maven-jxr-plugin-version}</version>
         </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-project-info-reports-plugin</artifactId>
-          <version>2.9</version>
+          <version>${maven-project-info-reports-plugin-version}</version>
         </plugin>
       </plugins>
     </pluginManagement>
@@ -412,7 +400,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-enforcer-plugin</artifactId>
-        <version>3.0.0-M1</version>
+        <version>3.0.0-M2</version>
         <executions>
           <execution>
             <id>enforce-maven</id>
@@ -560,7 +548,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-jxr-plugin</artifactId>
-        <version>2.5</version>
+        <version>${maven-jxr-plugin-version}</version>
       </plugin>
       <plugin>
         <groupId>com.github.spotbugs</groupId>
@@ -570,7 +558,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-surefire-report-plugin</artifactId>
-        <version>${maven-surefire-report-plugin-version}</version>
+        <version>${maven-surefire-version}</version>
         <configuration>
           <aggregate>true</aggregate>
           <alwaysGenerateSurefireReport>false</alwaysGenerateSurefireReport>
@@ -582,16 +570,20 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-project-info-reports-plugin</artifactId>
-        <version>2.9</version>
+        <version>${maven-project-info-reports-plugin-version}</version>
         <reportSets>
           <reportSet>
             <reports>
               <report>dependencies</report>
-              <report>project-team</report>
-              <report>mailing-list</report>
-              <report>cim</report>
-              <report>issue-tracking</report>
-              <report>license</report>
+              <report>dependency-convergence</report>
+              <report>dependency-management</report>
+              <report>index</report>
+              <report>summary</report>
+              <report>team</report>
+              <report>mailing-lists</report>
+              <report>ci-management</report>
+              <report>issue-management</report>
+              <report>licenses</report>
               <report>scm</report>
             </reports>
           </reportSet>
@@ -798,7 +790,7 @@
               <dependency>
                 <groupId>com.google.errorprone</groupId>
                 <artifactId>error_prone_core</artifactId>
-                <version>2.3.1</version>
+                <version>2.3.2</version>
               </dependency>
             </dependencies>
           </plugin>
@@ -836,7 +828,7 @@
               <dependency>
                 <groupId>org.eclipse.jdt</groupId>
                 <artifactId>ecj</artifactId>
-                <version>3.13.102</version>
+                <version>3.15.0</version>
               </dependency>
             </dependencies>
           </plugin>
@@ -931,11 +923,13 @@
     <module>org.eclipse.jgit.ui</module>
     <module>org.eclipse.jgit.http.apache</module>
     <module>org.eclipse.jgit.http.server</module>
+    <module>org.eclipse.jgit.ssh.apache</module>
     <module>org.eclipse.jgit.pgm</module>
     <module>org.eclipse.jgit.lfs</module>
     <module>org.eclipse.jgit.lfs.server</module>
     <module>org.eclipse.jgit.junit</module>
     <module>org.eclipse.jgit.junit.http</module>
+    <module>org.eclipse.jgit.junit.ssh</module>
 
     <module>org.eclipse.jgit.test</module>
     <module>org.eclipse.jgit.ant.test</module>
@@ -943,6 +937,7 @@
     <module>org.eclipse.jgit.pgm.test</module>
     <module>org.eclipse.jgit.lfs.test</module>
     <module>org.eclipse.jgit.lfs.server.test</module>
+    <module>org.eclipse.jgit.ssh.apache.test</module>
   </modules>
 
 </project>
diff --git a/tools/BUILD b/tools/BUILD
index e69de29..9025e0a 100644
--- a/tools/BUILD
+++ b/tools/BUILD
@@ -0,0 +1,107 @@
+load(
+    "@bazel_tools//tools/jdk:default_java_toolchain.bzl",
+    "JDK9_JVM_OPTS",
+    "default_java_toolchain",
+)
+
+default_java_toolchain(
+    name = "error_prone_warnings_toolchain",
+    bootclasspath = ["@bazel_tools//tools/jdk:platformclasspath.jar"],
+    jvm_opts = JDK9_JVM_OPTS,
+    package_configuration = [
+        ":error_prone",
+    ],
+    visibility = ["//visibility:public"],
+)
+
+# This EP warnings list borrowed from here:
+# https://github.com/bazelbuild/BUILD_file_generator/blob/master/tools/bazel_defs/java.bzl
+java_package_configuration(
+    name = "error_prone",
+    javacopts = [
+        "-XepDisableWarningsInGeneratedCode",
+        "-Xep:MissingCasesInEnumSwitch:ERROR",
+        "-Xep:ReferenceEquality:WARN",
+        "-Xep:StringEquality:WARN",
+        "-Xep:WildcardImport:WARN",
+        "-Xep:AmbiguousMethodReference:WARN",
+        "-Xep:BadAnnotationImplementation:WARN",
+        "-Xep:BadComparable:WARN",
+        "-Xep:BoxedPrimitiveConstructor:ERROR",
+        "-Xep:CannotMockFinalClass:WARN",
+        "-Xep:ClassCanBeStatic:ERROR",
+        "-Xep:ClassNewInstance:WARN",
+        "-Xep:DefaultCharset:ERROR",
+        "-Xep:DoubleCheckedLocking:WARN",
+        "-Xep:ElementsCountedInLoop:WARN",
+        "-Xep:EqualsHashCode:WARN",
+        "-Xep:EqualsIncompatibleType:WARN",
+        "-Xep:ExpectedExceptionChecker:ERROR",
+        "-Xep:Finally:WARN",
+        "-Xep:FloatingPointLiteralPrecision:WARN",
+        "-Xep:FragmentInjection:WARN",
+        "-Xep:FragmentNotInstantiable:WARN",
+        "-Xep:FunctionalInterfaceClash:WARN",
+        "-Xep:FutureReturnValueIgnored:WARN",
+        "-Xep:GetClassOnEnum:WARN",
+        "-Xep:ImmutableAnnotationChecker:WARN",
+        "-Xep:ImmutableEnumChecker:WARN",
+        "-Xep:IncompatibleModifiers:WARN",
+        "-Xep:InjectOnConstructorOfAbstractClass:WARN",
+        "-Xep:InputStreamSlowMultibyteRead:WARN",
+        "-Xep:IterableAndIterator:WARN",
+        "-Xep:JUnit3FloatingPointComparisonWithoutDelta:WARN",
+        "-Xep:JUnitAmbiguousTestClass:WARN",
+        "-Xep:LiteralClassName:WARN",
+        "-Xep:MissingFail:ERROR",
+        "-Xep:MissingOverride:WARN",
+        "-Xep:MutableConstantField:WARN",
+        "-Xep:NarrowingCompoundAssignment:WARN",
+        "-Xep:NonAtomicVolatileUpdate:WARN",
+        "-Xep:NonOverridingEquals:WARN",
+        "-Xep:NullableConstructor:WARN",
+        "-Xep:NullablePrimitive:WARN",
+        "-Xep:NullableVoid:WARN",
+        "-Xep:OperatorPrecedence:WARN",
+        "-Xep:OverridesGuiceInjectableMethod:WARN",
+        "-Xep:PreconditionsInvalidPlaceholder:WARN",
+        "-Xep:ProtoFieldPreconditionsCheckNotNull:WARN",
+        "-Xep:ProtocolBufferOrdinal:WARN",
+        "-Xep:RequiredModifiers:WARN",
+        "-Xep:ShortCircuitBoolean:WARN",
+        "-Xep:SimpleDateFormatConstant:WARN",
+        "-Xep:StaticGuardedByInstance:WARN",
+        "-Xep:SynchronizeOnNonFinalField:WARN",
+        "-Xep:TruthConstantAsserts:WARN",
+        "-Xep:TypeParameterShadowing:WARN",
+        "-Xep:TypeParameterUnusedInFormals:WARN",
+        "-Xep:URLEqualsHashCode:WARN",
+        "-Xep:UnsynchronizedOverridesSynchronized:WARN",
+        "-Xep:WaitNotInLoop:WARN",
+    ],
+    packages = ["error_prone_packages"],
+)
+
+package_group(
+    name = "error_prone_packages",
+    packages = [
+        "//org.eclipse.jgit.ant.test/...",
+        "//org.eclipse.jgit.ant/...",
+        "//org.eclipse.jgit.archive/...",
+        "//org.eclipse.jgit.http.apache/...",
+        "//org.eclipse.jgit.http.server/...",
+        "//org.eclipse.jgit.http.test/...",
+        "//org.eclipse.jgit.junit.http/...",
+        "//org.eclipse.jgit.junit/...",
+        "//org.eclipse.jgit.lfs.server.test/...",
+        "//org.eclipse.jgit.lfs.server/...",
+        "//org.eclipse.jgit.lfs.test/...",
+        "//org.eclipse.jgit.lfs/...",
+        "//org.eclipse.jgit.packaging/...",
+        "//org.eclipse.jgit.pgm.test/...",
+        "//org.eclipse.jgit.pgm/...",
+        "//org.eclipse.jgit.test/...",
+        "//org.eclipse.jgit.ui/...",
+        "//org.eclipse.jgit/...",
+    ],
+)
diff --git a/tools/maven-central/deploy.rb b/tools/maven-central/deploy.rb
index 2744e77..7cab322 100755
--- a/tools/maven-central/deploy.rb
+++ b/tools/maven-central/deploy.rb
@@ -55,9 +55,11 @@
              group + '.http.server',
              group + '.junit',
              group + '.junit.http',
+             group + '.junit.ssh',
              group + '.lfs',
              group + '.lfs.server',
              group + '.pgm',
+             group + '.ssh.apache',
              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 b6c2671..543ae87 100755
--- a/tools/maven-central/download.rb
+++ b/tools/maven-central/download.rb
@@ -15,9 +15,11 @@
              group + '.http.server',
              group + '.junit',
              group + '.junit.http',
+             group + '.junit.ssh',
              group + '.lfs',
              group + '.lfs.server',
              group + '.pgm',
+             group + '.ssh.apache',
              group + '.ui']
 
 puts 'Deleting current files'