Merge branch 'stable-5.1' into stable-5.2

* stable-5.1:
  Use FileSnapshot without using configs for FileBasedConfig

Change-Id: I17ede8876a0cf231c38cb9652c7bf51553b1e90e
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 46cb265..28c4c59 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -53,6 +53,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.55",
     sha1 = "bbd40e5aa7aa3cfad5db34965456cee738a42a50",
@@ -83,6 +89,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",
@@ -114,8 +132,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(
@@ -149,20 +167,22 @@
 )
 
 maven_jar(
-    name = "mockito-core",
+    name = "mockito",
     artifact = "org.mockito:mockito-core:2.23.0",
     sha1 = "497ddb32fd5d01f9dbe99a2ec790aeb931dff1b1",
 )
 
+BYTE_BUDDY_VERSION = "1.9.0"
+
 maven_jar(
     name = "bytebuddy",
-    artifact = "net.bytebuddy:byte-buddy:1.9.0",
+    artifact = "net.bytebuddy:byte-buddy:" + BYTE_BUDDY_VERSION,
     sha1 = "8cb0d5baae526c9df46ae17693bbba302640538b",
 )
 
 maven_jar(
     name = "bytebuddy-agent",
-    artifact = "net.bytebuddy:byte-buddy-agent:1.9.0",
+    artifact = "net.bytebuddy:byte-buddy-agent:" + BYTE_BUDDY_VERSION,
     sha1 = "37b5703b4a6290be3fffc63ae9c6bcaaee0ff856",
 )
 
diff --git a/lib/BUILD b/lib/BUILD
index 4e3eaf7..7403ce2 100644
--- a/lib/BUILD
+++ b/lib/BUILD
@@ -26,12 +26,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__",
@@ -63,6 +71,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"],
@@ -139,7 +169,19 @@
         "@hamcrest-core//jar",
         "@hamcrest-library//jar",
         "@junit//jar",
-        "@mockito-core//jar",
+        "@mockito//jar",
+        "@objenesis//jar",
+    ],
+)
+
+java_library(
+    name = "mockito",
+    testonly = 1,
+    visibility = ["//visibility:public"],
+    exports = [
+        "@bytebuddy-agent//jar",
+        "@bytebuddy//jar",
+        "@mockito//jar",
         "@objenesis//jar",
     ],
 )
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 b4d199a..d4751b5 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 589dc31..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.17.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.17,5.2.0)",
- org.eclipse.jgit.junit;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lib;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.util;version="[5.1.17,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 705d49b..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.17-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 2d38bef..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.17.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.17,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.17";
+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 9b30435..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.17-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 e0a76cf..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.17.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.17,5.2.0)",
- org.eclipse.jgit.lib;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.nls;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.revwalk;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.util;version="[5.1.17,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.17";
+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 b797074..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.17.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.archive;version="5.1.17.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 76c2b84..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.17-SNAPSHOT</version>
+    <version>5.2.3-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.archive</artifactId>
diff --git a/org.eclipse.jgit.benchmarks/pom.xml b/org.eclipse.jgit.benchmarks/pom.xml
index d90ae16..67bedc9 100644
--- a/org.eclipse.jgit.benchmarks/pom.xml
+++ b/org.eclipse.jgit.benchmarks/pom.xml
@@ -47,7 +47,7 @@
   <modelVersion>4.0.0</modelVersion>
 
   <groupId>org.eclipse.jgit</groupId>
-  <version>5.1.17-SNAPSHOT</version>
+  <version>5.2.3-SNAPSHOT</version>
   <artifactId>org.eclipse.jgit.benchmarks</artifactId>
   <packaging>jar</packaging>
 
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 f8a0440..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.17.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.17,5.2.0)",
- org.eclipse.jgit.transport.http;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.util;version="[5.1.17,5.2.0)"
-Export-Package: org.eclipse.jgit.transport.http.apache;version="5.1.17";
+ 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 46a360d..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.17-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 af41e1e..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.17.qualifier
+Bundle-Version: 5.2.3.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
-Export-Package: org.eclipse.jgit.http.server;version="5.1.17",
- org.eclipse.jgit.http.server.glue;version="5.1.17";
+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.17";
+ 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.17,5.2.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lib;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.nls;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.revwalk;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.transport;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.transport.resolver;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.util;version="[5.1.17,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 ae4bafe..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.17-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/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/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 b4d199a..d4751b5 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 6f978e3..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.17.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.17,5.2.0)",
- org.eclipse.jgit.http.server;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.http.server.glue;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.http.server.resolver;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.internal;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.internal.storage.reftable;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.junit;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.junit.http;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lib;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.nls;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.revwalk;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.storage.file;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.transport;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.transport.http;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.transport.http.apache;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.transport.resolver;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.util;version="[5.1.17,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 c183afc..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.17-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.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 0d91082..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.17.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.17,5.2.0)",
- org.eclipse.jgit.http.server;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.junit;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lib;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.revwalk;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.transport;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.transport.resolver;version="[5.1.17,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.17";
+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 425af4f..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.17-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..a8b96b7
--- /dev/null
+++ b/org.eclipse.jgit.junit.ssh/BUILD
@@ -0,0 +1,17 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
+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 6251b76..b675029 100644
--- a/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.core.prefs
@@ -98,6 +98,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=ignore
 org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
 org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
 org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
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 3013921..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.17.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.17,5.2.0)",
- org.eclipse.jgit.api.errors;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.dircache;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.errors;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.internal.storage.pack;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lib;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.merge;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.revwalk;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.storage.file;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.treewalk;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.treewalk.filter;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.util;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.util.io;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.util.time;version="[5.1.17,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.17";
+ 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.17"
+   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 c42be4b..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.17-SNAPSHOT</version>
+    <version>5.2.3-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.junit</artifactId>
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 b4d199a..d4751b5 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 ca729ba..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.17.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.17,5.2.0)",
- org.eclipse.jgit.api.errors;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.junit;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.junit.http;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lfs;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lfs.errors;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lfs.lib;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lfs.server;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lfs.server.fs;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lfs.test;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lib;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.revwalk;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.storage.file;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.transport;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.treewalk;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.treewalk.filter;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.util;version="[5.1.17,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 d544a6d..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.17-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 d121cb5..6609c3d 100644
--- a/org.eclipse.jgit.lfs.server/.settings/.api_filters
+++ b/org.eclipse.jgit.lfs.server/.settings/.api_filters
@@ -1,13 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <component id="org.eclipse.jgit.lfs.server" version="2">
-    <resource path="META-INF/MANIFEST.MF">
-        <filter id="924844039">
-            <message_arguments>
-                <message_argument value="5.1.13"/>
-                <message_argument value="5.1.0"/>
-            </message_arguments>
-        </filter>
-    </resource>
     <resource path="src/org/eclipse/jgit/lfs/server/fs/ObjectUploadListener.java" type="org.eclipse.jgit.lfs.server.fs.ObjectUploadListener">
         <filter id="1142947843">
             <message_arguments>
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 a195a98..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.17.qualifier
+Bundle-Version: 5.2.3.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
-Export-Package: org.eclipse.jgit.lfs.server;version="5.1.17";
+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.17";
+ 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.17";x-internal:=true,
- org.eclipse.jgit.lfs.server.s3;version="5.1.17";
+ 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.17,5.2.0)",
- org.eclipse.jgit.internal;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lfs.errors;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lfs.internal;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lfs.lib;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lib;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.nls;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.transport.http;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.transport.http.apache;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.util;version="[5.1.17,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 e94ea85..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.17-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 b4d199a..d4751b5 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 f008be9..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.17.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.17,5.2.0)",
- org.eclipse.jgit.junit;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lfs;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lfs.errors;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lfs.lib;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lib;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.revwalk;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.treewalk;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.treewalk.filter;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.util;version="[5.1.17,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.17";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 46588cd..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.17-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/.settings/.api_filters b/org.eclipse.jgit.lfs/.settings/.api_filters
deleted file mode 100644
index e31567f..0000000
--- a/org.eclipse.jgit.lfs/.settings/.api_filters
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<component id="org.eclipse.jgit.lfs" version="2">
-    <resource path="META-INF/MANIFEST.MF">
-        <filter id="924844039">
-            <message_arguments>
-                <message_argument value="5.1.13"/>
-                <message_argument value="5.1.0"/>
-            </message_arguments>
-        </filter>
-    </resource>
-</component>
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 2aef075..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.17.qualifier
+Bundle-Version: 5.2.3.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
-Export-Package: org.eclipse.jgit.lfs;version="5.1.17",
- org.eclipse.jgit.lfs.errors;version="5.1.17",
- org.eclipse.jgit.lfs.internal;version="5.1.17";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.17"
+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.17,5.2.0)";resolution:=optional,
- org.eclipse.jgit.api.errors;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.attributes;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.diff;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.errors;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.hooks;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lib;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.nls;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.revwalk;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.storage.file;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.storage.pack;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.transport;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.transport.http;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.treewalk;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.treewalk.filter;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.util;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.util.io;version="[5.1.17,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 baad1c0..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.17-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 c8bb8a8..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.17.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 bf0515e..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.17-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 4a5790a..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.17.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 fac9a7b..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.17-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 2478031..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.17.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 ef0c4c3..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.17-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 a79d8e3..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.17.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 8b3e86c..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.17-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 45a273d..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.17.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.17" match="equivalent"/>
-      <import feature="org.eclipse.jgit.lfs" version="5.1.17" 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 5d6aac8..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.17-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 7638402..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.17.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 e2e648f..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.17-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 16e4a03..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.17-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 5e19ab9..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.17.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 8ca8965..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.17-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 0ef38ca..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.17.qualifier
+Bundle-Version: 5.2.3.qualifier
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target
new file mode 100644
index 0000000..268fbc7
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<?pde?>
+<!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
+<target name="jgit-4.9-staging" sequenceNumber="1638575042">
+  <locations>
+    <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
+      <unit id="org.eclipse.jetty.client" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.client.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.continuation" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.continuation.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.http" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.http.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.io" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.io.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.security" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.security.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.server" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.server.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.servlet" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.servlet.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.util" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.util.source" version="9.4.11.v20180605"/>
+      <repository id="jetty-9.4.11" location="https://archive.eclipse.org/jetty/updates/jetty-bundles-9.x/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.10.5.v20190526-1402"/>
+      <unit id="org.apache.ant.source" version="1.10.5.v20190526-1402"/>
+      <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.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.6.v20190503-0009"/>
+      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.6.v20190503-0009"/>
+      <unit id="org.apache.httpcomponents.httpcore" version="4.4.10.v20190123-2214"/>
+      <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.10.v20190123-2214"/>
+      <unit id="org.apache.log4j" version="1.2.15.v201012070815"/>
+      <unit id="org.apache.log4j.source" version="1.2.15.v201012070815"/>
+      <unit id="org.kohsuke.args4j" version="2.33.0.v20160323-2218"/>
+      <unit id="org.kohsuke.args4j.source" version="2.33.0.v20160323-2218"/>
+      <unit id="org.hamcrest" version="1.1.0.v20090501071000"/>
+      <unit id="org.hamcrest.core" version="1.3.0.v20180420-1519"/>
+      <unit id="org.hamcrest.core.source" version="1.3.0.v20180420-1519"/>
+      <unit id="org.hamcrest.library" version="1.3.0.v20180524-2246"/>
+      <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="2.6.0.v20180420-1519"/>
+      <unit id="org.objenesis.source" version="2.6.0.v20180420-1519"/>
+      <unit id="org.mockito" version="2.23.0.v20190527-1420"/>
+      <unit id="org.mockito.source" version="2.23.0.v20190527-1420"/>
+      <unit id="net.bytebuddy.byte-buddy" version="1.9.0.v20181107-1410"/>
+      <unit id="net.bytebuddy.byte-buddy.source" version="1.9.0.v20181107-1410"/>
+      <unit id="net.bytebuddy.byte-buddy-agent" version="1.9.0.v20181106-1534"/>
+      <unit id="net.bytebuddy.byte-buddy-agent.source" version="1.9.0.v20181106-1534"/>
+      <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.55.v20190404-1902"/>
+      <unit id="com.jcraft.jsch.source" version="0.1.55.v20190404-1902"/>
+      <unit id="org.junit" version="4.12.0.v201504281640"/>
+      <unit id="org.junit.source" version="4.12.0.v201504281640"/>
+      <unit id="javax.servlet" version="3.1.0.v201410161800"/>
+      <unit id="javax.servlet.source" version="3.1.0.v201410161800"/>
+      <unit id="org.tukaani.xz" version="1.6.0.v20170629-1752"/>
+      <unit id="org.tukaani.xz.source" version="1.6.0.v20170629-1752"/>
+      <unit id="org.slf4j.api" version="1.7.2.v20121108-1250"/>
+      <unit id="org.slf4j.api.source" version="1.7.2.v20121108-1250"/>
+      <unit id="org.slf4j.impl.log4j12" version="1.7.2.v20131105-2200"/>
+      <unit id="org.slf4j.impl.log4j12.source" version="1.7.2.v20131105-2200"/>
+      <unit id="com.jcraft.jzlib" version="1.1.1.v201205102305"/>
+      <unit id="com.jcraft.jzlib.source" version="1.1.1.v201205102305"/>
+      <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/R20190602212107/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/releases/2018-12/"/>
+    </location>
+  </locations>
+</target>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.tpd
new file mode 100644
index 0000000..71230d7
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.tpd
@@ -0,0 +1,8 @@
+target "jgit-4.9-staging" with source configurePhase
+
+include "projects/jetty-9.4.11.tpd"
+include "orbit/R20190602212107-2019-06.tpd"
+
+location "http://download.eclipse.org/releases/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 c5665e8..15ee446 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/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.5" sequenceNumber="1638435262">
+<target name="jgit-4.5" sequenceNumber="1638575045">
   <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="https://archive.eclipse.org/jetty/updates/jetty-bundles-9.x/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.v20190526-1402"/>
+      <unit id="org.apache.ant.source" version="1.10.5.v20190526-1402"/>
       <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.6.v20190503-0009"/>
@@ -70,6 +70,12 @@
       <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"/>
+      <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/R20190602212107/repository"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
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 69a0790..bc1c54f 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/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.6" sequenceNumber="1638435262">
+<target name="jgit-4.6" sequenceNumber="1638575051">
   <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="https://archive.eclipse.org/jetty/updates/jetty-bundles-9.x/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.v20190526-1402"/>
+      <unit id="org.apache.ant.source" version="1.10.5.v20190526-1402"/>
       <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.6.v20190503-0009"/>
@@ -70,6 +70,12 @@
       <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"/>
+      <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/R20190602212107/repository"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
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 3ce2421..596e3c4 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/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.7" sequenceNumber="1638435262">
+<target name="jgit-4.7" sequenceNumber="1638575047">
   <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="https://archive.eclipse.org/jetty/updates/jetty-bundles-9.x/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.v20190526-1402"/>
+      <unit id="org.apache.ant.source" version="1.10.5.v20190526-1402"/>
       <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.6.v20190503-0009"/>
@@ -70,6 +70,12 @@
       <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"/>
+      <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/R20190602212107/repository"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
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 86a0802..a0e0948 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/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.8" sequenceNumber="1638435262">
+<target name="jgit-4.8" sequenceNumber="1638575042">
   <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="https://archive.eclipse.org/jetty/updates/jetty-bundles-9.x/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.v20190526-1402"/>
+      <unit id="org.apache.ant.source" version="1.10.5.v20190526-1402"/>
       <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.6.v20190503-0009"/>
@@ -70,6 +70,12 @@
       <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"/>
+      <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/R20190602212107/repository"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target
index 568543e..a4dd92c 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.9" sequenceNumber="1638435262">
+<target name="jgit-4.9" sequenceNumber="1638575042">
   <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="https://archive.eclipse.org/jetty/updates/jetty-bundles-9.x/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.v20190526-1402"/>
+      <unit id="org.apache.ant.source" version="1.10.5.v20190526-1402"/>
       <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.6.v20190503-0009"/>
@@ -70,6 +70,12 @@
       <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"/>
+      <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/R20190602212107/repository"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20180905201904-2018-09.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20180905201904-2018-09.tpd
new file mode 100644
index 0000000..eaae99c
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20180905201904-2018-09.tpd
@@ -0,0 +1,48 @@
+target "R20180905201904-2018-09" with source configurePhase
+// see http://download.eclipse.org/tools/orbit/downloads/
+
+location "http://download.eclipse.org/tools/orbit/downloads/drops/R20180905201904/repository" {
+	org.apache.ant [1.9.6.v201510161327,1.9.6.v201510161327]
+	org.apache.ant.source [1.9.6.v201510161327,1.9.6.v201510161327]
+	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.15.0.v20180119-1613,1.15.0.v20180119-1613]
+	org.apache.commons.compress.source [1.15.0.v20180119-1613,1.15.0.v20180119-1613s]
+	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 [1.0.0.v201505121915,1.0.0.v201505121915]
+	org.objenesis.source [1.0.0.v201505121915,1.0.0.v201505121915]
+	org.mockito [1.8.4.v201303031500,1.8.4.v201303031500]
+	org.mockito.source [1.8.4.v201303031500,1.8.4.v201303031500]
+	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]
+}
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/orbit/R20190602212107-2019-06.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20190602212107-2019-06.tpd
index f49571e..b503092 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20190602212107-2019-06.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20190602212107-2019-06.tpd
@@ -2,12 +2,12 @@
 // see http://download.eclipse.org/tools/orbit/downloads/
 
 location "http://download.eclipse.org/tools/orbit/downloads/drops/R20190602212107/repository" {
-	org.apache.ant [1.9.6.v201510161327,1.9.6.v201510161327]
-	org.apache.ant.source [1.9.6.v201510161327,1.9.6.v201510161327]
+	org.apache.ant [1.10.5.v20190526-1402,1.10.5.v20190526-1402]
+	org.apache.ant.source [1.10.5.v20190526-1402,1.10.5.v20190526-1402]
 	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.15.0.v20180119-1613,1.15.0.v20180119-1613]
-	org.apache.commons.compress.source [1.15.0.v20180119-1613,1.15.0.v20180119-1613s]
+	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.6.v20190503-0009,4.5.6.v20190503-0009]
@@ -49,4 +49,10 @@
 	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 a5a705f..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.17-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 5a3b10c..35fa738 100644
--- a/org.eclipse.jgit.packaging/pom.xml
+++ b/org.eclipse.jgit.packaging/pom.xml
@@ -49,7 +49,7 @@
 
   <groupId>org.eclipse.jgit</groupId>
   <artifactId>jgit.tycho.parent</artifactId>
-  <version>5.1.17-SNAPSHOT</version>
+  <version>5.2.3-SNAPSHOT</version>
   <packaging>pom</packaging>
 
   <name>JGit Tycho Parent</name>
@@ -75,10 +75,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>
@@ -111,6 +113,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 b4d199a..d4751b5 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 7deb65e..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.17.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.17,5.2.0)",
- org.eclipse.jgit.api.errors;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.diff;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.dircache;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.internal.storage.file;version="5.1.17",
- org.eclipse.jgit.junit;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lib;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.merge;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.pgm;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.pgm.internal;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.pgm.opt;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.revwalk;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.storage.file;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.transport;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.treewalk;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.util;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.util.io;version="[5.1.17,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 f435b35..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.17-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/.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 00c48d1..18607ea 100644
--- a/org.eclipse.jgit.pgm/BUILD
+++ b/org.eclipse.jgit.pgm/BUILD
@@ -9,6 +9,7 @@
     deps = [
         ":services",
         "//lib:args4j",
+        "//lib:commons-logging",
         "//lib:httpclient",
         "//lib:httpcore",
         "//lib:jetty-http",
@@ -23,6 +24,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 6024f88..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.17.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.17,5.2.0)",
- org.eclipse.jgit.api.errors;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.archive;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.awtui;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.blame;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.diff;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.dircache;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.errors;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.gitrepo;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.internal.ketch;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.internal.storage.io;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.internal.storage.pack;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.internal.storage.reftable;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.internal.storage.reftree;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lfs;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lfs.lib;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lfs.server;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lfs.server.fs;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lfs.server.s3;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lib;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.merge;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.nls;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.notes;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.revplot;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.revwalk;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.revwalk.filter;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.storage.file;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.storage.pack;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.transport;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.transport.http.apache;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.transport.resolver;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.treewalk;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.treewalk.filter;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.util;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.util.io;version="[5.1.17,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.17";
+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.17";
+ 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.17";
+ 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.17";x-friends:="org.eclipse.jgit.pgm.test,org.eclipse.jgit.test",
- org.eclipse.jgit.pgm.opt;version="5.1.17";
+ 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 6c2ac31..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.17.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.pgm;version="5.1.17.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 fa49549..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.17-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 08a3d7e..bbf9366 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
@@ -398,6 +398,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
@@ -407,7 +408,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/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..98a38ff
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/ApacheSshTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.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..42388ad
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/BUILD
@@ -0,0 +1,21 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
+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 b4d199a..d4751b5 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 9145671..2858427 100644
--- a/org.eclipse.jgit.test/BUILD
+++ b/org.eclipse.jgit.test/BUILD
@@ -7,7 +7,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",
@@ -19,6 +22,7 @@
     "nls/NonTranslatedBundle.java",
     "revwalk/RevQueueTestCase.java",
     "revwalk/RevWalkTestCase.java",
+    "transport/ObjectIdMatcher.java",
     "transport/SpiTransport.java",
     "treewalk/filter/AlwaysCloneTreeFilter.java",
     "test/resources/SampleDataRepositoryTestCase.java",
@@ -31,6 +35,8 @@
     PKG + "lib/sorttest.gitindex.dat",
 ]
 
+RESOURCES = glob(["resources/**"])
+
 tests(tests = glob(
     ["tst/**/*.java"],
     exclude = HELPERS + DATA,
@@ -42,13 +48,35 @@
     srcs = HELPERS,
     resources = DATA,
     deps = [
+        "//lib:jsch",
         "//lib:junit",
+        "//lib:mockito",
         "//lib:slf4j-simple",
         "//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 701305f..8bd041c 100644
--- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
 Bundle-Name: %plugin_name
 Automatic-Module-Name: org.eclipse.jgit.test
 Bundle-SymbolicName: org.eclipse.jgit.test
-Bundle-Version: 5.1.17.qualifier
+Bundle-Version: 5.2.3.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
@@ -17,63 +17,68 @@
  org.apache.commons.compress.compressors.bzip2;version="[1.15.0,2.0)",
  org.apache.commons.compress.compressors.gzip;version="[1.15.0,2.0)",
  org.apache.commons.compress.compressors.xz;version="[1.15.0,2.0)",
- org.eclipse.jgit.api;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.api.errors;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.archive;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.attributes;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.awtui;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.blame;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.diff;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.dircache;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.errors;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.events;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.fnmatch;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.gitrepo;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.hooks;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.ignore;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.ignore.internal;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.internal;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.internal.fsck;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.internal.storage.io;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.internal.storage.pack;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.internal.storage.reftable;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.internal.storage.reftree;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.junit;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.junit.time;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lfs;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lib;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.merge;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.nls;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.notes;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.patch;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.pgm;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.pgm.internal;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.revplot;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.revwalk;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.revwalk.filter;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.storage.file;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.storage.pack;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.submodule;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.transport;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.transport.http;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.transport.resolver;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.treewalk;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.treewalk.filter;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.util;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.util.io;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.util.sha1;version="[5.1.17,5.2.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.archive;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.junit.time;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.23.0,3.0.0)",
+ org.mockito.invocation;version="[2.23.0,3.0.0)",
  org.mockito.junit;version="[2.23.0,3.0.0)",
- org.mockito.stubbing;version="2.23.0",
- org.objenesis;version="2.6.0",
+ org.mockito.stubbing;version="[2.23.0,3.0.0)",
+ org.objenesis;version="[2.6.0,3.0.0)",
  org.slf4j;version="[1.7.0,2.0.0)",
  org.tukaani.xz;version="[1.6.0,2.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 a3e3f86..e6a99b2 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.17-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>
 
@@ -109,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>
@@ -155,6 +159,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>
@@ -170,7 +186,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 345da81..d2f6d70 100644
--- a/org.eclipse.jgit.test/tests.bzl
+++ b/org.eclipse.jgit.test/tests.bzl
@@ -42,13 +42,24 @@
             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",
+            ]
         if src.endswith("ArchiveCommandTest.java"):
             additional_deps = [
                 "//lib:commons-compress",
                 "//lib:xz",
                 "//org.eclipse.jgit.archive:jgit-archive",
             ]
-
         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 c67c86a9..687926b 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 fbec024..0f2e6b8 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;
 
@@ -406,7 +407,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);
 		}
 
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 08ad7b8..65c20aa 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
@@ -160,7 +160,7 @@
 	}
 
 	@Test
-	public void testCheckoutWithConflict() {
+	public void testCheckoutWithConflict() throws Exception {
 		CheckoutCommand co = git.checkout();
 		try {
 			writeTrashFile("Test.txt", "Another change");
@@ -171,6 +171,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 613ca5c..f5f6529 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 43c0051..2a2a6ba 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;
@@ -1304,7 +1305,7 @@
 					return "";
 				}
 				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 588387d..dd7230b 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
@@ -86,6 +86,8 @@
 
 	private File indexFile;
 
+	private File indexNestedFile;
+
 	private File untrackedFile;
 
 	private DirCacheEntry prestage;
@@ -101,7 +103,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();
@@ -123,13 +125,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();
@@ -152,6 +157,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 d9a4203..50753ae 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
@@ -90,6 +90,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 f23e4be..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;
 
@@ -141,6 +146,7 @@
 
 	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 8cc06d9..1d3ca03 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;
 
@@ -193,7 +194,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();
@@ -209,7 +211,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 a450969..9eb1816 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
@@ -335,9 +335,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/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 2d0fe86..22dc471 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,12 +48,13 @@
 
 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.MICROSECONDS;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.MINUTES;
 import static java.util.concurrent.TimeUnit.NANOSECONDS;
-import static java.util.concurrent.TimeUnit.MICROSECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.eclipse.jgit.util.FileUtils.pathToString;
 import static org.junit.Assert.assertArrayEquals;
@@ -68,11 +69,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;
@@ -80,6 +85,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;
@@ -96,6 +102,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();
 
@@ -692,11 +704,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"));
@@ -713,11 +721,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"));
@@ -803,11 +807,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()) {
@@ -826,7 +828,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));
@@ -837,15 +839,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));
 	}
 
@@ -854,15 +854,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));
 	}
 
@@ -883,15 +881,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
@@ -906,6 +902,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));
 	}
@@ -1229,4 +1525,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/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/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/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 61ab042..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
@@ -302,7 +302,7 @@
 		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", UTF_8.name());
+		fmt.formatMerge(bo, r, "B", "O", "T", UTF_8);
 		return new String(bo.toByteArray(), UTF_8);
 	}
 
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 f22b7d6..fa02227 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
@@ -974,7 +974,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/storage/file/FileBasedConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/FileBasedConfigTest.java
index cfb2735..8b9869a 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
@@ -54,6 +54,7 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.StandardOpenOption;
+import java.util.StringTokenizer;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -73,16 +74,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 Path trash;
 
 	private MockSystemReader mockSystemReader;
@@ -102,7 +110,7 @@
 
 	@Test
 	public void testSystemEncoding() throws IOException, ConfigInvalidException {
-		final Path file = createFile(CONTENT1.getBytes());
+		final Path file = createFile(CONTENT1.getBytes(UTF_8));
 		final FileBasedConfig config = new FileBasedConfig(file.toFile(),
 				FS.DETECTED);
 		config.load();
@@ -110,7 +118,7 @@
 
 		config.setString(USER, null, NAME, BOB);
 		config.save();
-		assertArrayEquals(CONTENT2.getBytes(), IO.readFully(file.toFile()));
+		assertArrayEquals(CONTENT2.getBytes(UTF_8), IO.readFully(file.toFile()));
 	}
 
 	@Test
@@ -123,7 +131,7 @@
 
 		config.setString(USER, null, NAME, BOB);
 		config.save();
-		assertArrayEquals(CONTENT2.getBytes(), IO.readFully(file.toFile()));
+		assertArrayEquals(CONTENT2.getBytes(UTF_8), IO.readFully(file.toFile()));
 	}
 
 	@Test
@@ -154,8 +162,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 Path file = createFile(bos1.toByteArray());
 		final FileBasedConfig config = new FileBasedConfig(file.toFile(),
@@ -167,18 +175,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.toFile()));
 	}
 
 	@Test
 	public void testIncludeAbsolute()
 			throws IOException, ConfigInvalidException {
-		final Path includedFile = createFile(CONTENT1.getBytes());
+		final Path includedFile = createFile(CONTENT1.getBytes(UTF_8));
 		final ByteArrayOutputStream bos = new ByteArrayOutputStream();
-		bos.write("[include]\npath=".getBytes());
-		bos.write(pathToString(includedFile.toFile()).getBytes());
+		bos.write("[include]\npath=".getBytes(UTF_8));
+		bos.write(pathToString(includedFile.toFile()).getBytes(UTF_8));
 
 		final Path file = createFile(bos.toByteArray());
 		final FileBasedConfig config = new FileBasedConfig(file.toFile(),
@@ -190,10 +198,10 @@
 	@Test
 	public void testIncludeRelativeDot()
 			throws IOException, ConfigInvalidException {
-		final Path includedFile = createFile(CONTENT1.getBytes(), "dir1");
+		final Path includedFile = createFile(CONTENT1.getBytes(UTF_8), "dir1");
 		final ByteArrayOutputStream bos = new ByteArrayOutputStream();
-		bos.write("[include]\npath=".getBytes());
-		bos.write(("./" + includedFile.getFileName()).getBytes());
+		bos.write("[include]\npath=".getBytes(UTF_8));
+		bos.write(("./" + includedFile.getFileName()).getBytes(UTF_8));
 
 		final Path file = createFile(bos.toByteArray(), "dir1");
 		final FileBasedConfig config = new FileBasedConfig(file.toFile(),
@@ -205,11 +213,11 @@
 	@Test
 	public void testIncludeRelativeDotDot()
 			throws IOException, ConfigInvalidException {
-		final Path includedFile = createFile(CONTENT1.getBytes(), "dir1");
+		final Path 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.getParent().getFileName() + "/"
-				+ includedFile.getFileName()).getBytes());
+				+ includedFile.getFileName()).getBytes(UTF_8));
 
 		final Path file = createFile(bos.toByteArray(), "dir2");
 		final FileBasedConfig config = new FileBasedConfig(file.toFile(),
@@ -221,10 +229,10 @@
 	@Test
 	public void testIncludeRelativeDotDotNotFound()
 			throws IOException, ConfigInvalidException {
-		final Path includedFile = createFile(CONTENT1.getBytes());
+		final Path includedFile = createFile(CONTENT1.getBytes(UTF_8));
 		final ByteArrayOutputStream bos = new ByteArrayOutputStream();
-		bos.write("[include]\npath=".getBytes());
-		bos.write(("../" + includedFile.getFileName()).getBytes());
+		bos.write("[include]\npath=".getBytes(UTF_8));
+		bos.write(("../" + includedFile.getFileName()).getBytes(UTF_8));
 
 		final Path file = createFile(bos.toByteArray());
 		final FileBasedConfig config = new FileBasedConfig(file.toFile(),
@@ -236,10 +244,10 @@
 	@Test
 	public void testIncludeWithTilde()
 			throws IOException, ConfigInvalidException {
-		final Path includedFile = createFile(CONTENT1.getBytes(), "home");
+		final Path includedFile = createFile(CONTENT1.getBytes(UTF_8), "home");
 		final ByteArrayOutputStream bos = new ByteArrayOutputStream();
-		bos.write("[include]\npath=".getBytes());
-		bos.write(("~/" + includedFile.getFileName()).getBytes());
+		bos.write("[include]\npath=".getBytes(UTF_8));
+		bos.write(("~/" + includedFile.getFileName()).getBytes(UTF_8));
 
 		final Path file = createFile(bos.toByteArray(), "repo");
 		final FS fs = FS.DETECTED.newInstance();
@@ -251,6 +259,51 @@
 	}
 
 	@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 Path includedFile = createFile(CONTENT3.getBytes(UTF_8), "dir1");
+
+		final Path file = createFile(new byte[0], "dir2");
+		FileBasedConfig config = new FileBasedConfig(file.toFile(),
+				FS.DETECTED);
+		config.setString("include", null, "path",
+				("../" + includedFile.getParent().getFileName() + "/"
+						+ includedFile.getFileName()));
+
+		// 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.toFile(), 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));
+	}
+
+	@Test
 	public void testSavedConfigFileShouldNotReadUserGitConfig()
 			throws IOException {
 		AtomicBoolean userConfigTimeRead = new AtomicBoolean(false);
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 0358718..2e5027f 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;
@@ -70,6 +69,7 @@
 import org.junit.Test;
 
 import com.jcraft.jsch.ConfigRepository;
+import com.jcraft.jsch.ConfigRepository.Config;
 
 public class OpenSshConfigTest extends RepositoryTestCase {
 	private File home;
@@ -173,6 +173,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");
@@ -343,21 +357,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/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/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/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/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 57bb68e..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.17.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.17"
-Import-Package: org.eclipse.jgit.errors;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.lib;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.nls;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.revplot;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.revwalk;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.transport;version="[5.1.17,5.2.0)",
- org.eclipse.jgit.util;version="[5.1.17,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 e95c069..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.17-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 c8da358..998c7cd 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -1,13 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <component id="org.eclipse.jgit" version="2">
-    <resource path="META-INF/MANIFEST.MF">
-        <filter id="924844039">
-            <message_arguments>
-                <message_argument value="5.1.13"/>
-                <message_argument value="5.1.0"/>
-            </message_arguments>
-        </filter>
-    </resource>
     <resource path="src/org/eclipse/jgit/dircache/DirCacheEntry.java" type="org.eclipse.jgit.dircache.DirCacheEntry">
         <filter id="1142947843">
             <message_arguments>
@@ -42,12 +34,29 @@
             </message_arguments>
         </filter>
     </resource>
-    <resource path="src/org/eclipse/jgit/lib/ConfigConstants.java" type="org.eclipse.jgit.lib.ConfigConstants">
-        <filter id="337768515">
+    <resource path="src/org/eclipse/jgit/gitrepo/RepoCommand.java" type="org.eclipse.jgit.gitrepo.RepoCommand$DefaultRemoteReader">
+        <filter id="338792546">
             <message_arguments>
-                <message_argument value="org.eclipse.jgit.lib.ConfigConstants"/>
+                <message_argument value="org.eclipse.jgit.gitrepo.RepoCommand.DefaultRemoteReader"/>
+                <message_argument value="readFile(String, String, String)"/>
             </message_arguments>
         </filter>
+        <filter id="338792546">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.gitrepo.RepoCommand.DefaultRemoteReader"/>
+                <message_argument value="readFileFromRepo(Repository, String, String)"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/gitrepo/RepoCommand.java" type="org.eclipse.jgit.gitrepo.RepoCommand$RemoteReader">
+        <filter id="403804204">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.gitrepo.RepoCommand.RemoteReader"/>
+                <message_argument value="readFileWithMode(String, String, String)"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/lib/ConfigConstants.java" type="org.eclipse.jgit.lib.ConfigConstants">
         <filter id="1142947843">
             <message_arguments>
                 <message_argument value="5.1.13"/>
@@ -119,6 +128,26 @@
             </message_arguments>
         </filter>
     </resource>
+    <resource path="src/org/eclipse/jgit/revwalk/DepthWalk.java" type="org.eclipse.jgit.revwalk.DepthWalk">
+        <filter id="403804204">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.revwalk.DepthWalk"/>
+                <message_argument value="getDeepenNotFlag()"/>
+            </message_arguments>
+        </filter>
+        <filter id="404000815">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.revwalk.DepthWalk"/>
+                <message_argument value="getDeepenNots()"/>
+            </message_arguments>
+        </filter>
+        <filter id="404000815">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.revwalk.DepthWalk"/>
+                <message_argument value="getDeepenSince()"/>
+            </message_arguments>
+        </filter>
+    </resource>
     <resource path="src/org/eclipse/jgit/storage/file/WindowCacheConfig.java" type="org.eclipse.jgit.storage.file.WindowCacheConfig">
         <filter id="1142947843">
             <message_arguments>
@@ -198,10 +227,11 @@
             </message_arguments>
         </filter>
     </resource>
-    <resource path="src/org/eclipse/jgit/transport/GitProtocolConstants.java" type="org.eclipse.jgit.transport.GitProtocolConstants">
-        <filter id="337768515">
+    <resource path="src/org/eclipse/jgit/transport/RemoteSession.java" type="org.eclipse.jgit.transport.RemoteSession">
+        <filter id="404000815">
             <message_arguments>
-                <message_argument value="org.eclipse.jgit.transport.GitProtocolConstants"/>
+                <message_argument value="org.eclipse.jgit.transport.RemoteSession"/>
+                <message_argument value="getFtpChannel()"/>
             </message_arguments>
         </filter>
     </resource>
@@ -219,6 +249,14 @@
             </message_arguments>
         </filter>
     </resource>
+    <resource path="src/org/eclipse/jgit/transport/http/HttpConnection.java" type="org.eclipse.jgit.transport.http.HttpConnection">
+        <filter id="403804204">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.transport.http.HttpConnection"/>
+                <message_argument value="getHeaderFields(String)"/>
+            </message_arguments>
+        </filter>
+    </resource>
     <resource path="src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java" type="org.eclipse.jgit.treewalk.WorkingTreeIterator">
         <filter id="1142947843">
             <message_arguments>
@@ -242,13 +280,6 @@
         </filter>
     </resource>
     <resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS">
-        <filter id="1141899266">
-            <message_arguments>
-                <message_argument value="4.7"/>
-                <message_argument value="5.1"/>
-                <message_argument value="createNewFileAtomic(File)"/>
-            </message_arguments>
-        </filter>
         <filter id="1142947843">
             <message_arguments>
                 <message_argument value="4.5.6"/>
@@ -302,15 +333,6 @@
             </message_arguments>
         </filter>
     </resource>
-    <resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS$LockToken">
-        <filter id="1141899266">
-            <message_arguments>
-                <message_argument value="4.7"/>
-                <message_argument value="5.1"/>
-                <message_argument value="LockToken"/>
-            </message_arguments>
-        </filter>
-    </resource>
     <resource path="src/org/eclipse/jgit/util/FileUtils.java" type="org.eclipse.jgit.util.FileUtils">
         <filter id="1142947843">
             <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 a7354e1..66701b0 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.17.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.17",
- org.eclipse.jgit.api;version="5.1.17";
+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.17";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.errors",
- org.eclipse.jgit.attributes;version="5.1.17",
- org.eclipse.jgit.blame;version="5.1.17";
+ 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.17";
+ 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.17";
+ 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.17";
+ 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.17";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.fnmatch;version="5.1.17",
- org.eclipse.jgit.gitrepo;version="5.1.17";
+ 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.17";x-internal:=true,
- org.eclipse.jgit.hooks;version="5.1.17";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.ignore;version="5.1.17",
- org.eclipse.jgit.ignore.internal;version="5.1.17";x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal;version="5.1.17";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test",
- org.eclipse.jgit.internal.fsck;version="5.1.17";x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.ketch;version="5.1.17";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.dfs;version="5.1.17";
+ 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.17";
+ 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.17";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.pack;version="5.1.17";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.reftable;version="5.1.17";
-  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.17";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.lib;version="5.1.17";
+   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.http.server",
+ 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.17";x-internal:=true,
- org.eclipse.jgit.merge;version="5.1.17";
+ 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.17",
- org.eclipse.jgit.notes;version="5.1.17";
+ 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.17";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.diff",
- org.eclipse.jgit.revplot;version="5.1.17";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.revwalk",
- org.eclipse.jgit.revwalk;version="5.1.17";
+ 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.17";uses:="org.eclipse.jgit.revwalk,org.eclipse.jgit.lib,org.eclipse.jgit.util",
- org.eclipse.jgit.storage.file;version="5.1.17";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.util",
- org.eclipse.jgit.storage.pack;version="5.1.17";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.submodule;version="5.1.17";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.treewalk.filter,org.eclipse.jgit.treewalk",
- org.eclipse.jgit.transport;version="5.1.17";
+ 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.17";uses:="javax.net.ssl",
- org.eclipse.jgit.transport.resolver;version="5.1.17";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport",
- org.eclipse.jgit.treewalk;version="5.1.17";
+ 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.17";uses:="org.eclipse.jgit.treewalk",
- org.eclipse.jgit.util;version="5.1.17";
+ 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.17",
- org.eclipse.jgit.util.sha1;version="5.1.17",
- org.eclipse.jgit.util.time;version="5.1.17"
+ 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 e0e1159..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.17.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit;version="5.1.17.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 47ac544..c30d648 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.17-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 c97b61b..504e793 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -423,6 +423,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}
@@ -590,8 +591,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
@@ -716,7 +717,9 @@
 uriNotFoundWithMessage={0} not found: {1}
 URINotSupported=URI not supported: {0}
 userConfigInvalid=Git config in the user's home directory {0} is 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 eee3da6..10a54ee 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/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 5110d77..8aa97df 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
@@ -525,7 +525,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;
@@ -600,6 +600,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 5a73cdc..e9d86df 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
@@ -59,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;
@@ -80,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;
@@ -90,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;
 
 /**
@@ -144,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
@@ -165,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
@@ -183,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")
@@ -587,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()) {
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 45f98ce..db3caec 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -484,6 +484,7 @@
 	/***/ public String newlineInQuotesNotAllowed;
 	/***/ public String noApplyInDelete;
 	/***/ public String noClosingBracket;
+	/***/ public String noCommitsSelectedForShallow;
 	/***/ public String noCredentialsProvider;
 	/***/ public String noHEADExistsAndNoExplicitStartingRevisionWasSpecified;
 	/***/ public String noHMACsupport;
@@ -777,7 +778,9 @@
 	/***/ public String uriNotFoundWithMessage;
 	/***/ public String URINotSupported;
 	/***/ public String userConfigInvalid;
+	/***/ 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/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..c1e94a0
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java
@@ -0,0 +1,924 @@
+/*
+ * 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.time.Instant;
+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.FS;
+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 Instant 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 Instant mtime = FS.DETECTED.lastModifiedInstant(configFile);
+		if (!mtime.equals(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 ba3160f..6ab5c14 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 003ce23..c72395f 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";
 
@@ -464,6 +494,20 @@
 	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";
+
+	/**
 	 * The "filesystem" section
 	 * @since 5.1.9
 	 */
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/DefaultTypedConfigGetter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
index 001ae93..fb23939 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
@@ -301,7 +301,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/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 2a2699f..77d268a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
@@ -1981,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 e37f207..909f3b1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
@@ -1029,7 +1029,7 @@
 				db != null ? nonNullRepo().getDirectory() : null, inCoreLimit);
 		try {
 			new MergeFormatter().formatMerge(buf, result,
-					Arrays.asList(commitNames), UTF_8.name());
+					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 34829a2..072938b 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;
@@ -283,7 +282,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 7924ec8..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,7 +274,7 @@
 	}
 
 	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(), //$NON-NLS-1$
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 4f1eba6..7dd019b 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.time.Instant;
 import java.util.Collection;
 import java.util.HashMap;
@@ -214,7 +217,8 @@
 		this.hosts.clear();
 		this.lastModified = FS.DETECTED.lastModifiedInstant(this.netrc);
 
-		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 4dd5df9..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,30 +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.time.Instant;
-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;
 
@@ -84,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
@@ -93,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>
@@ -154,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 Instant 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());
 	}
 
 	/**
@@ -203,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 Instant mtime = FS.DETECTED.lastModifiedInstant(configFile);
-		if (!mtime.equals(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());
 	}
 
 	/**
@@ -830,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}.
 		 */
@@ -839,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);
 		}
 
 		/**
@@ -911,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;
 		}
 
@@ -958,7 +352,7 @@
 					+ ", preferredAuthentications=" + preferredAuthentications
 					+ ", batchMode=" + batchMode + ", strictHostKeyChecking="
 					+ strictHostKeyChecking + ", connectionAttempts="
-					+ connectionAttempts + ", config=" + config + "]";
+					+ connectionAttempts + ", entry=" + entry + "]";
 		}
 	}
 
@@ -978,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/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/WorkingTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
index 83be8f6..c8b3ef3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
@@ -1052,7 +1052,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 7d37cfa..a9cef59 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
@@ -1703,6 +1703,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);
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/SimpleLruCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SimpleLruCache.java
index 709d9ee..3fcfd21 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SimpleLruCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SimpleLruCache.java
@@ -162,7 +162,7 @@
 	public V get(Object key) {
 		Entry<K, V> entry = map.get(key);
 		if (entry != null) {
-			entry.lastAccessed = ++time;
+			entry.lastAccessed = tick();
 			return entry.value;
 		}
 		return null;
@@ -186,13 +186,18 @@
 	 *             if the specified key or value is null
 	 */
 	public V put(@NonNull K key, @NonNull V value) {
-		map.put(key, new Entry<>(key, value, ++time));
+		map.put(key, new Entry<>(key, value, tick()));
 		if (map.size() > maximumSize) {
 			purge();
 		}
 		return value;
 	}
 
+	@SuppressWarnings("NonAtomicVolatileUpdate")
+	private long tick() {
+		return ++time;
+	}
+
 	/**
 	 * Returns the current size of this cache
 	 *
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 5313f2c..df604a6 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.17-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.55</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,6 +202,8 @@
     <maven-javadoc-plugin-version>3.3.1</maven-javadoc-plugin-version>
     <tycho-extras-version>1.7.0</tycho-extras-version>
     <gson-version>2.8.2</gson-version>
+    <maven-project-info-reports-plugin-version>3.1.2</maven-project-info-reports-plugin-version>
+    <maven-jxr-plugin-version>3.1.1</maven-jxr-plugin-version>
     <spotbugs-maven-plugin-version>4.3.0</spotbugs-maven-plugin-version>
     <maven-surefire-plugin-version>3.0.0-M5</maven-surefire-plugin-version>
     <maven-surefire-report-plugin-version>${maven-surefire-plugin-version}</maven-surefire-report-plugin-version>
@@ -387,7 +375,7 @@
             <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>
@@ -399,12 +387,12 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-jxr-plugin</artifactId>
-          <version>3.1.1</version>
+          <version>${maven-jxr-plugin-version}</version>
         </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-project-info-reports-plugin</artifactId>
-          <version>3.0.0</version>
+          <version>${maven-project-info-reports-plugin-version}</version>
         </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
@@ -581,7 +569,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>
@@ -591,7 +579,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>
@@ -603,11 +591,15 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-project-info-reports-plugin</artifactId>
-        <version>3.1.2</version>
+        <version>${maven-project-info-reports-plugin-version}</version>
         <reportSets>
           <reportSet>
             <reports>
               <report>dependencies</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>
@@ -950,11 +942,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>
@@ -962,6 +956,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>
     <module>org.eclipse.jgit.benchmarks</module>
   </modules>
 
diff --git a/tools/BUILD b/tools/BUILD
index e69de29..f1b1536 100644
--- a/tools/BUILD
+++ b/tools/BUILD
@@ -0,0 +1,108 @@
+load("@rules_java//java:defs.bzl", "java_package_configuration")
+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'